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'" },