modelcontextprotocol / modelcontextprotocol/inspector
Form-encoded secret masking can be defeated by an unescaped & inside the secret value itself
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 10.9k
- Forks
- 1.5k
- Avg merge
- 6h 17m
- Merged PRs (30d)
- 151
Description
Which version line?
v2 — current (@modelcontextprotocol/inspector@latest)
Which client?
Web
Inspector version
2.7.0 (git tag) — static code-review finding, not run locally
Node version
N/A — static code review, no live run performed
Operating system (and browser, for the web client)
N/A — static code review
Transport
Streamable HTTP
MCP server under inspection
N/A — this is a static code-review finding against the 2.7.0 tag source (clients/web/src/utils/maskSecrets.ts), not a live reproduction against a running MCP server.
Steps to reproduce
Found via static review of the 2.7.0 tag source, not a live run.
- maskFormBody() (clients/web/src/utils/maskSecrets.ts, lines 119-142) masks a form-urlencoded body by first calling body.split("&") (line 122), then for each resulting piece splitting on the first "=" to get a key and value, and masking the value when the key is one of the sensitive FORM_SENSITIVE_KEYS.
- A conforming form-urlencoded body always percent-encodes a literal "&" inside a value (as %26), so split("&") is normally safe. But the function trusts the wire's own encoding; nothing here validates that a value contains no raw "&".
- If a non-conforming OAuth/token endpoint returns a body where a secret value (e.g. access_token) contains a raw, un-percent-encoded "&", split("&") breaks that single value into two or more pieces mid-value.
- The prefix piece still looks like "access_token=" and gets masked. But the remaining piece(s) — the tail of the secret — have no "=" sign, so eq === -1 (line 125) and the piece is returned completely unmasked (line 125:
if (eq === -1) return pair;), landing verbatim in the displayed/masked body.
I have not exercised this against a live non-conforming server; flagging what tracing the function's logic against the 2.7.0 tag source shows.
Expected behavior
Every character of a masked secret value stays out of the displayed Network-tab body, even when the value itself contains a raw, non-percent-encoded "&" from a non-conforming server.
Actual behavior
A raw "&" inside a masked key's value defeats the masking for everything after that character: the tail of the secret is displayed as its own unmasked, no-"=" segment in the body shown in the Network tab, right next to the properly-masked prefix.
Suggested fix: a non-conforming value containing a raw "&" is already a malformed body, so no split on "&" can fully recover intent — but the current behavior (silently pass through a no-"=" segment unchanged) is the worst case, since it looks like an ordinary flag-style parameter rather than a leak. Treat a segment with no "=" sign as suspicious when it immediately follows a masked pair (mask it too, or flag the body as only partially maskable) rather than displaying it untouched.
Logs, errors, or screenshots
No response
Already prototyped a fix?
No response
Before you submit
- I searched existing issues and this is not a duplicate.
- This is not a security vulnerability report (those go through the private advisory process).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in clients/web/src/utils/maskSecrets.ts at maskFormBody(), especially the body.split("&") and no-"=" handling described in lines 119-142. Trace how a raw ampersand in a sensitive value reaches the Network-tab display. Done means the complete sensitive value is excluded from the displayed body, including this malformed-body case, with coverage for the behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100