Uh oh!
There was an error while loading. Please reload this page.
security: redact CLI logs, bound regex matching (ReDoS) (PER-8609/8615) - #2279
Conversation
Shivanshu-07
commented
Jun 16, 2026
Claude Code PR ReviewPR:#2279 • Head:bda111a • Reviewers: stack:code-reviewer SummaryThree runtime-hardening measures in
Review Table
Findings
Verdict: PASS |
Uh oh!
There was an error while loading. Please reload this page.
…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
3e0aae3Compare…-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>
Shivanshu-07
commented
Jul 8, 2026
Resolved the Semgrep |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shivanshu-07
commented
Jul 9, 2026
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 |
Shivanshu-07
commented
Jul 13, 2026
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- 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>
Shivanshu-07
commented
Jul 15, 2026
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 |
Uh oh!
There was an error while loading. Please reload this page.
Master landed the same fix in #2279 (security: redact CLI logs, bound regex matching). Both conflicting hunks resolved in favour of master's version, which is a superset of this branch's: same redactSecrets call on clilogs in sendBuildLogs, same compile-patterns-once memoization, plus in-place entry mutation, the ReDoS bound and a semgrep annotation this branch didn't have. What remains of this branch is the integration-level sendBuildLogs redaction test; master's coverage for #2279 is unit-level in test/unit/utils.test.js. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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