From 2bc888ccc0707762197469f04598718df696f90b Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Thu, 18 Jun 2026 13:26:11 +0200 Subject: [PATCH] Add units to reachability timeout/memory-limit flags --reach-analysis-timeout now accepts an optional duration unit (s/m/h, e.g. 90s, 10m, 1h) and --reach-analysis-memory-limit an optional size unit (MB/GB, case-insensitive, e.g. 512MB, 8GB). Values are forwarded verbatim to the reachability engine (@coana-tech/cli), which owns parsing and validation, so the unit grammar and error messages live in a single source of truth. To pass values through unchanged, the four flags (incl. the hidden --reach-timeout/--reach-memory-limit aliases) drop type=int and accept raw strings; the dataclass fields become Optional[str]. A defensive str() is kept at the coana forward point so config-file JSON numbers (which bypass argparse's type converter via set_defaults) still reach subprocess as strings, and the guard uses `is not None` so an explicit empty string flows through and triggers coana's own error rather than being silently dropped. Bare numbers remain accepted for backward compatibility (seconds for the timeout, MB for the memory limit) but are no longer documented. Bumped the pinned @coana-tech/cli version to 15.5.0, which ships the unit parser. Docs (cli-reference) and CHANGELOG updated; unit tests cover unit-bearing values, bare-int back-compat, and int coercion. --- CHANGELOG.md | 13 +++++++++++ docs/cli-reference.md | 6 ++--- pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- socketsecurity/config.py | 16 +++++-------- socketsecurity/core/tools/reachability.py | 28 ++++++++++++++--------- tests/unit/test_config.py | 20 ++++++++++++---- tests/unit/test_reachability.py | 17 +++++++++++++- uv.lock | 2 +- 9 files changed, 73 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e686a..8ddfa73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 2.4.11 + +### Changed: units for `--reach-analysis-timeout` and `--reach-analysis-memory-limit` + +- `--reach-analysis-timeout` now accepts a duration with an optional unit suffix — `s`, `m` + or `h` (e.g. `90s`, `10m`, `1h`). `--reach-analysis-memory-limit` now accepts a size with an + optional unit suffix — `MB` or `GB`, case-insensitive (e.g. `512MB`, `8GB`). The value is + passed through verbatim to the reachability engine (`@coana-tech/cli`), which owns parsing + and validation, so error messages come from a single source of truth. +- Backward compatible: a bare number is still accepted (seconds for the timeout, MB for the + memory limit), exactly as before. This legacy form is no longer documented but keeps working. +- Bumped the pinned `@coana-tech/cli` version to `15.5.0`, which ships the unit parser. + ## 2.4.10 ### Added: opt directories back into manifest discovery via `--include-dirs` diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 7524495..d162d8c 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -241,9 +241,9 @@ If you don't want to provide the Socket API Token every time then you can use th | Parameter | Required | Default | Description | |:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------| | `--reach` | False | False | Enable reachability analysis to identify which vulnerable functions are actually called by your code. Creates a tier-1 full-application reachability scan (`scan_type=socket_tier1`). | -| `--reach-version` | False | 15.3.24 | Version of @coana-tech/cli to use. Defaults to the pinned version that ships with this CLI release, so the engine only changes when you upgrade the Socket CLI. Pass `latest` to always use the newest published version (opt-in auto-update), or an explicit version (e.g. `1.2.3`) to pin it. | -| `--reach-analysis-timeout` | False | 600 | Timeout in seconds for the reachability analysis. Omitted by default, so coana applies its own default. Alias: `--reach-timeout` | -| `--reach-analysis-memory-limit` | False | 8192 | Memory limit in MB for the reachability analysis. Omitted by default, so coana applies its own default. Alias: `--reach-memory-limit` | +| `--reach-version` | False | 15.5.0 | Version of @coana-tech/cli to use. Defaults to the pinned version that ships with this CLI release, so the engine only changes when you upgrade the Socket CLI. Pass `latest` to always use the newest published version (opt-in auto-update), or an explicit version (e.g. `1.2.3`) to pin it. | +| `--reach-analysis-timeout` | False | 10m | Timeout for each reachability analysis run, e.g. `90s`, `10m` or `1h`. Omitted by default, so coana applies its own default (`10m`). Alias: `--reach-timeout` | +| `--reach-analysis-memory-limit` | False | 8GB | Memory limit for each reachability analysis run, e.g. `512MB` or `8GB`. Omitted by default, so coana applies its own default (`8GB`). Alias: `--reach-memory-limit` | | `--reach-concurrency` | False | 1 | Control parallel analysis execution (must be >= 1). Omitted by default, so coana applies its own default. | | `--reach-additional-params` | False | | Pass custom parameters to the coana CLI tool | | `--reach-ecosystems` | False | | Comma-separated list of ecosystems to analyze (e.g., "npm,pypi"). If not specified, all supported ecosystems are analyzed | diff --git a/pyproject.toml b/pyproject.toml index 357f26e..508d0f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.4.10" +version = "2.4.11" requires-python = ">= 3.11" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index 4b86d9b..56da7cf 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.4.10' +__version__ = '2.4.11' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/socketsecurity/config.py b/socketsecurity/config.py index fdb7005..79495fc 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -168,8 +168,8 @@ class CliConfig: # Reachability Flags reach: bool = False reach_version: Optional[str] = None - reach_analysis_memory_limit: Optional[int] = None - reach_analysis_timeout: Optional[int] = None + reach_analysis_memory_limit: Optional[str] = None + reach_analysis_timeout: Optional[str] = None reach_disable_analytics: bool = False reach_disable_analysis_splitting: bool = False # Deprecated, kept for backwards compatibility reach_enable_analysis_splitting: bool = False @@ -988,29 +988,25 @@ def create_argument_parser() -> argparse.ArgumentParser: reachability_group.add_argument( "--reach-analysis-timeout", dest="reach_analysis_timeout", - type=int, - metavar="", - help="Timeout for reachability analysis in seconds" + metavar="", + help="Set the timeout for each reachability analysis run, e.g. 90s, 10m or 1h. (default: 10m)" ) # Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help. reachability_group.add_argument( "--reach-timeout", dest="reach_analysis_timeout", - type=int, help=argparse.SUPPRESS ) reachability_group.add_argument( "--reach-analysis-memory-limit", dest="reach_analysis_memory_limit", - type=int, - metavar="", - help="Memory limit for reachability analysis in MB (defaults to the coana CLI's own default, currently 8192)" + metavar="", + help="Set the memory limit for each reachability analysis run, e.g. 512MB or 8GB. (default: 8GB)" ) # Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help. reachability_group.add_argument( "--reach-memory-limit", dest="reach_analysis_memory_limit", - type=int, help=argparse.SUPPRESS ) reachability_group.add_argument( diff --git a/socketsecurity/core/tools/reachability.py b/socketsecurity/core/tools/reachability.py index 88d3105..c549a17 100644 --- a/socketsecurity/core/tools/reachability.py +++ b/socketsecurity/core/tools/reachability.py @@ -18,7 +18,7 @@ # Pinned @coana-tech/cli version. Bumped deliberately per Python CLI release so the # reachability engine version only changes through a standard pip upgrade (advance notice). # Pass --reach-version latest to opt into the newest published version instead. -DEFAULT_COANA_CLI_VERSION: Final = "15.3.24" +DEFAULT_COANA_CLI_VERSION: Final = "15.5.0" # Resolved @coana-tech/cli script paths from the npm-install fallback, keyed by version. # Lives for the process lifetime so repeated fallback invocations install only once @@ -55,7 +55,7 @@ def __init__(self, sdk: socketdev, api_token: str): def _resolve_coana_package_spec(self, version: Optional[str] = None) -> str: """ - Resolve the @coana-tech/cli package spec to run (e.g. '@coana-tech/cli@15.3.24'). + Resolve the @coana-tech/cli package spec to run (e.g. '@coana-tech/cli@15.5.0'). Args: version: Coana CLI version to use. @@ -64,7 +64,7 @@ def _resolve_coana_package_spec(self, version: Optional[str] = None) -> str: - '': that exact version. Returns: - str: The package specifier to use with npx (e.g. '@coana-tech/cli@15.3.24'). + str: The package specifier to use with npx (e.g. '@coana-tech/cli@15.5.0'). """ return f"@coana-tech/cli@{self._resolve_coana_version(version)}" @@ -79,8 +79,8 @@ def run_reachability_analysis( target_directory: str, tar_hash: Optional[str] = None, output_path: str = ".socket.facts.json", - timeout: Optional[int] = None, - memory_limit: Optional[int] = None, + timeout: Optional[str] = None, + memory_limit: Optional[str] = None, ecosystems: Optional[List[str]] = None, exclude_paths: Optional[List[str]] = None, min_severity: Optional[str] = None, @@ -112,8 +112,10 @@ def run_reachability_analysis( target_directory: Directory to analyze tar_hash: Tar hash from manifest upload or existing scan (optional) output_path: Output file path for results - timeout: Analysis timeout in seconds - memory_limit: Memory limit in MB + timeout: Analysis timeout, forwarded verbatim to coana --analysis-timeout + (coana parses the units, e.g. '90s', '10m', '1h'; a bare number is seconds) + memory_limit: Memory limit, forwarded verbatim to coana --memory-limit + (coana parses the units, e.g. '512MB', '8GB'; a bare number is MB) ecosystems: List of ecosystems to analyze (e.g., ['npm', 'pypi']) exclude_paths: Paths to exclude from analysis min_severity: Minimum severity level (info, low, moderate, high, critical) @@ -149,11 +151,15 @@ def run_reachability_analysis( "--disable-report-submission" ]) - # Add conditional arguments - if timeout: + # Add conditional arguments. timeout/memory_limit are forwarded verbatim; coana owns + # unit parsing/validation (e.g. '90s', '8GB'). We coerce to str only for subprocess + # safety — config-file values can arrive as ints via argparse set_defaults — and use + # `is not None` (not truthiness) so an explicit empty string still reaches coana and + # triggers coana's own error, rather than being silently dropped. + if timeout is not None: coana_args.extend(["--analysis-timeout", str(timeout)]) - - if memory_limit: + + if memory_limit is not None: coana_args.extend(["--memory-limit", str(memory_limit)]) if disable_analytics: diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 7403005..717e302 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -180,21 +180,31 @@ def test_reach_defaults_are_unset_and_delegated_to_coana(self): assert config.reach_analysis_timeout is None def test_reach_node_style_name_aliases(self): - """G8: Node-style primary names map to the same dests.""" + """G8: Node-style primary names map to the same dests. Values are kept as raw + strings and forwarded verbatim to coana (coana owns unit parsing).""" config = CliConfig.from_args( self.BASE_ARGS + ["--reach", "--reach-analysis-timeout", "300", "--reach-analysis-memory-limit", "2048"] ) - assert config.reach_analysis_timeout == 300 - assert config.reach_analysis_memory_limit == 2048 + assert config.reach_analysis_timeout == "300" + assert config.reach_analysis_memory_limit == "2048" def test_reach_legacy_name_aliases_still_work(self): """G8: pre-alignment names keep working (hidden aliases).""" config = CliConfig.from_args( self.BASE_ARGS + ["--reach", "--reach-timeout", "111", "--reach-memory-limit", "512"] ) - assert config.reach_analysis_timeout == 111 - assert config.reach_analysis_memory_limit == 512 + assert config.reach_analysis_timeout == "111" + assert config.reach_analysis_memory_limit == "512" + + def test_reach_unit_suffixes_are_passed_through_verbatim(self): + """Unit-bearing values (parsed/validated by coana) are stored as-is, not coerced.""" + config = CliConfig.from_args( + self.BASE_ARGS + + ["--reach", "--reach-analysis-timeout", "10m", "--reach-analysis-memory-limit", "8GB"] + ) + assert config.reach_analysis_timeout == "10m" + assert config.reach_analysis_memory_limit == "8GB" def test_reach_debug_flag(self): """G9: dedicated --reach-debug flag, independent of --enable-debug.""" diff --git a/tests/unit/test_reachability.py b/tests/unit/test_reachability.py index 1130be3..98933fe 100644 --- a/tests/unit/test_reachability.py +++ b/tests/unit/test_reachability.py @@ -87,11 +87,26 @@ def test_disable_external_tool_checks(analyzer, mocker): def test_concurrency_and_memory_args(analyzer, mocker): """G7: explicit concurrency/memory propagate as coana args.""" - cmd, _ = _run(analyzer, mocker, concurrency=1, memory_limit=8192) + cmd, _ = _run(analyzer, mocker, concurrency=1, memory_limit="8192") assert "--concurrency" in cmd and cmd[cmd.index("--concurrency") + 1] == "1" assert "--memory-limit" in cmd and cmd[cmd.index("--memory-limit") + 1] == "8192" +def test_timeout_and_memory_units_forwarded_verbatim(analyzer, mocker): + """Unit-bearing timeout/memory strings are forwarded to coana untouched (coana parses them).""" + cmd, _ = _run(analyzer, mocker, timeout="10m", memory_limit="8GB") + assert cmd[cmd.index("--analysis-timeout") + 1] == "10m" + assert cmd[cmd.index("--memory-limit") + 1] == "8GB" + + +def test_timeout_and_memory_int_values_coerced_to_str(analyzer, mocker): + """Config-file values can arrive as ints (set_defaults bypasses argparse type=); they must + still reach subprocess as strings, not raw ints.""" + cmd, _ = _run(analyzer, mocker, timeout=300, memory_limit=2048) + assert cmd[cmd.index("--analysis-timeout") + 1] == "300" + assert cmd[cmd.index("--memory-limit") + 1] == "2048" + + def test_env_identifies_python_cli(analyzer, mocker): """G5: SOCKET_CLI_VERSION + SOCKET_CALLER_USER_AGENT forwarded to coana.""" _, env = _run(analyzer, mocker) diff --git a/uv.lock b/uv.lock index b11778b..0d11821 100644 --- a/uv.lock +++ b/uv.lock @@ -1283,7 +1283,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.4.10" +version = "2.4.11" source = { editable = "." } dependencies = [ { name = "brotli", marker = "platform_python_implementation == 'CPython'" },