socketsecurity 2.4.6__tar.gz → 2.4.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/CHANGELOG.md +21 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/PKG-INFO +1 -1
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/docs/cli-reference.md +13 -8
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/pyproject.toml +1 -1
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/config.py +4 -2
- socketsecurity-2.4.7/socketsecurity/core/tools/reachability.py +469 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/socketcli.py +1 -1
- socketsecurity-2.4.7/tests/unit/test_reachability.py +384 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/uv.lock +1 -1
- socketsecurity-2.4.6/socketsecurity/core/tools/reachability.py +0 -330
- socketsecurity-2.4.6/tests/unit/test_reachability.py +0 -106
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/actions/setup-docker/action.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/actions/setup-hatch/action.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/actions/setup-sfw/action.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/dependabot.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/dependency-review.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/e2e-test.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/pr-preview.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/python-tests.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/release.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.github/zizmor.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.gitignore +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/.python-version +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/Dockerfile +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/LICENSE +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/Makefile +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/README.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/docs/ci-cd.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/docs/development.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/docs/troubleshooting.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-dashboard-parity.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-dashboard-parity.toml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-diff-ci-cd.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-diff-ci-cd.toml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-instance-detail.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/examples/config/sarif-instance-detail.toml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/instructions/gitlab-commit-status/uat.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/pytest.ini +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/build_container.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/scripts/run.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/session.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socket.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/alert_selection.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/fossa_compat.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/output.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/formatters/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/formatters/slack.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/conftest.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_diff_alerts.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_facts_compression.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_has_manifest_files.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_sdk_methods.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/fixtures/simple-npm/index.js +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/fixtures/simple-npm/package.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/validate-gitlab.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/validate-json.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/validate-reachability.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/validate-sarif.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/e2e/validate-scan.sh +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/fixtures/fossa/README.md +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_alert_selection.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_cli_config.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_dependency_overview.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_disable_ignore.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_exclude_paths.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_fossa_compat.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_fossa_parity.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_gitlab_commit_status.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_gitlab_format.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_slack_plugin.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_socketcli.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/tests/unit/test_tier1_finalize.py +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/workflows/buildkite.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/workflows/github-actions.yml +0 -0
- {socketsecurity-2.4.6 → socketsecurity-2.4.7}/workflows/gitlab-ci.yml +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.4.7
|
|
4
|
+
|
|
5
|
+
### Changed: pin @coana-tech/cli version; auto-update is now opt-in
|
|
6
|
+
|
|
7
|
+
- Reachability analysis now runs a fixed `@coana-tech/cli` version pinned to this CLI release
|
|
8
|
+
(`15.3.24`) via `npx`, instead of silently pulling the latest published version on every run.
|
|
9
|
+
Engine version changes now ride with the Socket Python CLI release (standard `pip` upgrade),
|
|
10
|
+
giving advance notice of analysis-engine changes.
|
|
11
|
+
- The CLI no longer runs `npm install -g @coana-tech/cli`; an existing global install is left
|
|
12
|
+
untouched (never auto-updated or downgraded).
|
|
13
|
+
- Opt into always-newest with `--reach-version latest`; pin an explicit version with
|
|
14
|
+
`--reach-version <semver>` (unchanged).
|
|
15
|
+
- Runs the engine via `npx --yes --force` (the same flags the Socket Node CLI passes for
|
|
16
|
+
coana); `--yes` skips npx's interactive install prompt so non-interactive/CI runs don't hang.
|
|
17
|
+
- Added an `npm install` + `node` fallback for when the `npx` launcher is missing or fails
|
|
18
|
+
before the engine starts. The installed engine is cached per version for the process
|
|
19
|
+
lifetime (installs once). Tunable via `SOCKET_CLI_COANA_FORCE_NPM_INSTALL` (use the fallback
|
|
20
|
+
as the primary path) and `SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK` (never fall back). `node` is
|
|
21
|
+
now part of the up-front prerequisite check. Also strips `npm_package_*` env vars before
|
|
22
|
+
spawning the engine to avoid `E2BIG` in large monorepos.
|
|
23
|
+
|
|
3
24
|
## 2.4.6
|
|
4
25
|
|
|
5
26
|
### Docs: reachability reference corrections
|
|
@@ -240,13 +240,13 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
240
240
|
| Parameter | Required | Default | Description |
|
|
241
241
|
|:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------|
|
|
242
242
|
| `--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`). |
|
|
243
|
-
| `--reach-version` | False |
|
|
244
|
-
| `--reach-analysis-timeout` | False |
|
|
245
|
-
| `--reach-analysis-memory-limit` | False |
|
|
246
|
-
| `--reach-concurrency` | False |
|
|
243
|
+
| `--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. |
|
|
244
|
+
| `--reach-analysis-timeout` | False | 600 | Timeout in seconds for the reachability analysis. Omitted by default, so coana applies its own default. Alias: `--reach-timeout` |
|
|
245
|
+
| `--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` |
|
|
246
|
+
| `--reach-concurrency` | False | 1 | Control parallel analysis execution (must be >= 1). Omitted by default, so coana applies its own default. |
|
|
247
247
|
| `--reach-additional-params` | False | | Pass custom parameters to the coana CLI tool |
|
|
248
248
|
| `--reach-ecosystems` | False | | Comma-separated list of ecosystems to analyze (e.g., "npm,pypi"). If not specified, all supported ecosystems are analyzed |
|
|
249
|
-
| `--reach-min-severity` | False |
|
|
249
|
+
| `--reach-min-severity` | False | info | Minimum severity of vulnerabilities to analyze (info, low, moderate, high, critical). Omitted by default, so coana analyzes all severities — equivalent to `info`, the lowest. |
|
|
250
250
|
| `--reach-skip-cache` | False | False | Skip cache and force fresh reachability analysis |
|
|
251
251
|
| `--reach-disable-analytics` | False | False | Disable analytics collection during reachability analysis |
|
|
252
252
|
| `--reach-enable-analysis-splitting` | False | False | Enable analysis splitting/bucketing (a legacy performance feature). Splitting is disabled by default. |
|
|
@@ -262,8 +262,9 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
262
262
|
**Reachability Analysis Requirements:**
|
|
263
263
|
|
|
264
264
|
The Python CLI verifies the following **up front** (before invoking the analysis engine) and exits with code **3** if any are unmet:
|
|
265
|
-
- `npm` - Required
|
|
266
|
-
- `npx` - Required to
|
|
265
|
+
- `npm` - Required (verified up front; ships alongside `npx`)
|
|
266
|
+
- `npx` - Required to fetch (on first use) and run `@coana-tech/cli` (the analysis engine)
|
|
267
|
+
- `node` - Required to run the engine (used directly by the `npm install` fallback)
|
|
267
268
|
- `uv` - Required by the analysis engine
|
|
268
269
|
- An **Enterprise** Socket organization plan (any `enterprise*` plan, including Enterprise trials)
|
|
269
270
|
|
|
@@ -313,7 +314,11 @@ Sample config files:
|
|
|
313
314
|
|
|
314
315
|
For CI-specific examples and guidance, see [`ci-cd.md`](ci-cd.md).
|
|
315
316
|
|
|
316
|
-
The CLI
|
|
317
|
+
The CLI runs a pinned `@coana-tech/cli` version via `npx --yes --force` (the same flags the Socket Node CLI passes for coana); it does **not** auto-update the engine or install it globally. `--yes` skips npx's interactive install prompt so non-interactive/CI runs don't hang. If the `npx` launcher is unavailable or fails before the engine starts, the CLI falls back to `npm install`-ing the pinned version into a temp directory and running it via `node`. Pass `--reach-version latest` to opt into the newest published version. Use `--reach` to enable reachability analysis during a full scan, or add `--only-facts-file` (with `--reach`) to submit only the reachability facts file (`.socket.facts.json`) when creating the full scan.
|
|
318
|
+
|
|
319
|
+
The launcher fallback can be tuned via environment variables:
|
|
320
|
+
- `SOCKET_CLI_COANA_FORCE_NPM_INSTALL` — skip `npx` entirely and always use the `npm install` + `node` path (useful where `npx` is known-broken).
|
|
321
|
+
- `SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK` — never fall back; surface the `npx` failure directly.
|
|
317
322
|
|
|
318
323
|
#### Advanced Configuration
|
|
319
324
|
| Parameter | Required | Default | Description |
|
|
@@ -943,8 +943,10 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
943
943
|
reachability_group.add_argument(
|
|
944
944
|
"--reach-version",
|
|
945
945
|
dest="reach_version",
|
|
946
|
-
metavar="<version>",
|
|
947
|
-
help="
|
|
946
|
+
metavar="<version|latest>",
|
|
947
|
+
help="Version of @coana-tech/cli to use. Defaults to the version pinned to this CLI "
|
|
948
|
+
"release; pass 'latest' to always use the newest published version (opt-in "
|
|
949
|
+
"auto-update), or an explicit version (e.g. '1.2.3') to pin it."
|
|
948
950
|
)
|
|
949
951
|
reachability_group.add_argument(
|
|
950
952
|
"--reach-analysis-timeout",
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
from socketdev import socketdev
|
|
2
|
+
from typing import List, Optional, Dict, Any, Final
|
|
3
|
+
import atexit
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import json
|
|
9
|
+
import pathlib
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
|
|
14
|
+
from socketsecurity import __version__
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Pinned @coana-tech/cli version. Bumped deliberately per Python CLI release so the
|
|
19
|
+
# reachability engine version only changes through a standard pip upgrade (advance notice).
|
|
20
|
+
# Pass --reach-version latest to opt into the newest published version instead.
|
|
21
|
+
DEFAULT_COANA_CLI_VERSION: Final = "15.3.24"
|
|
22
|
+
|
|
23
|
+
# Resolved @coana-tech/cli script paths from the npm-install fallback, keyed by version.
|
|
24
|
+
# Lives for the process lifetime so repeated fallback invocations install only once
|
|
25
|
+
# (mirrors the Node CLI's installedCoanaScriptPathsByVersion).
|
|
26
|
+
_INSTALLED_COANA_SCRIPT_PATHS: Dict[str, str] = {}
|
|
27
|
+
|
|
28
|
+
# Temp dirs created by the npm-install fallback, removed at process exit.
|
|
29
|
+
_COANA_INSTALL_DIRS: List[str] = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@atexit.register
|
|
33
|
+
def _cleanup_coana_install_dirs() -> None:
|
|
34
|
+
for install_dir in _COANA_INSTALL_DIRS:
|
|
35
|
+
shutil.rmtree(install_dir, ignore_errors=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_caller_user_agent() -> str:
|
|
39
|
+
"""Build the SOCKET_CALLER_USER_AGENT string forwarded to the coana CLI.
|
|
40
|
+
|
|
41
|
+
Mirrors the Node CLI's ``<product>/<version> <runtime>/<version> <platform>/<arch>``
|
|
42
|
+
shape so the backend can attribute reachability calls to the Python CLI.
|
|
43
|
+
"""
|
|
44
|
+
return (
|
|
45
|
+
f"socket/{__version__} "
|
|
46
|
+
f"python/{platform.python_version()} "
|
|
47
|
+
f"{platform.system().lower()}/{platform.machine().lower()}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ReachabilityAnalyzer:
|
|
52
|
+
def __init__(self, sdk: socketdev, api_token: str):
|
|
53
|
+
self.sdk = sdk
|
|
54
|
+
self.api_token = api_token
|
|
55
|
+
|
|
56
|
+
def _resolve_coana_package_spec(self, version: Optional[str] = None) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Resolve the @coana-tech/cli package spec to run (e.g. '@coana-tech/cli@15.3.24').
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
version: Coana CLI version to use.
|
|
62
|
+
- None: the pinned ``DEFAULT_COANA_CLI_VERSION`` (no auto-update).
|
|
63
|
+
- 'latest': always the newest published version (opt-in to auto-update).
|
|
64
|
+
- '<semver>': that exact version.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: The package specifier to use with npx (e.g. '@coana-tech/cli@15.3.24').
|
|
68
|
+
"""
|
|
69
|
+
return f"@coana-tech/cli@{self._resolve_coana_version(version)}"
|
|
70
|
+
|
|
71
|
+
def _resolve_coana_version(self, version: Optional[str] = None) -> str:
|
|
72
|
+
"""Resolve the effective @coana-tech/cli version string (see _resolve_coana_package_spec)."""
|
|
73
|
+
return (version or DEFAULT_COANA_CLI_VERSION).strip()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_reachability_analysis(
|
|
77
|
+
self,
|
|
78
|
+
org_slug: str,
|
|
79
|
+
target_directory: str,
|
|
80
|
+
tar_hash: Optional[str] = None,
|
|
81
|
+
output_path: str = ".socket.facts.json",
|
|
82
|
+
timeout: Optional[int] = None,
|
|
83
|
+
memory_limit: Optional[int] = None,
|
|
84
|
+
ecosystems: Optional[List[str]] = None,
|
|
85
|
+
exclude_paths: Optional[List[str]] = None,
|
|
86
|
+
min_severity: Optional[str] = None,
|
|
87
|
+
skip_cache: bool = False,
|
|
88
|
+
disable_analytics: bool = False,
|
|
89
|
+
enable_analysis_splitting: bool = False,
|
|
90
|
+
detailed_analysis_log_file: bool = False,
|
|
91
|
+
lazy_mode: bool = False,
|
|
92
|
+
repo_name: Optional[str] = None,
|
|
93
|
+
branch_name: Optional[str] = None,
|
|
94
|
+
version: Optional[str] = None,
|
|
95
|
+
concurrency: Optional[int] = None,
|
|
96
|
+
additional_params: Optional[List[str]] = None,
|
|
97
|
+
allow_unverified: bool = False,
|
|
98
|
+
enable_debug: bool = False,
|
|
99
|
+
use_only_pregenerated_sboms: bool = False,
|
|
100
|
+
continue_on_analysis_errors: bool = False,
|
|
101
|
+
continue_on_install_errors: bool = False,
|
|
102
|
+
continue_on_missing_lock_files: bool = False,
|
|
103
|
+
continue_on_no_source_files: bool = False,
|
|
104
|
+
reach_debug: bool = False,
|
|
105
|
+
disable_external_tool_checks: bool = False,
|
|
106
|
+
) -> Dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Run reachability analysis.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
org_slug: Socket organization slug
|
|
112
|
+
target_directory: Directory to analyze
|
|
113
|
+
tar_hash: Tar hash from manifest upload or existing scan (optional)
|
|
114
|
+
output_path: Output file path for results
|
|
115
|
+
timeout: Analysis timeout in seconds
|
|
116
|
+
memory_limit: Memory limit in MB
|
|
117
|
+
ecosystems: List of ecosystems to analyze (e.g., ['npm', 'pypi'])
|
|
118
|
+
exclude_paths: Paths to exclude from analysis
|
|
119
|
+
min_severity: Minimum severity level (info, low, moderate, high, critical)
|
|
120
|
+
skip_cache: Skip cache usage
|
|
121
|
+
disable_analytics: Disable analytics sharing
|
|
122
|
+
enable_analysis_splitting: Enable analysis splitting (disabled by default)
|
|
123
|
+
detailed_analysis_log_file: Print detailed analysis log file path
|
|
124
|
+
lazy_mode: Enable lazy mode for analysis
|
|
125
|
+
repo_name: Repository name
|
|
126
|
+
branch_name: Branch name
|
|
127
|
+
version: @coana-tech/cli version to use. None uses the pinned
|
|
128
|
+
DEFAULT_COANA_CLI_VERSION (no auto-update); 'latest' opts into the newest
|
|
129
|
+
published version; '<semver>' pins an explicit version.
|
|
130
|
+
concurrency: Concurrency level for analysis (must be >= 1)
|
|
131
|
+
additional_params: Additional parameters to pass to coana CLI
|
|
132
|
+
allow_unverified: Disable SSL certificate verification (sets NODE_TLS_REJECT_UNAUTHORIZED=0)
|
|
133
|
+
enable_debug: Enable debug mode (passes -d flag to coana CLI)
|
|
134
|
+
use_only_pregenerated_sboms: Use only pre-generated CDX and SPDX files for the scan
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dict containing scan_id and report_path
|
|
138
|
+
"""
|
|
139
|
+
# Build the coana CLI arguments (everything after the package spec). The launcher
|
|
140
|
+
# (npx, or the npm-install + node fallback) is chosen in _spawn_coana() below.
|
|
141
|
+
coana_args = ["run", "."]
|
|
142
|
+
|
|
143
|
+
# Add required arguments
|
|
144
|
+
output_dir = str(pathlib.Path(output_path).parent)
|
|
145
|
+
log.debug(f"output_dir: {output_dir}, output_path: {output_path}")
|
|
146
|
+
coana_args.extend([
|
|
147
|
+
"--output-dir", output_dir,
|
|
148
|
+
"--socket-mode", output_path,
|
|
149
|
+
"--disable-report-submission"
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
# Add conditional arguments
|
|
153
|
+
if timeout:
|
|
154
|
+
coana_args.extend(["--analysis-timeout", str(timeout)])
|
|
155
|
+
|
|
156
|
+
if memory_limit:
|
|
157
|
+
coana_args.extend(["--memory-limit", str(memory_limit)])
|
|
158
|
+
|
|
159
|
+
if disable_analytics:
|
|
160
|
+
coana_args.append("--disable-analytics-sharing")
|
|
161
|
+
|
|
162
|
+
# Analysis splitting is disabled by default; only omit the flag if explicitly enabled
|
|
163
|
+
if not enable_analysis_splitting:
|
|
164
|
+
coana_args.append("--disable-analysis-splitting")
|
|
165
|
+
|
|
166
|
+
if detailed_analysis_log_file:
|
|
167
|
+
coana_args.append("--print-analysis-log-file")
|
|
168
|
+
|
|
169
|
+
if lazy_mode:
|
|
170
|
+
coana_args.append("--lazy-mode")
|
|
171
|
+
|
|
172
|
+
# KEY POINT: Only add manifest tar hash if we have one
|
|
173
|
+
if tar_hash:
|
|
174
|
+
coana_args.extend(["--run-without-docker", "--manifests-tar-hash", tar_hash])
|
|
175
|
+
|
|
176
|
+
if ecosystems:
|
|
177
|
+
coana_args.extend(["--purl-types"] + ecosystems)
|
|
178
|
+
|
|
179
|
+
if exclude_paths:
|
|
180
|
+
coana_args.extend(["--exclude-dirs"] + exclude_paths)
|
|
181
|
+
|
|
182
|
+
if min_severity:
|
|
183
|
+
coana_args.extend(["--min-severity", min_severity])
|
|
184
|
+
|
|
185
|
+
if skip_cache:
|
|
186
|
+
coana_args.append("--skip-cache-usage")
|
|
187
|
+
|
|
188
|
+
if concurrency:
|
|
189
|
+
coana_args.extend(["--concurrency", str(concurrency)])
|
|
190
|
+
|
|
191
|
+
if enable_debug:
|
|
192
|
+
coana_args.append("-d")
|
|
193
|
+
|
|
194
|
+
if reach_debug:
|
|
195
|
+
coana_args.append("--debug")
|
|
196
|
+
|
|
197
|
+
if disable_external_tool_checks:
|
|
198
|
+
coana_args.append("--disable-external-tool-checks")
|
|
199
|
+
|
|
200
|
+
if use_only_pregenerated_sboms:
|
|
201
|
+
coana_args.append("--use-only-pregenerated-sboms")
|
|
202
|
+
|
|
203
|
+
if continue_on_analysis_errors:
|
|
204
|
+
coana_args.append("--reach-continue-on-analysis-errors")
|
|
205
|
+
|
|
206
|
+
if continue_on_install_errors:
|
|
207
|
+
coana_args.append("--reach-continue-on-install-errors")
|
|
208
|
+
|
|
209
|
+
if continue_on_missing_lock_files:
|
|
210
|
+
coana_args.append("--reach-continue-on-missing-lock-files")
|
|
211
|
+
|
|
212
|
+
if continue_on_no_source_files:
|
|
213
|
+
coana_args.append("--reach-continue-on-no-source-files")
|
|
214
|
+
|
|
215
|
+
# Add any additional parameters provided by the user
|
|
216
|
+
if additional_params:
|
|
217
|
+
coana_args.extend(additional_params)
|
|
218
|
+
|
|
219
|
+
# Set up environment variables
|
|
220
|
+
env = os.environ.copy()
|
|
221
|
+
|
|
222
|
+
# Required environment variables for Coana CLI
|
|
223
|
+
env["SOCKET_ORG_SLUG"] = org_slug
|
|
224
|
+
env["SOCKET_CLI_API_TOKEN"] = self.api_token
|
|
225
|
+
|
|
226
|
+
# Identify the calling CLI to the coana tool / backend (parity with the Node CLI).
|
|
227
|
+
env["SOCKET_CLI_VERSION"] = __version__
|
|
228
|
+
env["SOCKET_CALLER_USER_AGENT"] = _build_caller_user_agent()
|
|
229
|
+
|
|
230
|
+
# NOTE: no proxy env is set here. coana already reads HTTPS_PROXY/HTTP_PROXY itself, and
|
|
231
|
+
# we pass the full parent env above, so it inherits them. A SOCKET_CLI_API_PROXY override
|
|
232
|
+
# should only be set from an explicit --proxy flag (not yet implemented), since seeding it
|
|
233
|
+
# from HTTPS_PROXY would be a no-op (it's the same value coana already resolves).
|
|
234
|
+
|
|
235
|
+
# Optional environment variables.
|
|
236
|
+
# NOTE: repo/branch are intentionally omitted by the caller (passed as None) when they
|
|
237
|
+
# are the default sentinels, to avoid polluting coana's per-repo/branch cache buckets.
|
|
238
|
+
if repo_name:
|
|
239
|
+
env["SOCKET_REPO_NAME"] = repo_name
|
|
240
|
+
|
|
241
|
+
if branch_name:
|
|
242
|
+
env["SOCKET_BRANCH_NAME"] = branch_name
|
|
243
|
+
|
|
244
|
+
# Set NODE_TLS_REJECT_UNAUTHORIZED=0 if allow_unverified is True
|
|
245
|
+
if allow_unverified:
|
|
246
|
+
env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"
|
|
247
|
+
|
|
248
|
+
# Execute coana
|
|
249
|
+
log.info("Running reachability analysis...")
|
|
250
|
+
log.debug(f"Environment: SOCKET_ORG_SLUG={org_slug}, SOCKET_REPO_NAME={repo_name or 'not set'}, SOCKET_BRANCH_NAME={branch_name or 'not set'}")
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
# Prefer npx (with caching disabled); fall back to `npm install` + `node`
|
|
254
|
+
# if the npx launcher fails before coana starts (parity with the Node CLI).
|
|
255
|
+
returncode = self._spawn_coana(coana_args, version, env, target_directory)
|
|
256
|
+
|
|
257
|
+
if returncode != 0:
|
|
258
|
+
log.error(f"Reachability analysis failed with exit code {returncode}")
|
|
259
|
+
raise Exception(f"Reachability analysis failed with exit code {returncode}")
|
|
260
|
+
|
|
261
|
+
# Extract scan ID from output file
|
|
262
|
+
scan_id = self._extract_scan_id(output_path)
|
|
263
|
+
|
|
264
|
+
log.info(f"Reachability analysis completed successfully")
|
|
265
|
+
if scan_id:
|
|
266
|
+
log.info(f"Scan ID: {scan_id}")
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
"scan_id": scan_id,
|
|
270
|
+
"report_path": output_path,
|
|
271
|
+
"tar_hash_used": tar_hash
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
log.error(f"Failed to run reachability analysis: {str(e)}")
|
|
276
|
+
raise Exception(f"Failed to run reachability analysis: {str(e)}")
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _sanitize_coana_env(env: Dict[str, str]) -> Dict[str, str]:
|
|
280
|
+
"""Drop npm-injected ``npm_package_*`` vars before spawning coana.
|
|
281
|
+
|
|
282
|
+
npm/pnpm/yarn populate one env var per leaf of the cwd's package.json
|
|
283
|
+
(``npm_package_dependencies_*`` etc.). In large monorepos this can be tens of KB
|
|
284
|
+
and push argv+env past the OS ARG_MAX, making the spawn fail with E2BIG before
|
|
285
|
+
coana even starts. coana doesn't read these, so dropping them is safe; we keep
|
|
286
|
+
``npm_config_*`` (registry/cache/proxy) untouched. Mirrors the Node CLI.
|
|
287
|
+
"""
|
|
288
|
+
return {k: v for k, v in env.items() if not k.startswith("npm_package_")}
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _npx_launcher_failed_before_coana(returncode: int) -> bool:
|
|
292
|
+
"""Heuristic: did npx fail *before* coana started (so retrying is worthwhile)?
|
|
293
|
+
|
|
294
|
+
We stream coana's output (no capture), so we classify by exit code alone, like the
|
|
295
|
+
Node CLI does with inherited stdio: signal kills (negative codes) and codes >= 128
|
|
296
|
+
are conventionally launcher/signal failures -> retry. Small positive codes (1..127)
|
|
297
|
+
are ambiguous (coana's own exit codes are small ints), so we do NOT retry.
|
|
298
|
+
"""
|
|
299
|
+
return returncode < 0 or returncode >= 128
|
|
300
|
+
|
|
301
|
+
def _spawn_coana(
|
|
302
|
+
self,
|
|
303
|
+
coana_args: List[str],
|
|
304
|
+
version: Optional[str],
|
|
305
|
+
env: Dict[str, str],
|
|
306
|
+
cwd: str,
|
|
307
|
+
) -> int:
|
|
308
|
+
"""Run coana for the given args, returning the process exit code.
|
|
309
|
+
|
|
310
|
+
We run a pinned, versioned spec via npx and intentionally do NOT ``npm install -g``:
|
|
311
|
+
that would silently auto-update the engine on every run and mutate the user's global
|
|
312
|
+
install. The pinned version rides with the Python CLI release instead (see
|
|
313
|
+
``DEFAULT_COANA_CLI_VERSION``).
|
|
314
|
+
|
|
315
|
+
Primary path: ``npx --yes --force @coana-tech/cli@<version> ...`` — the exact flags the
|
|
316
|
+
Socket Node CLI passes for coana (``--yes`` skips npx's interactive install prompt so
|
|
317
|
+
CI runs don't hang).
|
|
318
|
+
|
|
319
|
+
Fallback path: if npx is missing, or its launcher dies before coana starts, install
|
|
320
|
+
@coana-tech/cli into a temp dir via ``npm install`` and run it directly via ``node``.
|
|
321
|
+
Toggle with ``SOCKET_CLI_COANA_FORCE_NPM_INSTALL`` (use the fallback as the primary
|
|
322
|
+
path) and ``SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK`` (never fall back).
|
|
323
|
+
"""
|
|
324
|
+
effective_version = self._resolve_coana_version(version)
|
|
325
|
+
coana_env = self._sanitize_coana_env(env)
|
|
326
|
+
disable_fallback = bool(os.environ.get("SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK"))
|
|
327
|
+
|
|
328
|
+
if os.environ.get("SOCKET_CLI_COANA_FORCE_NPM_INSTALL"):
|
|
329
|
+
return self._spawn_coana_via_npm_install(coana_args, effective_version, coana_env, cwd)
|
|
330
|
+
|
|
331
|
+
package_spec = f"@coana-tech/cli@{effective_version}"
|
|
332
|
+
npx_cmd = ["npx", "--yes", "--force", package_spec, *coana_args]
|
|
333
|
+
log.debug(f"Reachability command: {' '.join(npx_cmd)}")
|
|
334
|
+
try:
|
|
335
|
+
result = subprocess.run(
|
|
336
|
+
npx_cmd,
|
|
337
|
+
env=coana_env,
|
|
338
|
+
cwd=cwd,
|
|
339
|
+
stdout=sys.stderr, # Send stdout to stderr so the user sees it
|
|
340
|
+
stderr=sys.stderr,
|
|
341
|
+
)
|
|
342
|
+
except FileNotFoundError:
|
|
343
|
+
# npx is not on PATH: the launcher provably never started coana.
|
|
344
|
+
if disable_fallback:
|
|
345
|
+
raise
|
|
346
|
+
log.warning("npx not found on PATH; retrying reachability analysis via `npm install` + `node`.")
|
|
347
|
+
return self._spawn_coana_via_npm_install(coana_args, effective_version, coana_env, cwd)
|
|
348
|
+
|
|
349
|
+
if result.returncode == 0:
|
|
350
|
+
return 0
|
|
351
|
+
|
|
352
|
+
if not disable_fallback and self._npx_launcher_failed_before_coana(result.returncode):
|
|
353
|
+
log.warning(
|
|
354
|
+
f"npx launcher failed (exit {result.returncode}) before coana started; "
|
|
355
|
+
"retrying reachability analysis via `npm install` + `node`."
|
|
356
|
+
)
|
|
357
|
+
return self._spawn_coana_via_npm_install(coana_args, effective_version, coana_env, cwd)
|
|
358
|
+
|
|
359
|
+
return result.returncode
|
|
360
|
+
|
|
361
|
+
def _spawn_coana_via_npm_install(
|
|
362
|
+
self,
|
|
363
|
+
coana_args: List[str],
|
|
364
|
+
version: str,
|
|
365
|
+
env: Dict[str, str],
|
|
366
|
+
cwd: str,
|
|
367
|
+
) -> int:
|
|
368
|
+
"""Fallback launcher: ``npm install`` @coana-tech/cli into a temp dir, run via ``node``.
|
|
369
|
+
|
|
370
|
+
Used when npx is unavailable or its launcher fails before coana boots. Mirrors the
|
|
371
|
+
Node CLI's npm-install fallback. Returns coana's exit code; raises if the install
|
|
372
|
+
itself fails or if ``node`` is unavailable.
|
|
373
|
+
"""
|
|
374
|
+
script_path = self._install_coana_to_tmpdir(version, env)
|
|
375
|
+
node_cmd = self._build_coana_node_cmd(script_path, coana_args)
|
|
376
|
+
log.debug(f"Reachability fallback command: {' '.join(node_cmd)}")
|
|
377
|
+
try:
|
|
378
|
+
result = subprocess.run(node_cmd, env=env, cwd=cwd, stdout=sys.stderr, stderr=sys.stderr)
|
|
379
|
+
except FileNotFoundError as e:
|
|
380
|
+
# The fallback exists for broken-launcher environments, but it still needs node.
|
|
381
|
+
raise Exception(
|
|
382
|
+
"`node` was not found on PATH; it is required to run the reachability engine "
|
|
383
|
+
"via the npm-install fallback."
|
|
384
|
+
) from e
|
|
385
|
+
return result.returncode
|
|
386
|
+
|
|
387
|
+
def _install_coana_to_tmpdir(self, version: str, env: Dict[str, str]) -> str:
|
|
388
|
+
"""``npm install`` @coana-tech/cli@<version> into a temp dir; return its executable JS path.
|
|
389
|
+
|
|
390
|
+
Caches the resolved path per version for the process lifetime so repeated fallback
|
|
391
|
+
invocations install only once (mirrors the Node CLI's installCoanaToTmpdir). Raises if
|
|
392
|
+
the install fails.
|
|
393
|
+
"""
|
|
394
|
+
cached = _INSTALLED_COANA_SCRIPT_PATHS.get(version)
|
|
395
|
+
if cached and os.path.exists(cached):
|
|
396
|
+
return cached
|
|
397
|
+
|
|
398
|
+
install_dir = tempfile.mkdtemp(prefix="socket-coana-")
|
|
399
|
+
_COANA_INSTALL_DIRS.append(install_dir)
|
|
400
|
+
npm_cmd = [
|
|
401
|
+
"npm", "install",
|
|
402
|
+
"--no-save", "--no-package-lock", "--no-audit", "--no-fund",
|
|
403
|
+
"--prefix", install_dir,
|
|
404
|
+
f"@coana-tech/cli@{version}",
|
|
405
|
+
]
|
|
406
|
+
log.info("Installing reachability analysis engine via npm fallback...")
|
|
407
|
+
log.debug(f"npm install fallback command: {' '.join(npm_cmd)}")
|
|
408
|
+
install = subprocess.run(npm_cmd, env=env, stdout=sys.stderr, stderr=sys.stderr)
|
|
409
|
+
if install.returncode != 0:
|
|
410
|
+
raise Exception(
|
|
411
|
+
f"npm install fallback for @coana-tech/cli@{version} failed with exit code {install.returncode}"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
script_path = self._resolve_coana_bin(install_dir)
|
|
415
|
+
_INSTALLED_COANA_SCRIPT_PATHS[version] = script_path
|
|
416
|
+
return script_path
|
|
417
|
+
|
|
418
|
+
@staticmethod
|
|
419
|
+
def _resolve_coana_bin(install_dir: str) -> str:
|
|
420
|
+
"""Resolve @coana-tech/cli's executable JS from its installed package.json ``bin`` field."""
|
|
421
|
+
package_json_path = os.path.join(
|
|
422
|
+
install_dir, "node_modules", "@coana-tech", "cli", "package.json"
|
|
423
|
+
)
|
|
424
|
+
with open(package_json_path, "r") as f:
|
|
425
|
+
pkg = json.load(f)
|
|
426
|
+
bin_field = pkg.get("bin")
|
|
427
|
+
relative_bin = None
|
|
428
|
+
if isinstance(bin_field, str):
|
|
429
|
+
relative_bin = bin_field
|
|
430
|
+
elif isinstance(bin_field, dict):
|
|
431
|
+
# Prefer an entry named "coana"; otherwise take the first.
|
|
432
|
+
relative_bin = bin_field.get("coana") or next(iter(bin_field.values()), None)
|
|
433
|
+
if not relative_bin:
|
|
434
|
+
raise Exception(
|
|
435
|
+
f"@coana-tech/cli package.json at {package_json_path} is missing a usable bin entry"
|
|
436
|
+
)
|
|
437
|
+
return os.path.abspath(os.path.join(os.path.dirname(package_json_path), relative_bin))
|
|
438
|
+
|
|
439
|
+
@staticmethod
|
|
440
|
+
def _build_coana_node_cmd(script_path: str, coana_args: List[str]) -> List[str]:
|
|
441
|
+
"""Run a .js/.mjs entry via ``node``; invoke a native binary directly (Node CLI parity)."""
|
|
442
|
+
if script_path.endswith(".js") or script_path.endswith(".mjs"):
|
|
443
|
+
return ["node", script_path, *coana_args]
|
|
444
|
+
return [script_path, *coana_args]
|
|
445
|
+
|
|
446
|
+
def _extract_scan_id(self, facts_file_path: str) -> Optional[str]:
|
|
447
|
+
"""
|
|
448
|
+
Extract tier1ReachabilityScanId from the socket facts JSON file.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
facts_file_path: Path to the .socket.facts.json file
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Optional[str]: The scan ID if found, None otherwise
|
|
455
|
+
"""
|
|
456
|
+
try:
|
|
457
|
+
if not os.path.exists(facts_file_path):
|
|
458
|
+
log.warning(f"Facts file not found: {facts_file_path}")
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
with open(facts_file_path, 'r') as f:
|
|
462
|
+
facts = json.load(f)
|
|
463
|
+
|
|
464
|
+
scan_id = facts.get('tier1ReachabilityScanId')
|
|
465
|
+
return scan_id.strip() if scan_id else None
|
|
466
|
+
|
|
467
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
468
|
+
log.warning(f"Failed to extract scan ID from {facts_file_path}: {e}")
|
|
469
|
+
return None
|
|
@@ -189,7 +189,7 @@ def main_code():
|
|
|
189
189
|
# Check for required dependencies if reachability analysis is enabled
|
|
190
190
|
if config.reach:
|
|
191
191
|
log.info("Reachability analysis enabled, checking for required dependencies...")
|
|
192
|
-
required_deps = ["npm", "uv", "npx"]
|
|
192
|
+
required_deps = ["npm", "node", "uv", "npx"]
|
|
193
193
|
missing_deps = []
|
|
194
194
|
found_deps = []
|
|
195
195
|
|