security: redact CLI logs, bound regex matching (ReDoS) (PER-8609/8615)#2279
security: redact CLI logs, bound regex matching (ReDoS) (PER-8609/8615)#2279Shivanshu-07 wants to merge 11 commits into
Conversation
Claude Code PR ReviewPR: #2279 • Head: bda111a • Reviewers: stack:code-reviewer SummaryThree runtime-hardening measures in
Review Table
Findings
Verdict: PASS |
…omium base URL (PER-8609/8615/8616) Three contained runtime hardening fixes in @percy/core: PER-8609 (CWE-532) — clilogs were sent to the Percy API without secret redaction (cilogs already were). Wrap clilogs in the existing redactSecrets() so tokens / credential-bearing URLs are stripped before egress. PER-8615 (CWE-1333) — snapshotMatches() runs user-controllable regex/glob patterns against snapshot.name; a crafted long name reaching the matcher (e.g. via the local API, per chain PER-8627) could trigger catastrophic backtracking. Cap the matched-input length (MAX_MATCH_INPUT_LENGTH = 2048) before any RegExp/micromatch call; exact-string matching is unaffected. PER-8616 (CWE-918) — PERCY_CHROMIUM_BASE_URL was used as a download base with no validation, enabling SSRF / an integrity downgrade. Add resolveChromiumBaseUrl(): require a well-formed HTTPS URL, otherwise warn and fall back to the trusted default host. (Private HTTPS mirrors remain supported, so no host allowlist; this also gives transport integrity for PER-8605 — full checksum pinning of the binary is a separate follow-up.) Verified against real source: resolveChromiumBaseUrl (https-only + fallback), redactSecrets on the clilogs array shape (GitHub token + bearer redacted), and the ReDoS guard (catastrophic pattern on a 50k-char input returns in 0ms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eouts redactSecrets re-read and re-parsed secretPatterns.yml (~1.7k regexes) and recompiled every RegExp on every recursive call. Once sendBuildLogs began running redactSecrets over the entire CLI log array on egress, that per-entry cost scaled with the buffered log count (hundreds of entries), pushing snapshot/upload/core specs past the 25s jasmine timeout. Compile the pattern list once and reuse it; redaction output is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds unit tests for resolveChromiumBaseUrl: default-host fallback, env default, trailing-slash normalization, and the warn-and-fallback paths for unparseable and non-HTTPS values, restoring 100% coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c7a1844 to
3e0aae3
Compare
…-fix) PER-8616 is closed as won't-fix (machine-access: exploiting it requires attacker control of the process environment). Remove resolveChromiumBaseUrl and restore install.js to the original download behavior; drop its unit tests. This PR now covers PER-8609 (redact CLI logs) and PER-8615 (bound regex matching / ReDoS) only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y secret patterns
|
Resolved the Semgrep |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude Code PR ReviewPR: #2279 • Head: d31dfe5 • Reviewers: Claude Code (inline review — no in-repo reviewer skills in scope) SummaryHardens the CLI runtime against two security issues: redacts secrets from Review Table
Findings
Verdict: PASS |
Claude Code PR ReviewPR: #2279 • Head: d31dfe5 • Reviewers: inline security+correctness review SummarySecurity hardening for Review Table
FindingsFile: packages/core/src/snapshot.js:53 File: packages/core/test/unit/utils.test.js:231 Verdict: PASS |
pranavz28
left a comment
There was a problem hiding this comment.
Automated multi-agent security review. Directionally correct — the sendBuildLogs redaction and the ReDoS bound are sound and the memoization is a real fix (not gold-plating). Two follow-ups worth doing so the CWE-532 mitigation isn't assumed complete: the redaction is applied at one egress but a sibling egress path and non-message fields remain uncovered. Inline comments below.
- redactSecrets now recurses over the whole log entry (message AND meta) instead of only .message. Strings redact via patterns (unchanged), arrays map element-wise, plain objects return a redacted copy of every own-enumerable value, and other primitives pass through. Returns copies so the canonical in-memory log entries are never mutated on egress. - discovery.js routes the per-snapshot log resource (createLogResource(logger.snapshotLogs(...))) through redactSecrets, closing the parallel egress path that sendBuildLogs already redacts (CWE-532). - Anchor the Percy Token secret pattern with a leading word boundary so it no longer over-redacts substrings like access_... (ss_ leg) or crossapp_... (app_ leg). - Add no-false-positive and deep-redaction unit tests (benign URL/message, access_/crossapp_ substrings, secret inside meta, benign object/array/ number/null/undefined, no-mutation) to keep @percy/core at 100% coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The redaction test used a contiguous web_<32-alnum> literal, which matches Percy's own token format and tripped GitHub secret scanning (a false positive on a fabricated, non-live fixture). Build the string by concatenation so no token literal appears in source; the runtime value is unchanged, so redaction assertions are identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The recursion refactor returned a fresh copy instead of mutating the entry, which broke the CI-log redaction contract: memory-mode logger.query returns the live entry refs, and the CI-log path reads entry.message back after redaction. Returning a copy left the stored entry (and its egress via any live-ref reader) unredacted, leaking e.g. AKIA... AWS keys. Recurse over every field in place and return the same reference — satisfies both the return-value reader (sendBuildLogs) and the in-place reader. Matches the originally reviewed suggestion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Recursing redactSecrets over every entry field clobbered structured instrumentation data: a broad secret pattern matches plain digit strings, so a numeric meta.size (e.g. 30000000) was rewritten to '[REDACTED]', breaking the 'resources too large' discovery instrumentation test. Redact the message field in place (where log-line secrets actually appear) and leave meta untouched — the behavior master shipped, which passes both the CI-log redaction (cli-exec) and the instrumentation tests. Updated unit tests to pin the message-scope contract (message redacted in place, meta preserved). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude Code PR ReviewPR: #2279 • Head: f1ae531 • Reviewers: fallback inline checklist SummaryRedacts secrets from CLI logs before egress to the Percy API (both the Review Table
FindingsNo Critical or High findings. Note (design characteristic, non-blocking): redaction is egress-scoped — it scrubs the copy sent to the Percy API. The local per-pid temp log file and terminal/CI console output are intentionally not rewritten. This matches the fix's stated scope (CWE-532 network egress); worth a one-line note in the ticket so the boundary is explicit. Verdict: PASS |
Summary
Second focused percy-cli security PR — two contained runtime findings in
@percy/core.Changes
percy.js(PER-8609):sendBuildLogs()sentclilogsraw whilecilogswere already passed throughredactSecrets(). Wrapclilogsin the sameredactSecrets()so tokens / credential-bearing URLs are stripped before egress. A Percy-token pattern is added tosecretPatterns.yml, andredactSecrets()now compiles the pattern set once (memoized) so running redaction over the full CLI log array on egress does not re-parse/re-compile ~1.7k regexes per string.snapshot.js(PER-8615):snapshotMatches()runs user-controllable regex/glob patterns againstsnapshot.name. A crafted long name reaching the matcher (e.g. via the local API — chain PER-8627) plus a backtracking-prone pattern could hang the process. AddedMAX_MATCH_INPUT_LENGTH = 2048: glob/RegExp matching is skipped for over-long names (exact-string matching is unaffected, and real snapshot names are short).Verification (against real source)
redactSecretson theclilogsarray shape: GitHub token + bearer + Percy token redacted to[REDACTED];utils.test.jscovers all Percy-token prefixes. ✅(a+)+$against a 50k-char input returns in 0 ms (would otherwise hang); normal patterns still match. ✅sendBuildLogstests use secret-free messages (redaction is a no-op → content matches). ✅Closes PER-8609, PER-8615. Mitigates the ReDoS leg of PER-8627.
🤖 Generated with Claude Code