Repository navigation
fix(secrets): Close credential leaks in request text and recording URLs - #54
Merged
Merged
Conversation
…osed on more bodies
Each of these showed a password, a GitHub token, or a JWT in the text
of a request sample and a miss, as a probe of `redactRequest()` on main
shows:
- A form field with a nested name, such as `user[password]` or
`user%5Bpassword%5D`. The form fields now compare by the last part of
the name.
- A JSON credential field whose value is an array or an object, such as
`{"api_key":["..."]}`. Each string in the value is now replaced at
its place. A number stays, so a tool schema with a `password`
property keeps its shape. A credential field with a number gives no
text.
- A body whose fields the proxy cannot find: JSON or XML with a form
content type, a JSON string, `pass\u0077ord` in JSON that is not
valid, and a bare `token` with a credential prefix in a body that is
not JSON. The fail-closed check now covers forms without a credential
field and JSON scalars, reads names with unicode and URL escapes, and
looks for the credential prefixes of `TOKEN_VALUE`.
The URL of a recording now also redacts a parameter that holds the
value of a credential field of the body, as the sample already did. It
reads the body only when the URL has a query or a fragment.
`fieldKind()` starts its map again when it is full. Before, the names
of one body could fill it for all later runs: 1 M form fields after
another body with 5,000 names took 850 ms on main and 576 ms now.
No key changes: `request-key.ts` and the redaction of responses do not
change. A copy of a field value in other text, such as in a prompt or
an escaped tool input, still shows. The README says so.
Co-Authored-By: David Cramer <23610+dcramer@users.noreply.github.com>
…equest text Review of the first commit found three partial fixes: - A string in an object or array credential value was redacted in place, but its copies at other places of the body and in the recording URL stayed. Each such string is now a known value of the body. - A number inside an object or array credential value stayed, such as the bytes of a Buffer. Any number in the value now withholds the sample, as a number at the top does. - `password[]` and `user[password][0]` were not credential fields, and a form with another credential field passed `user.password=` and JSON with URL escapes through. The form name is now the last part that is not empty and not a number, and a part that can still show a credential withholds the sample. A value that is only a field name, such as `grant_type=password`, does not count. Also: one bad `%` no longer turns off the URL escape check, and a JWT with `-` in its header counts in text. Both stay linear on 16 MiB bodies. Recording keys do not change. Co-Authored-By: David Cramer <23610+dcramer@users.noreply.github.com>
…ic copies Re-review of the second commit found: - A form field named `token` without a credential prefix skipped the check for a credential in its value, so `token=%7B%22password…` kept its value when the form had another credential field. Only a field of `CREDENTIAL_FIELDS` now skips that check. - A credential value or a JWT in a part of a form name that `formName` does not keep, such as `S[password]=`, stayed. Each part of the name of each field is now checked. - A JWT right after `_`, a letter, a digit, or `-` was not found in text. `showsToken` now finds a run of base64url characters with `eyJ` in it before `.eyJ`, and it reads each run a few times at most. - A number of a credential value that the query copies stayed in the recording URL. The numbers are now known values of the body. Recording keys do not change. Co-Authored-By: David Cramer <23610+dcramer@users.noreply.github.com>
dcramer
marked this pull request as ready for review
November 5, 2026 17:40
dcramer
added a commit
that referenced
this pull request
Nov 5, 2026
…, and the JSON scan into files (#55) Stacked on #54. Merge that first, then change the base to `main`. This PR only moves code. It moves code out of `server.ts`, `service.ts`, `recorder.ts` and `secrets.ts` into 5 new files: - `bodies.ts` - `clients.ts` - `run-config.ts` - `recording-file.ts` - `json-strings.ts` A compare of the code lines before and after shows only `export`/`import` changes and oxfmt line wraps. `git range-diff` gives `=` against the version that was checked before the last rebase. It also updates docs: - the README "Files" map - the README text on idle tunnels: Node closes an idle tunnel between requests after about 5–6 s (`keepAliveTimeout`), not 10 min - the TELEMETRY table: `roach.mode` can be `off`, and `roach.reason` is on the metric too **Verification:** - `pnpm check` passes on Node 24: 114 tests, lint clean, knip clean. - `request-key.ts` has no changes, and the pinned key tests pass. <!-- junior-request-attribution:start --> via @dcramer. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC0ACFA5JBDX%3A1793830221.324719 --> -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC0ACFA5JBDX%3A1793830221.324719) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC0ACFA5JBDX%3A1793830221.324719/?project=4510944073809921) <!-- junior-session-footer:end -->
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes credential leaks that a probe audit found in sample text, miss text and the recording
request.url. Every leak below also happens on main. Recording keys don't change:request-key.tshas no diff, and the pinned key tests pass.What changes in
secrets.tspass\u0077ordin invalid JSONghp_…or JWT in text/plain or invalid JSON%that can't be decoded, which no longer turns off the escape checkpassword[],user[password][0]anduser%5Bpassword%5Dnow match on their last bracket part that isn't empty and isn't a number. Every part of a form name, credential fields included, is checked for a known value or a token prefix. A non-credential field whose value may show a credential, including a baretoken, withholds the sample.grant_type=passwordandrefresh_tokenkeep their text.?q=are redacted too. A number anywhere in a credential value withholds the sample, and a copy of the number in the recording URL is redacted._,-or an alphanumeric character, and a JWT with-in its header.recorder.tsnow redacts a copy of a body credential in the recording'srequest.url.Trade-offs
*_typeOAuth bodies.eyJpasswordproperty show"type":"<<redacted>>"in samples.Verification
pnpm checkpasses on Node 24: 114 tests, lint clean, knip clean.redaction.test.tsfailed each time.keyRequest, thenredactRequestWith/redactRequestUrl. The final verdict was approve, with no must-fix items.%that can't be decoded.orJinside a JWTrefactor/service-split(move-only) is stacked on this branch.via @dcramer.
--
View Session [Sentry]