You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
core: redact sensitive headers in the fetch log #1561
Redact sensitive header values in the fetch tracker before any request/response is recorded: Authorization, Cookie, Set-Cookie, Proxy-Authorization, X-Api-Key, and x-mcp-remote-auth are replaced with a [REDACTED] marker.
Why
createFetchTracker entries flow to the in-memory list (visible in the UI), the pino logger, and persisted session storage. None of those sinks should ever see live bearer tokens or session cookies — especially once OAuth tokens ride these requests.
Re-implement informed by these changes at 33fac3f:
core/mcp/fetchTracking.ts — SENSITIVE_HEADERS set, REDACTED_HEADER_VALUE, redactSensitiveHeaders() applied to both request and response headers before recording (+48/−7)
Checked out v2/1561-redact-headers (commit 38102706), npm install at root, and exercised the redaction two ways: (1) calling redactSensitiveHeaders directly with a mixed-casing header set, and (2) driving the real entry path through createFetchTracker with a fake fetch and capturing the recorded trackRequest entry. No Playwright — a throwaway tsx script printing real before/after output (included below).
Confirmed: all six sensitive headers become [REDACTED] case-insensitively, benign headers and original key casing are preserved, the returned object is a distinct copy, and the live/original header object is untouched. The same holds through the real createFetchTracker entry path for both request and response headers.
AGENTS.md compliance
Mantine: N/A — this is core/ (no React/UI surface in the diff).
TypeScript: No any, no @ts-ignore / @ts-expect-error / eslint-disable / v8 ignore anywhere in the diff. redactSensitiveHeaders is fully typed (Record<string, string> → Record<string, string>); SENSITIVE_HEADERS is a ReadonlySet<string>. (The two "any" matches in the diff are prose in the commit message / a code comment, not a type.)
Coverage (cd clients/web && npm run test:coverage, full unit + integration suite, 3242 tests passing) — core/mcp/fetchTracking.ts:
Dimension
%
Covered/Total
Statements
100
59/59
Branches
100
33/33
Functions
100
9/9
Lines
100
57/57
All four dimensions ≥ 90 (all at 100). Note: running the fetchTracking.test.ts file in isolation reports Functions 88.88% only because createFetchTracker / isLongLivedStreamResponse are additionally exercised by the transport & integration suites; the full-suite aggregate is 100%.
Verdict
✅ PASS. Redaction works correctly and case-insensitively across all six sensitive headers, preserves casing and benign headers, does not mutate the live object, and the recorded entry is redacted through the real tracker path. Fully typed with no suppressions and 100% per-file coverage. No violations found. Recommend merge.
Smoke test script (throwaway, not committed)
// Smoke test for PR #1585 — redact sensitive headers in the fetch log (issue #1561)// Run: cd clients/web && npx tsx smoke-redact-1561.mtsimport{redactSensitiveHeaders,createFetchTracker,REDACTED_HEADER_VALUE,}from"../../core/mcp/fetchTracking.ts";importtype{FetchRequestEntryBase}from"../../core/mcp/types.ts";constline=(s: string)=>console.log(s);// ---- Part A: direct redactSensitiveHeaders with mixed casing ----constoriginal: Record<string,string>={Authorization: "Bearer super-secret-token",Cookie: "session=abc123","Set-Cookie": "session=abc123; HttpOnly","Proxy-Authorization": "Basic zzz","X-Api-Key": "sk-live-9999","x-mcp-remote-auth": "Bearer inspector-backend-token","Content-Type": "application/json","X-Trace-Id": "trace-42",};constsnapshot=JSON.stringify(original);constredacted=redactSensitiveHeaders(original);line("=== Part A: redactSensitiveHeaders (mixed casing) ===");line("REDACTED sentinel = "+JSON.stringify(REDACTED_HEADER_VALUE));line("");line("BEFORE (original object):");line(JSON.stringify(original,null,2));line("");line("AFTER (returned copy):");line(JSON.stringify(redacted,null,2));line("");line("Live/original object untouched? "+(JSON.stringify(original)===snapshot));line("Returned is a different object? "+(redacted!==original));line("");constexpectRedacted=["Authorization","Cookie","Set-Cookie","Proxy-Authorization","X-Api-Key","x-mcp-remote-auth",];constexpectKept=["Content-Type","X-Trace-Id"];letok=true;for(constkofexpectRedacted){constpass=redacted[k]===REDACTED_HEADER_VALUE;ok&&=pass;line(` [${pass ? "OK" : "FAIL"}] ${k} redacted (casing preserved: ${kinredacted})`);}for(constkofexpectKept){constpass=redacted[k]===original[k];ok&&=pass;line(` [${pass ? "OK" : "FAIL"}] ${k} preserved = ${JSON.stringify(redacted[k])}`);}constcaseCheck=redactSensitiveHeaders({authorization: "a",AUTHORIZATION: "b","x-API-key": "c",});line("");line("Case-insensitivity spot check:");line(" "+JSON.stringify(caseCheck));ok&&=caseCheck.authorization===REDACTED_HEADER_VALUE&&caseCheck.AUTHORIZATION===REDACTED_HEADER_VALUE&&caseCheck["x-API-key"]===REDACTED_HEADER_VALUE;// ---- Part B: drive the fetch-tracker entry path end-to-end ----line("");line("=== Part B: createFetchTracker records a redacted entry ===");letcaptured: FetchRequestEntryBase|undefined;constfakeFetch: typeoffetch=async()=>newResponse("pong",{status: 200,headers: {"Set-Cookie": "sess=leak-me; HttpOnly","Content-Type": "text/plain"},});consttracked=createFetchTracker(fakeFetch,{trackRequest: (entry)=>{captured=entry;},});awaittracked("https://example.test/mcp",{method: "POST",headers: {Authorization: "Bearer LIVE-TOKEN-DO-NOT-LOG","x-mcp-remote-auth": "Bearer BACKEND-TOKEN","Content-Type": "application/json",},});line("Recorded request headers:");line(JSON.stringify(captured?.requestHeaders,null,2));line("Recorded response headers:");line(JSON.stringify(captured?.responseHeaders,null,2));constreqH=captured?.requestHeaders??{};constresH=captured?.responseHeaders??{};constbPass=reqH["authorization"]===REDACTED_HEADER_VALUE&&reqH["x-mcp-remote-auth"]===REDACTED_HEADER_VALUE&&reqH["content-type"]==="application/json"&&resH["set-cookie"]===REDACTED_HEADER_VALUE&&resH["content-type"]==="text/plain";line("Entry-path redaction correct? "+bPass);ok&&=bPass;line("");line(ok ? "ALL SMOKE CHECKS PASSED ✅" : "SMOKE CHECKS FAILED ❌");process.exit(ok ? 0 : 1);
🔗 Implemented by PR #1585. Its code is already integrated into the Wave 1 rollup branch 1579-wave-1 (see rollup PR #1601 / verification #1600). #1585 currently targets v2/main and will auto-show as Merged once the rollup lands in v2/main. The Development panel doesn't link it now because the closing keyword is on a non-default base branch.
Activity
cliffhall commentedon Jul 2, 2026
Smoke test & AGENTS.md audit — PR #1585
Checked out
v2/1561-redact-headers(commit38102706),npm installat root, and exercised the redaction two ways: (1) callingredactSensitiveHeadersdirectly with a mixed-casing header set, and (2) driving the real entry path throughcreateFetchTrackerwith a fakefetchand capturing the recordedtrackRequestentry. No Playwright — a throwawaytsxscript printing real before/after output (included below).Smoke method
redactSensitiveHeaders({ Authorization, Cookie, Set-Cookie, Proxy-Authorization, X-Api-Key, x-mcp-remote-auth, Content-Type, X-Trace-Id })with mixed casing.createFetchTracker(fakeFetch, { trackRequest })→await tracked(url, { headers: { Authorization, x-mcp-remote-auth, Content-Type } }), response carryingSet-Cookie; then inspected the captured entry'srequestHeaders/responseHeaders.Real output
Confirmed: all six sensitive headers become
[REDACTED]case-insensitively, benign headers and original key casing are preserved, the returned object is a distinct copy, and the live/original header object is untouched. The same holds through the realcreateFetchTrackerentry path for both request and response headers.AGENTS.md compliance
Mantine: N/A — this is
core/(no React/UI surface in the diff).TypeScript: No
any, no@ts-ignore/@ts-expect-error/eslint-disable/v8 ignoreanywhere in the diff.redactSensitiveHeadersis fully typed (Record<string, string> → Record<string, string>);SENSITIVE_HEADERSis aReadonlySet<string>. (The two "any" matches in the diff are prose in the commit message / a code comment, not a type.)Coverage (
cd clients/web && npm run test:coverage, full unit + integration suite, 3242 tests passing) —core/mcp/fetchTracking.ts:All four dimensions ≥ 90 (all at 100). Note: running the
fetchTracking.test.tsfile in isolation reports Functions 88.88% only becausecreateFetchTracker/isLongLivedStreamResponseare additionally exercised by the transport & integration suites; the full-suite aggregate is 100%.Verdict
✅ PASS. Redaction works correctly and case-insensitively across all six sensitive headers, preserves casing and benign headers, does not mutate the live object, and the recorded entry is redacted through the real tracker path. Fully typed with no suppressions and 100% per-file coverage. No violations found. Recommend merge.
Smoke test script (throwaway, not committed)
cliffhall commentedon Jul 2, 2026
🔗 Implemented by PR #1585. Its code is already integrated into the Wave 1 rollup branch
1579-wave-1(see rollup PR #1601 / verification #1600). #1585 currently targetsv2/mainand will auto-show as Merged once the rollup lands inv2/main. The Development panel doesn't link it now because the closing keyword is on a non-default base branch.feat(core): redact sensitive headers in the fetch log (closes modelco…