redact bearer tokens from captured deploy logs - #1829
Conversation
beam deploy --format json captured the printed invocation examples (curl/websocat) verbatim, so the JSON logs array included the full workspace bearer token — which then leaked into CI logs and files. Captured logs now redact Authorization header values; the interactive copy-paste snippet is unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
2 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="sdk/src/beta9/logging.py">
<violation number="1" location="sdk/src/beta9/logging.py:13">
P2: The bearer-token redactor only strips values whose characters all fall inside the RFC 6750 token68 charset (`[A-Za-z0-9-._~+/=]`). The canonical gateway-generated token is base64url and is fully covered, but the SDK also builds tokens from caller-provided values via `BETA9_TOKEN`/`BEAM_TOKEN` or the interactive Token prompt, which accept arbitrary >=64-char strings. If such a token contains a character outside that charset (e.g. `%`, `!`, `#`, `@`, `:`), redaction stops at the first offending character and the rest of the live credential leaks into the JSON `logs` that `beam deploy --format json` persists to CI logs/files — the exact scenario this PR is intended to fix. Consider matching a broader “run up to whitespace/quotes” token instead of restricting to token68, so any non-whitespace/non-quote token is fully replaced; over-redacting is safer than under-redacting for this capture path.</violation>
<violation number="2" location="sdk/src/beta9/logging.py:167">
P1: Captured JSON deploy logs can still persist a live bearer token when the output is split across writes, because redaction runs independently on each `write()` fragment. Buffer captured text across writes or redact the complete assembled log entries before returning the JSON output.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| # Captured logs end up in machine-readable output (e.g. deploy | ||
| # --format json) that gets piped into CI logs and files, so strip | ||
| # credentials that are fine to show interactively. | ||
| data = redact_bearer_tokens(data) |
There was a problem hiding this comment.
P1: Captured JSON deploy logs can still persist a live bearer token when the output is split across writes, because redaction runs independently on each write() fragment. Buffer captured text across writes or redact the complete assembled log entries before returning the JSON output.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk/src/beta9/logging.py, line 167:
<comment>Captured JSON deploy logs can still persist a live bearer token when the output is split across writes, because redaction runs independently on each `write()` fragment. Buffer captured text across writes or redact the complete assembled log entries before returning the JSON output.</comment>
<file context>
@@ -147,6 +160,11 @@ def __exit__(self, *_, **__):
+ # Captured logs end up in machine-readable output (e.g. deploy
+ # --format json) that gets piped into CI logs and files, so strip
+ # credentials that are fine to show interactively.
+ data = redact_bearer_tokens(data)
self.logs.append(data)
if not self.capture_logs:
</file context>
| # invocation examples). Restricted to the RFC 6750 token68 charset so | ||
| # surrounding quotes survive redaction. | ||
| _BEARER_TOKEN_PATTERN = re.compile( | ||
| r"(Authorization:\s*Bearer\s+)[A-Za-z0-9\-._~+/=]+", re.IGNORECASE |
There was a problem hiding this comment.
P2: The bearer-token redactor only strips values whose characters all fall inside the RFC 6750 token68 charset ([A-Za-z0-9-._~+/=]). The canonical gateway-generated token is base64url and is fully covered, but the SDK also builds tokens from caller-provided values via BETA9_TOKEN/BEAM_TOKEN or the interactive Token prompt, which accept arbitrary >=64-char strings. If such a token contains a character outside that charset (e.g. %, !, #, @, :), redaction stops at the first offending character and the rest of the live credential leaks into the JSON logs that beam deploy --format json persists to CI logs/files — the exact scenario this PR is intended to fix. Consider matching a broader “run up to whitespace/quotes” token instead of restricting to token68, so any non-whitespace/non-quote token is fully replaced; over-redacting is safer than under-redacting for this capture path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk/src/beta9/logging.py, line 13:
<comment>The bearer-token redactor only strips values whose characters all fall inside the RFC 6750 token68 charset (`[A-Za-z0-9-._~+/=]`). The canonical gateway-generated token is base64url and is fully covered, but the SDK also builds tokens from caller-provided values via `BETA9_TOKEN`/`BEAM_TOKEN` or the interactive Token prompt, which accept arbitrary >=64-char strings. If such a token contains a character outside that charset (e.g. `%`, `!`, `#`, `@`, `:`), redaction stops at the first offending character and the rest of the live credential leaks into the JSON `logs` that `beam deploy --format json` persists to CI logs/files — the exact scenario this PR is intended to fix. Consider matching a broader “run up to whitespace/quotes” token instead of restricting to token68, so any non-whitespace/non-quote token is fully replaced; over-redacting is safer than under-redacting for this capture path.</comment>
<file context>
@@ -2,9 +2,22 @@
+# invocation examples). Restricted to the RFC 6750 token68 charset so
+# surrounding quotes survive redaction.
+_BEARER_TOKEN_PATTERN = re.compile(
+ r"(Authorization:\s*Bearer\s+)[A-Za-z0-9\-._~+/=]+", re.IGNORECASE
+)
+
</file context>
| r"(Authorization:\s*Bearer\s+)[A-Za-z0-9\-._~+/=]+", re.IGNORECASE | |
| _BEARER_TOKEN_PATTERN = re.compile( | |
| r"(Authorization:\s*Bearer\s+)[^\s'\";]+", re.IGNORECASE | |
| ) |
Summary
beam deploy --format jsoncaptures everything printed during the deploy into the JSONlogsarray — including the "Invocation details" curl/websocat examples, which embed the full workspace bearer token (sdk/src/beta9/abstractions/base/runner.py). Anyone piping--format jsonoutput into CI logs or files was persisting a live credential; a user hit exactly this and had to rotate their tokens.StoredStdoutInterceptornow redactsAuthorization: Bearer <token>values in captured logs (Bearer [REDACTED]), at the capture choke point so any current or future print during a JSON-formatted deploy is covered.Fixes #1828
Test plan
StoredStdoutInterceptor(sdk/tests/test_logging.py)Co-authored-by: Cursor cursoragent@cursor.com
Made with Cursor