Skip to content

Commit 3b154de

Browse files
committed
feat: add --no-upload-logs to explicitly decline log upload
Backend now distinguishes "user wants out" from "user said nothing": - `decline_logs: true` (the new flag) overrides every other signal including the server-side org-level override, so users with a legal/consent reason for no upload get a guaranteed off. - `share_logs: true` (the existing --upload-logs) opts in. - Otherwise the server applies its own policy. Argparse enforces that --upload-logs and --no-upload-logs are mutually exclusive (post-parse check via parser.error so dash/underscore aliases on either side still coexist with the same dests). register_cli_run now sends both `share_logs` and `decline_logs` in the payload; setup_streaming forwards both. CHANGELOG 2.4.8 entry updated to call out --no-upload-logs alongside --upload-logs.
1 parent 06982fb commit 3b154de

7 files changed

Lines changed: 65 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
### Added: opt-in streaming log channel via `--upload-logs`
66

7-
- 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. The Socket backend can also force-enable streaming for specific orgs regardless of the flag. The feature is best-effort — registration or upload failures silently degrade and never block the scan.
7+
- 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.
8+
- 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).
9+
- 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.
810

911
## 2.4.7
1012

socketsecurity/config.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class CliConfig:
140140
disable_blocking: bool = False
141141
disable_ignore: bool = False
142142
upload_logs: bool = False
143+
decline_logs: bool = False
143144
strict_blocking: bool = False
144145
integration_type: IntegrationType = "api"
145146
integration_org_slug: Optional[str] = None
@@ -213,6 +214,9 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
213214

214215
args = parser.parse_args(args_list)
215216

217+
if args.upload_logs and args.decline_logs:
218+
parser.error("--upload-logs and --no-upload-logs are mutually exclusive")
219+
216220
if args.reach_exclude_paths:
217221
logging.warning(
218222
"--reach-exclude-paths is deprecated; use --exclude-paths instead. "
@@ -284,6 +288,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
284288
'disable_blocking': args.disable_blocking,
285289
'disable_ignore': args.disable_ignore,
286290
'upload_logs': args.upload_logs,
291+
'decline_logs': args.decline_logs,
287292
'strict_blocking': args.strict_blocking,
288293
'integration_type': args.integration,
289294
'pending_head': args.pending_head,
@@ -874,14 +879,29 @@ def create_argument_parser() -> argparse.ArgumentParser:
874879
action="store_true",
875880
help="Upload the CLI's log output to the Socket backend for this run. "
876881
"When set, the CLI registers the run with share_logs=true and streams "
877-
"its log records in 5s batches. Default off."
882+
"its log records in 5s batches. Default off. Mutually exclusive with "
883+
"--no-upload-logs."
878884
)
879885
advanced_group.add_argument(
880886
"--upload_logs",
881887
dest="upload_logs",
882888
action="store_true",
883889
help=argparse.SUPPRESS
884890
)
891+
advanced_group.add_argument(
892+
"--no-upload-logs",
893+
dest="decline_logs",
894+
action="store_true",
895+
help="Explicitly opt out of uploading CLI logs to the Socket backend, even "
896+
"when an org-level override would otherwise enable it. Mutually "
897+
"exclusive with --upload-logs."
898+
)
899+
advanced_group.add_argument(
900+
"--no_upload_logs",
901+
dest="decline_logs",
902+
action="store_true",
903+
help=argparse.SUPPRESS
904+
)
885905
advanced_group.add_argument(
886906
"--strict-blocking",
887907
dest="strict_blocking",

socketsecurity/core/cli_run.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@ def register_cli_run(
2929
client: CliClient,
3030
client_version: str,
3131
share_logs: bool,
32+
decline_logs: bool,
3233
) -> Optional[str]:
3334
try:
3435
resp = client.request(
3536
path="python-cli-runs",
3637
method="POST",
37-
payload=json.dumps({"client_version": client_version, "share_logs": share_logs}),
38+
payload=json.dumps({
39+
"client_version": client_version,
40+
"share_logs": share_logs,
41+
"decline_logs": decline_logs,
42+
}),
3843
)
3944
except APIFailure as e:
4045
log.debug(f"cli-run register failed (streaming disabled): {e}")

socketsecurity/core/streaming.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ def setup_streaming(
3737
sdk_logger: logging.Logger,
3838
client_version: str,
3939
share_logs: bool,
40+
decline_logs: bool,
4041
enable_debug: bool,
4142
) -> Optional[Callable[[], None]]:
4243
run_id = register_cli_run(
4344
client,
4445
client_version=client_version,
4546
share_logs=share_logs,
47+
decline_logs=decline_logs,
4648
)
4749
if not run_id:
4850
cli_logger.debug("server log streaming not active for this run")

socketsecurity/socketcli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def main_code():
198198
sdk_logger=socket_logger,
199199
client_version=config.version,
200200
share_logs=config.upload_logs,
201+
decline_logs=config.decline_logs,
201202
enable_debug=config.enable_debug,
202203
)
203204
if teardown:

tests/unit/test_cli_run.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ def test_register_cli_run_returns_run_id_when_enabled():
1919
"run_id": "srv-issued-123",
2020
})
2121

22-
run_id = register_cli_run(client, client_version="1.2.3", share_logs=True)
22+
run_id = register_cli_run(
23+
client, client_version="1.2.3", share_logs=True, decline_logs=False
24+
)
2325

2426
assert run_id == "srv-issued-123"
2527
args, kwargs = client.request.call_args
2628
assert kwargs["path"] == "python-cli-runs"
2729
assert kwargs["method"] == "POST"
2830
body = json.loads(kwargs["payload"])
29-
assert body == {"client_version": "1.2.3", "share_logs": True}
31+
assert body == {"client_version": "1.2.3", "share_logs": True, "decline_logs": False}
3032

3133

3234
def test_register_cli_run_returns_none_when_disabled_by_server():
@@ -36,31 +38,47 @@ def test_register_cli_run_returns_none_when_disabled_by_server():
3638
"run_id": None,
3739
})
3840

39-
assert register_cli_run(client, client_version="1.0.0", share_logs=False) is None
41+
assert register_cli_run(
42+
client, client_version="1.0.0", share_logs=False, decline_logs=False
43+
) is None
4044

4145

4246
def test_register_cli_run_sends_share_logs_false_when_not_opted_in():
4347
client = Mock(spec=CliClient)
4448
client.request.return_value = _resp({"log_streaming_enabled": False, "run_id": None})
4549

46-
register_cli_run(client, client_version="1.0.0", share_logs=False)
50+
register_cli_run(client, client_version="1.0.0", share_logs=False, decline_logs=False)
4751

4852
body = json.loads(client.request.call_args.kwargs["payload"])
49-
assert body == {"client_version": "1.0.0", "share_logs": False}
53+
assert body == {"client_version": "1.0.0", "share_logs": False, "decline_logs": False}
54+
55+
56+
def test_register_cli_run_sends_decline_logs_true_when_opted_out():
57+
client = Mock(spec=CliClient)
58+
client.request.return_value = _resp({"log_streaming_enabled": False, "run_id": None})
59+
60+
register_cli_run(client, client_version="1.0.0", share_logs=False, decline_logs=True)
61+
62+
body = json.loads(client.request.call_args.kwargs["payload"])
63+
assert body == {"client_version": "1.0.0", "share_logs": False, "decline_logs": True}
5064

5165

5266
def test_register_cli_run_returns_none_on_api_failure():
5367
client = Mock(spec=CliClient)
5468
client.request.side_effect = APIFailure("network down")
5569

56-
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
70+
assert register_cli_run(
71+
client, client_version="1.0.0", share_logs=True, decline_logs=False
72+
) is None
5773

5874

5975
def test_register_cli_run_returns_none_on_missing_run_id_when_enabled():
6076
client = Mock(spec=CliClient)
6177
client.request.return_value = _resp({"log_streaming_enabled": True})
6278

63-
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
79+
assert register_cli_run(
80+
client, client_version="1.0.0", share_logs=True, decline_logs=False
81+
) is None
6482

6583

6684
def test_register_cli_run_returns_none_on_bad_json():
@@ -69,7 +87,9 @@ def test_register_cli_run_returns_none_on_bad_json():
6987
client = Mock(spec=CliClient)
7088
client.request.return_value = bad
7189

72-
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
90+
assert register_cli_run(
91+
client, client_version="1.0.0", share_logs=True, decline_logs=False
92+
) is None
7393

7494

7595
def test_finalize_cli_run_posts_status_and_null_report_run_id_by_default():

tests/unit/test_streaming.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def test_setup_streaming_returns_none_when_register_fails():
2828
sdk_logger=logging.getLogger("t-fail-sdk"),
2929
client_version="1.0",
3030
share_logs=True,
31+
decline_logs=False,
3132
enable_debug=False,
3233
)
3334
assert teardown is None
@@ -52,6 +53,7 @@ def fake_finalize(client, run_id, status="success", report_run_id=None):
5253
sdk_logger=sdk_logger,
5354
client_version="1.0",
5455
share_logs=True,
56+
decline_logs=False,
5557
enable_debug=False,
5658
)
5759
assert teardown is not None
@@ -82,6 +84,7 @@ def fake_finalize(client, run_id, status="success", report_run_id=None):
8284
sdk_logger=sdk_logger,
8385
client_version="1.0",
8486
share_logs=True,
87+
decline_logs=False,
8588
enable_debug=False,
8689
)
8790
teardown()
@@ -108,6 +111,7 @@ def test_setup_streaming_restores_logger_state_on_teardown():
108111
sdk_logger=sdk_logger,
109112
client_version="1.0",
110113
share_logs=True,
114+
decline_logs=False,
111115
enable_debug=False,
112116
)
113117
# During streaming: levels and propagate are forced

0 commit comments

Comments
 (0)