Skip to content

Commit 50f0f22

Browse files
committed
Merge remote-tracking branch 'origin/main' into martin/env-variable-cleanup
# Conflicts: # CHANGELOG.md # pyproject.toml # socketsecurity/__init__.py # uv.lock
2 parents 01afb8b + 6e3996d commit 50f0f22

18 files changed

Lines changed: 1672 additions & 700 deletions

CHANGELOG.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 2.4.9
3+
## 2.4.12
44

55
### Changed: consolidated coana launcher env vars into `SOCKET_CLI_COANA_LAUNCHER`
66

@@ -13,6 +13,42 @@
1313
variables remain supported for back-compat when `SOCKET_CLI_COANA_LAUNCHER` is unset, but
1414
are deprecated and no longer documented.
1515

16+
## 2.4.11
17+
18+
### Changed: units for `--reach-analysis-timeout` and `--reach-analysis-memory-limit`
19+
20+
- `--reach-analysis-timeout` now accepts a duration with an optional unit suffix — `s`, `m`
21+
or `h` (e.g. `90s`, `10m`, `1h`). `--reach-analysis-memory-limit` now accepts a size with an
22+
optional unit suffix — `MB` or `GB`, case-insensitive (e.g. `512MB`, `8GB`). The value is
23+
passed through verbatim to the reachability engine (`@coana-tech/cli`), which owns parsing
24+
and validation, so error messages come from a single source of truth.
25+
- Backward compatible: a bare number is still accepted (seconds for the timeout, MB for the
26+
memory limit), exactly as before. This legacy form is no longer documented but keeps working.
27+
- Bumped the pinned `@coana-tech/cli` version to `15.5.0`, which ships the unit parser.
28+
29+
## 2.4.10
30+
31+
### Added: opt directories back into manifest discovery via `--include-dirs`
32+
33+
- New `--include-dirs` flag (comma-separated directory names) that re-includes directories
34+
the CLI excludes from manifest discovery by default. The default exclude list
35+
(`node_modules`, `bower_components`, `jspm_packages`, `__pycache__`, `.venv`, `venv`,
36+
`build`, `dist`, `.tox`, `.mypy_cache`, `.pytest_cache`, `*.egg-info`, `vendor`) is a sane
37+
default, but some projects keep manifest files under those names — e.g. `build/requirements.txt`.
38+
Pass `--include-dirs build,dist` to scan them. Names are matched against any path segment,
39+
mirroring how the default exclude list is applied.
40+
- `--include-module-folders` now functions as documented: it re-includes the JS/TS module
41+
folders (`node_modules`, `bower_components`, `jspm_packages`) as a group. Previously the
42+
flag was accepted but had no effect.
43+
44+
## 2.4.9
45+
46+
### Added: opt-in streaming log channel via `--upload-logs`
47+
48+
- New `--upload-logs` flag (default off). When set, each CLI invocation registers a run, reports a per-run status (`in_progress` / `success` / `failure` / `cancelled`), and uploads a transcript of its own log output to the Socket backend for that run, visible in the Socket admin views. The transcript is captured regardless of the local `--enable-debug` state; the existing terminal verbosity is unchanged.
49+
- New `--no-upload-logs` flag (mutually exclusive with `--upload-logs`) explicitly opts the run out of uploading logs, even when an org-level override would otherwise enable it. Use this when you need a guaranteed no-upload guarantee (e.g. legal/consent reasons).
50+
- The Socket backend can also force-enable streaming for specific orgs in the absence of an explicit opt-out. The feature is best-effort — registration or upload failures silently degrade and never block the scan.
51+
1652
## 2.4.8
1753

1854
### Fixed: retry transient full-scan upload failures

docs/cli-reference.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--workspace WORKSPACE] [--
148148
[--owner OWNER] [--pr-number PR_NUMBER] [--commit-message COMMIT_MESSAGE] [--commit-sha COMMIT_SHA] [--committers [COMMITTERS ...]]
149149
[--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--license-file-name LICENSE_FILE_NAME] [--save-submitted-files-list SAVE_SUBMITTED_FILES_LIST]
150150
[--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME]
151-
[--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--exclude-paths EXCLUDE_PATHS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
151+
[--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--exclude-paths EXCLUDE_PATHS] [--include-dirs INCLUDE_DIRS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
152152
[--enable-json] [--enable-sarif] [--sarif-file <path>] [--sarif-scope {diff,full}] [--sarif-grouping {instance,alert}] [--sarif-reachability {all,reachable,potentially,reachable-or-potentially}] [--enable-gitlab-security] [--gitlab-security-file <path>]
153153
[--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
154154
[--ignore-commit-files] [--disable-blocking] [--disable-ignore] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
@@ -205,13 +205,14 @@ If you don't want to provide the Socket API Token every time then you can use th
205205
| `--workspace-name` | False | | Workspace name suffix to append to repository name (repo-name-workspace_name). Must be used with `--sub-path` |
206206
| `--excluded-ecosystems` | False | [] | List of ecosystems to exclude from analysis (JSON array string). You can get supported files from the [Supported Files API](https://docs.socket.dev/reference/getsupportedfiles) |
207207
| `--exclude-paths` | False | | Comma-separated paths/globs to exclude from **both** manifest discovery (every scan) **and** reachability analysis (e.g. `tests/**,packages/legacy,*.spec.ts`). Patterns are scan-root-relative, case-sensitive globs where `*` does not cross `/` and `**` does. Supersedes `--reach-exclude-paths`. |
208+
| `--include-dirs` | False | | Comma-separated directory **names** that are excluded from manifest discovery by default but should be scanned (e.g. `build,dist`). Names are matched against any path segment, mirroring the default exclude list (`node_modules`, `bower_components`, `jspm_packages`, `__pycache__`, `.venv`, `venv`, `build`, `dist`, `.tox`, `.mypy_cache`, `.pytest_cache`, `*.egg-info`, `vendor`). Use this when manifest files live under a normally-ignored folder, e.g. `build/requirements.txt`. |
208209

209210
#### Branch and Scan Configuration
210211
| Parameter | Required | Default | Description |
211212
|:-------------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------|
212213
| `--default-branch` | False | *auto* | Make this branch the default branch (auto-detected from git and CI environment when not specified) |
213214
| `--pending-head` | False | *auto* | If true, the new scan will be set as the branch's head scan (automatically synced with default-branch) |
214-
| `--include-module-folders` | False | False | If enabled will include manifest files from folders like node_modules |
215+
| `--include-module-folders` | False | False | If enabled, re-includes the JS/TS module folders (`node_modules`, `bower_components`, `jspm_packages`) in manifest discovery. For other excluded directories, use `--include-dirs`. |
215216
216217
#### Output Configuration
217218
| Parameter | Required | Default | Description |
@@ -240,9 +241,9 @@ If you don't want to provide the Socket API Token every time then you can use th
240241
| Parameter | Required | Default | Description |
241242
|:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------|
242243
| `--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 | 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` |
244+
| `--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. |
245+
| `--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` |
246+
| `--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` |
246247
| `--reach-concurrency` | False | 1 | Control parallel analysis execution (must be >= 1). Omitted by default, so coana applies its own default. |
247248
| `--reach-additional-params` | False | | Pass custom parameters to the coana CLI tool |
248249
| `--reach-ecosystems` | False | | Comma-separated list of ecosystems to analyze (e.g., "npm,pypi"). If not specified, all supported ecosystems are analyzed |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.4.9"
9+
version = "2.4.12"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.4.9'
2+
__version__ = '2.4.12'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/config.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ class CliConfig:
139139
ignore_commit_files: bool = False
140140
disable_blocking: bool = False
141141
disable_ignore: bool = False
142+
# Tri-state log-upload preference: True = --upload-logs, False = --no-upload-logs,
143+
# None = neither (server-side override decides).
144+
upload_logs: Optional[bool] = None
142145
strict_blocking: bool = False
143146
integration_type: IntegrationType = "api"
144147
integration_org_slug: Optional[str] = None
@@ -151,6 +154,7 @@ class CliConfig:
151154
repo_is_public: bool = False
152155
excluded_ecosystems: list[str] = field(default_factory=lambda: [])
153156
exclude_paths: Optional[List[str]] = None
157+
included_dirs: List[str] = field(default_factory=lambda: [])
154158
version: str = __version__
155159
jira_plugin: PluginConfig = field(default_factory=PluginConfig)
156160
slack_plugin: PluginConfig = field(default_factory=PluginConfig)
@@ -164,8 +168,8 @@ class CliConfig:
164168
# Reachability Flags
165169
reach: bool = False
166170
reach_version: Optional[str] = None
167-
reach_analysis_memory_limit: Optional[int] = None
168-
reach_analysis_timeout: Optional[int] = None
171+
reach_analysis_memory_limit: Optional[str] = None
172+
reach_analysis_timeout: Optional[str] = None
169173
reach_disable_analytics: bool = False
170174
reach_disable_analysis_splitting: bool = False # Deprecated, kept for backwards compatibility
171175
reach_enable_analysis_splitting: bool = False
@@ -282,6 +286,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
282286
'ignore_commit_files': args.ignore_commit_files,
283287
'disable_blocking': args.disable_blocking,
284288
'disable_ignore': args.disable_ignore,
289+
'upload_logs': args.upload_logs,
285290
'strict_blocking': args.strict_blocking,
286291
'integration_type': args.integration,
287292
'pending_head': args.pending_head,
@@ -310,6 +315,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
310315
'reach_ecosystems': args.reach_ecosystems.split(',') if args.reach_ecosystems else None,
311316
'reach_exclude_paths': args.reach_exclude_paths.split(',') if args.reach_exclude_paths else None,
312317
'exclude_paths': normalize_exclude_paths(args.exclude_paths),
318+
'included_dirs': normalize_exclude_paths(args.include_dirs) or [],
313319
'reach_skip_cache': args.reach_skip_cache,
314320
'reach_min_severity': args.reach_min_severity,
315321
'reach_output_file': args.reach_output_file,
@@ -635,6 +641,17 @@ def create_argument_parser() -> argparse.ArgumentParser:
635641
"Supersedes --reach-exclude-paths."
636642
)
637643

644+
path_group.add_argument(
645+
"--include-dirs",
646+
dest="include_dirs",
647+
metavar="<list>",
648+
help="Comma-separated directory names that are excluded from manifest discovery by "
649+
"default but should be scanned (e.g. 'build,dist'). Names are matched against any "
650+
"path segment, mirroring the default exclude list. Defaults excluded: "
651+
"node_modules, bower_components, jspm_packages, __pycache__, .venv, venv, build, "
652+
"dist, .tox, .mypy_cache, .pytest_cache, *.egg-info, vendor."
653+
)
654+
638655
# Branch and Scan Configuration
639656
config_group = parser.add_argument_group('Branch and Scan Configuration')
640657
config_group.add_argument(
@@ -866,6 +883,26 @@ def create_argument_parser() -> argparse.ArgumentParser:
866883
action="store_true",
867884
help=argparse.SUPPRESS
868885
)
886+
log_upload_group = advanced_group.add_mutually_exclusive_group()
887+
log_upload_group.add_argument(
888+
"--upload-logs",
889+
dest="upload_logs",
890+
action="store_const",
891+
const=True,
892+
help="Upload the CLI's log output to the Socket backend for this run. "
893+
"When set, the CLI registers the run with share_logs=true and streams "
894+
"its log records in 5s batches. Default off. Mutually exclusive with "
895+
"--no-upload-logs."
896+
)
897+
log_upload_group.add_argument(
898+
"--no-upload-logs",
899+
dest="upload_logs",
900+
action="store_const",
901+
const=False,
902+
help="Explicitly opt out of uploading CLI logs to the Socket backend, even "
903+
"when an org-level override would otherwise enable it. Mutually "
904+
"exclusive with --upload-logs."
905+
)
869906
advanced_group.add_argument(
870907
"--strict-blocking",
871908
dest="strict_blocking",
@@ -951,29 +988,25 @@ def create_argument_parser() -> argparse.ArgumentParser:
951988
reachability_group.add_argument(
952989
"--reach-analysis-timeout",
953990
dest="reach_analysis_timeout",
954-
type=int,
955-
metavar="<seconds>",
956-
help="Timeout for reachability analysis in seconds"
991+
metavar="<duration>",
992+
help="Set the timeout for each reachability analysis run, e.g. 90s, 10m or 1h. (default: 10m)"
957993
)
958994
# Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help.
959995
reachability_group.add_argument(
960996
"--reach-timeout",
961997
dest="reach_analysis_timeout",
962-
type=int,
963998
help=argparse.SUPPRESS
964999
)
9651000
reachability_group.add_argument(
9661001
"--reach-analysis-memory-limit",
9671002
dest="reach_analysis_memory_limit",
968-
type=int,
969-
metavar="<mb>",
970-
help="Memory limit for reachability analysis in MB (defaults to the coana CLI's own default, currently 8192)"
1003+
metavar="<size>",
1004+
help="Set the memory limit for each reachability analysis run, e.g. 512MB or 8GB. (default: 8GB)"
9711005
)
9721006
# Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help.
9731007
reachability_group.add_argument(
9741008
"--reach-memory-limit",
9751009
dest="reach_analysis_memory_limit",
976-
type=int,
9771010
help=argparse.SUPPRESS
9781011
)
9791012
reachability_group.add_argument(

socketsecurity/core/cli_run.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Lifecycle helpers for a CLI run on the Socket backend.
2+
3+
A "run" represents a single CLI invocation. `register_cli_run` opens it and
4+
returns a server-issued `run_id` when streaming is enabled; `finalize_cli_run`
5+
closes it on exit. The run_id keys the rows that `BatchedLogUploader` POSTs to
6+
`/python-cli-runs/<run_id>/logs` during the run so the dashboard can show
7+
what the user saw in their terminal.
8+
9+
Streaming is opt-in via the `share_logs` field on register. The server may
10+
also force-enable streaming for an org regardless of the client's request,
11+
so the CLI always calls register and gates on the response's
12+
`log_streaming_enabled` flag rather than the client's intent.
13+
14+
Both calls are best-effort: failures fall back to no-streaming and never
15+
prevent the scan from running.
16+
"""
17+
18+
import json
19+
import logging
20+
from typing import Optional
21+
22+
from .cli_client import CliClient
23+
24+
log = logging.getLogger("socketcli")
25+
26+
27+
def register_cli_run(
28+
client: CliClient,
29+
client_version: str,
30+
upload_logs: Optional[bool],
31+
) -> Optional[str]:
32+
"""Register a CLI run with the backend.
33+
34+
`upload_logs` is the user's tri-state preference (True / False / None);
35+
it's projected to the wire-format `share_logs` and `decline_logs`
36+
booleans here, at the API boundary.
37+
38+
Best-effort: any failure (network, malformed response, unexpected JSON
39+
shape, etc.) falls back to no-streaming and must never prevent the
40+
scan from running. The single broad except is intentional.
41+
"""
42+
try:
43+
resp = client.request(
44+
path="python-cli-runs",
45+
method="POST",
46+
payload=json.dumps({
47+
"client_version": client_version,
48+
"share_logs": upload_logs is True,
49+
"decline_logs": upload_logs is False,
50+
}),
51+
)
52+
body = resp.json()
53+
if not body.get("log_streaming_enabled"):
54+
log.debug("cli-run register: log streaming not enabled by server")
55+
return None
56+
run_id = body.get("run_id")
57+
if not isinstance(run_id, str) or not run_id:
58+
log.debug(f"cli-run register: enabled but missing run_id in response: {body!r}")
59+
return None
60+
return run_id
61+
except Exception as e:
62+
log.debug(f"cli-run register failed (streaming disabled): {e}")
63+
return None
64+
65+
66+
def finalize_cli_run(
67+
client: CliClient,
68+
run_id: str,
69+
status: str = "success",
70+
report_run_id: Optional[str] = None,
71+
) -> None:
72+
try:
73+
client.request(
74+
path=f"python-cli-runs/{run_id}/finalize",
75+
method="POST",
76+
payload=json.dumps({"status": status, "report_run_id": report_run_id}),
77+
)
78+
except Exception as e:
79+
log.debug(f"cli-run finalize failed (swallowed): {e}")

0 commit comments

Comments
 (0)