Skip to content

fix(redact): catch product-native credentials in content-shaped secret detection - #33

Open
DevChiniwala wants to merge 1 commit into
Helmryth:mainfrom
DevChiniwala:fix/redact-product-native-credential-prefixes
Open

fix(redact): catch product-native credentials in content-shaped secret detection#33
DevChiniwala wants to merge 1 commit into
Helmryth:mainfrom
DevChiniwala:fix/redact-product-native-credential-prefixes

Conversation

@DevChiniwala

Copy link
Copy Markdown

Summary

KEY_PREFIXES in server/redact.ts catches content-shaped secrets — credentials that appear as standalone text in bot replies, tool titles, and permission cards, where the key-name heuristic (isSecretName) does not apply.

Two credential prefixes native to HelmRyth itself were missing:

  • box_* — ascii.dev Box API tokens (box.ts:195 validates token.startsWith("box_")). A leaked token grants access to the user's cloud computers.
  • whsec_* — webhook signing secrets (webhooks.ts:280 generates "whsec_" + randomBytes(32)). A leaked secret allows unauthenticated webhook delivery.

Both are caught when they appear inside key-value structures (the key name contains "token" / "secret"), but pass through unmasked as standalone text — a bot echoing a curl command, a user pasting a token into chat, or a tool output containing the raw value.

What changed

  • Added two regex entries to KEY_PREFIXES, positioned between npm_ and JWT. The box_ pattern requires a qualifier segment (box_live_, box_test_, etc.) to avoid matching variable names like box_model.
  • Added two test cases following the existing split-prefix pattern.

Verification

  • pnpm typecheck — clean
  • pnpm test -- server/redact.test.ts — all 13 tests pass
  • pnpm check:brand — clean

…t detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new Box token prefix regex may miss valid box_ tokens that don’t include a qualifier segment, leaving a potential standalone-secret leak path.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR strengthens server/redact.ts’s “content-shaped secret” detection by expanding KEY_PREFIXES to include two HelmRyth-native credential formats that can appear as standalone text (e.g., in tool titles, bot replies, permission cards), ensuring they’re masked even when key-name heuristics don’t apply.

Changes:

  • Added box_* and whsec_* prefix-based regexes to KEY_PREFIXES for content-shaped secret redaction.
  • Added corresponding test cases in server/redact.test.ts to verify masking behavior.
File summaries
File Description
server/redact.ts Extends prefix-based redaction patterns to catch Box tokens and webhook secrets in plain text.
server/redact.test.ts Adds regression cases ensuring the new prefixes are masked when embedded in text.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread server/redact.ts
/\bAKIA[0-9A-Z]{16}\b/g, // aws access key id
/\bAIza[0-9A-Za-z_-]{30,}/g, // google api key
/\bnpm_[A-Za-z0-9]{20,}/g, // npm
/\bbox_[a-z]+_[A-Za-z0-9_-]{4,}/g, // ascii.dev box api token
@DivyamTalwar

Copy link
Copy Markdown
Member

@DevChiniwala — the insight behind this one is the best of the three. The redactor covered third-party credentials while missing the two this product mints and accepts itself. That's a gap you only find by reading the codebase properly, and "product-native" is exactly the right framing.

I confirmed the gap is real. On main, content-shaped redaction leaves every box_ value untouched:

redactSecretsInText("token box_live_zzz")  ->  "token box_live_zzz"

The existing box_live_zzz coverage in redact.test.ts:69 passes through the object/env path, keyed on the name HELMRYTH_BOX_TOKEN containing "TOKEN" — nothing was catching the value itself in free text. So the premise is correct.

server/redact.test.ts passes 13/13 locally. One of the two patterns needs rework.

whsec_ — correct as written

Matches server/webhooks.ts:280:

return `whsec_${randomBytes(32).toString("base64url")}`;

43 base64url chars, comfortably over {20,}. And the whsec_demo fixture in WebhooksPanel.test.ts is short enough not to trip it. Good.

box_ — too narrow and too broad at the same time

/\bbox_[a-z]+_[A-Za-z0-9_-]{4,}/g assumes a Stripe-style box_<word>_<payload> structure. The product never promises that. server/box.ts:197 states the only rule it actually guarantees:

That doesn't look like a box API key: they start with box_.

Requiring [a-z]+_ after the prefix means a key whose payload begins with an uppercase character — or that has no word segment at all — slips straight through. Measured:

value current box_[A-Za-z0-9_-]{16,}
box_live_abcdefghijklmnop (your fixture) MATCH MATCH
box_ + mixed-case payload, no word segment MISS MATCH
box_live_zzz (repo fixture) MISS (payload is 3, needs 4)
box_shadow_none MATCH
box_model_border MATCH
box_sizing_content MATCH

So it misses real keys and redacts ordinary identifiers. The false positives are the more serious half, because they contradict the contract stated directly above your change:

High precision on purpose: a generic "long hex/base64" heuristic would rewrite real code in the transcript, so only shapes that are unmistakably credentials match.

The test passes because it asserts the one shape the regex was built around — worth watching for, since a green test on a guessed format reads as more assurance than it is.

Requested change

/\bbox_[A-Za-z0-9_-]{16,}/g, // ascii.dev box api token

Follows the rule box.ts actually states, catches payloads regardless of case or structure, and the {16,} floor clears box_shadow_none, box_model_border and box_sizing_content — all of which fall short of it.

I checked this doesn't regress anything: box_live_zzz and box_live_dontlogme are both redacted via the env-key path, not this one, so shortening coverage here changes no existing test.

One coordination note

This and #32 both insert after the npm_ line in server/redact.ts and at the same spot in server/redact.test.ts. I confirmed they conflict — whichever lands first, the other needs a rebase.

CI hasn't run yet; GitHub holds workflow runs for first-time contributors until a maintainer approves. I'll approve it.

Swap the box_ pattern and this is a good change. Thanks for digging into the actual credential surface rather than just adding well-known prefixes — that's the harder and more useful version of this work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants