From ebefbac36ab9ee914595effd1578feb03f495be0 Mon Sep 17 00:00:00 2001 From: audristroyer Date: Fri, 12 Jun 2026 18:20:51 -0400 Subject: [PATCH 1/3] harden: subprocess timeout on codec exec + add LICENSE (audit 6.2-7/6.2-8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 6.2-7 (timeout): _call_c_exec ran the C codec via subprocess.run with no timeout, so a hung or looping codec process would block indefinitely. Add a generous default timeout (_C_EXEC_TIMEOUT = 300s, overridable via NDI_COMPRESS_TIMEOUT) and turn TimeoutExpired into a clear RuntimeError. 6.2-8 (LICENSE): add LICENSE (CC BY-NC-SA 4.0) matching the NDI-compress-matlabp counterpart. FLAGGED (not done): the deeper 6.2-7 concern — codec provenance (P-code vs committed C binaries built at different times; can't rule out format drift) — needs the codec source vendored + built in CI or a pinned versioned build + checksum manifest, plus a cross-language round-trip fixture. That requires the build provenance and a paired MATLAB run; see docs/Audit_Remediation_Results. Authored without the codec C toolchain; the timeout path is covered by the existing error handling. needs the codec binaries to run end-to-end. --- LICENSE | 3 +++ docs/Audit_Remediation_Results_2026-06-12.md | 24 ++++++++++++++++++++ src/ndicompress/compress.py | 15 +++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 docs/Audit_Remediation_Results_2026-06-12.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..230ad95 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +NDI-compress-python is licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1). + +Contact info@walthamdatascience.com for commercial licensing. diff --git a/docs/Audit_Remediation_Results_2026-06-12.md b/docs/Audit_Remediation_Results_2026-06-12.md new file mode 100644 index 0000000..ae15632 --- /dev/null +++ b/docs/Audit_Remediation_Results_2026-06-12.md @@ -0,0 +1,24 @@ +# NDI-compress-python Audit Remediation — Results (2026-06-12) + +Branch `audit/ndi-compress-python-2026-06`, off `origin/main`. + +## Findings addressed (audit §6.2-7, §6.2-8) + +| # | Status | Summary | +|---|--------|---------| +| 6.2-7 (subprocess timeout) | **Done** | `_call_c_exec` ran the C codec via `subprocess.run(...)` with no timeout, so a hung/looping codec process would block indefinitely. Added a generous default timeout (`_C_EXEC_TIMEOUT`, 300 s, overridable via the `NDI_COMPRESS_TIMEOUT` env var) and convert `TimeoutExpired` into a clear `RuntimeError`. | +| 6.2-8 (LICENSE) | **Done** | Added `LICENSE` (CC BY-NC-SA 4.0) matching the NDI-compress-matlabp counterpart. | + +## FLAGGED — codec provenance + cross-language round-trip (not done here) + +The audit's core §6.2-7 concern is that **NDI-compress is unauditable on both +sides**: the MATLAB side ships P-code and the Python side ships committed C +binaries, built at different times, so binary/format drift between the two codecs +cannot be ruled out. Resolving that requires either (a) vendoring the codec C +**source** in-repo and building it in CI, or (b) pinning a single versioned codec +build and committing a checksum manifest of the binaries — plus a cross-language +round-trip fixture test (compress with one language's codec, decompress with the +other, assert byte-identity). That work needs the codec build provenance / source +(not available in this environment) and a paired MATLAB run, so it is flagged for +a focused follow-up. Only the subprocess-timeout hardening and the LICENSE are +done here. diff --git a/src/ndicompress/compress.py b/src/ndicompress/compress.py index 149f99b..4be5713 100644 --- a/src/ndicompress/compress.py +++ b/src/ndicompress/compress.py @@ -10,10 +10,23 @@ from .utility import get_executable_path +# Maximum seconds to wait for a codec subprocess before treating it as hung. +# Compression/decompression of very large arrays can be slow, so this default is +# generous; override via the NDI_COMPRESS_TIMEOUT environment variable. +_C_EXEC_TIMEOUT = float(os.environ.get("NDI_COMPRESS_TIMEOUT", "300")) + + def _call_c_exec(exec_name, args): exec_path = get_executable_path(exec_name) cmd = [exec_path] + args - result = subprocess.run(cmd, capture_output=True, text=True) + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=_C_EXEC_TIMEOUT + ) + except subprocess.TimeoutExpired as exc: + raise RuntimeError( + f"C executable {exec_name} timed out after {_C_EXEC_TIMEOUT:g}s" + ) from exc if result.returncode != 0: raise RuntimeError(f"C executable {exec_name} failed: {result.stderr}") return result.stdout From 97ee6755c1d958c72fc874e852f025ab1817e589 Mon Sep 17 00:00:00 2001 From: audristroyer Date: Fri, 12 Jun 2026 18:43:11 -0400 Subject: [PATCH 2/3] review: harden NDI_COMPRESS_TIMEOUT parse + document it Adversarial review found that a non-numeric NDI_COMPRESS_TIMEOUT crashed import (float() at module scope raised ValueError before any code ran). Parse it in a helper that falls back to the 300s default with a RuntimeWarning on a bad value, and document the env var in the README (it was added in code but not user-facing docs). --- README.md | 5 +++++ src/ndicompress/compress.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc30af0..313f44e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ This package requires the NDI compression C executables to be present. By default, it looks for them in `../../C/bin` relative to the `ndi_compress` package file. You can override this by setting the `NDI_BIN_PATH` environment variable. +Each codec executable runs in a subprocess with a timeout (default 300 seconds) +so a hung or looping codec process cannot block indefinitely. Override it with +the `NDI_COMPRESS_TIMEOUT` environment variable (in seconds); an invalid value +falls back to the default with a warning. + ```python import ndi_compress import numpy as np diff --git a/src/ndicompress/compress.py b/src/ndicompress/compress.py index 4be5713..90d90b4 100644 --- a/src/ndicompress/compress.py +++ b/src/ndicompress/compress.py @@ -13,7 +13,23 @@ # Maximum seconds to wait for a codec subprocess before treating it as hung. # Compression/decompression of very large arrays can be slow, so this default is # generous; override via the NDI_COMPRESS_TIMEOUT environment variable. -_C_EXEC_TIMEOUT = float(os.environ.get("NDI_COMPRESS_TIMEOUT", "300")) +def _read_timeout_env(default=300.0): + raw = os.environ.get("NDI_COMPRESS_TIMEOUT") + if raw is None or raw == "": + return default + try: + return float(raw) + except (TypeError, ValueError): + import warnings + + warnings.warn( + f"Invalid NDI_COMPRESS_TIMEOUT={raw!r}; using default {default:g}s.", + RuntimeWarning, + ) + return default + + +_C_EXEC_TIMEOUT = _read_timeout_env() def _call_c_exec(exec_name, args): From f1044750c2cd7b6e522a4c4c736e28ac16eb082c Mon Sep 17 00:00:00 2001 From: audristroyer Date: Fri, 12 Jun 2026 19:49:58 -0400 Subject: [PATCH 3/3] docs: add reviewer/next-agent context banner (part of the 9-PR 2026-06 ecosystem audit; lockstep + deferrals) --- docs/Audit_Remediation_Results_2026-06-12.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Audit_Remediation_Results_2026-06-12.md b/docs/Audit_Remediation_Results_2026-06-12.md index ae15632..d246bff 100644 --- a/docs/Audit_Remediation_Results_2026-06-12.md +++ b/docs/Audit_Remediation_Results_2026-06-12.md @@ -1,5 +1,10 @@ # NDI-compress-python Audit Remediation — Results (2026-06-12) +> **Context for a reviewer / next agent.** One of **9 coordinated PRs** in the 2026-06 NDI +> ecosystem audit; **none are merged.** This repo's PR: **Waltham-Data-Science/NDI-compress-python#2**. +> Done here: subprocess timeout + LICENSE. **Deferred:** codec provenance/checksums + a +> cross-language round-trip test (needs the codec source + a paired MATLAB run) — see below. + Branch `audit/ndi-compress-python-2026-06`, off `origin/main`. ## Findings addressed (audit §6.2-7, §6.2-8)