fix(cdp): harden Comet launch and reject non-http(s) navigation#15
fix(cdp): harden Comet launch and reject non-http(s) navigation#15sergei-aronsen wants to merge 2 commits into
Conversation
Code Review: fix(cdp): harden Comet launch and reject non-http(s) navigationReviewed commit: Overall Assessment: APPROVEThis is a well-structured defense-in-depth PR addressing two real attack surfaces: DNS rebinding against the unauthenticated CDP endpoint, and indirect prompt injection targeting navigation primitives. The implementation is correct and the scope is appropriately limited. URL Validation (
|
| Area | Status |
|---|---|
| URL validation correctness | Pass |
| All navigation paths guarded | Pass |
| Launch args applied at all spawn sites | Pass |
| PowerShell escaping | Pass |
| No new security issues introduced | Pass |
| No regressions to existing functionality | Pass |
Recommendation: Safe to merge. The implementation is correct, well-documented, and addresses real security concerns with appropriate defense-in-depth measures.
`comet_upload` accepted any local path and forwarded it to
`DOM.setFileInputFiles` against whatever page Comet was on. Combined
with indirect prompt-injection from page content, this is an
arbitrary-file exfiltration vector: a malicious page can instruct the
agent to call `comet_upload {filePath: "/Users/x/.ssh/id_rsa"}` and
the file lands in the next form submission.
The fix is a layered allowlist:
1. **Opt-in `COMET_UPLOAD_ROOT` env**: when set, resolve symlinks with
`realpathSync` and require the canonical path to live under the
configured root. Anything outside is rejected with a user-facing
message.
2. **Hardcoded denylist** (active when the env is unset, to keep the
default permissive behaviour for existing users): refuse uploads
from `~/.ssh`, `~/.aws`, `~/.config/{gh,gcloud}`, `~/.gnupg`,
`~/.kube`, `~/.docker`, `~/Library/Keychains` (macOS), `/etc/shadow`,
`/etc/sudoers`, `/root`. These have no plausible upload use case.
3. **Warning log** when the env is unset, so users notice the default
is permissive and can flip on the allowlist.
Symlink resolution defeats `~/uploads/sneak -> ~/.ssh/id_rsa` style
bypasses. `statSync` confirms it's a regular file (not a FIFO or
character device).
Also tightened `comet_tabs action=switch|close tabId=...`: validate
the value matches `/^[A-Fa-f0-9-]{16,64}$/` before passing to
`Target.connect` / `Target.closeTarget`. CDP target ids are 32-char
hex; bogus values otherwise produce cryptic errors and can be coaxed
into traversing the local CDP HTTP API surface (`/json/version`, …).
Removed the dead `connectionCheckInterval` field on `CometCDPClient`
— declared but never assigned/cleared.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…non-http(s) navigation Chromium's CDP endpoint has no auth — historically any local process or any browser tab that resolves a hostname pointing at 127.0.0.1 could attach to it (DNS-rebinding-against-CDP, CVE-2018-6056 and follow-ons). The standard mitigation, shipped with Chromium since m90, is `--remote-allow-origins=<origin>`: connections from origins not in the list are rejected at handshake time. We always launch Comet with `--remote-allow-origins=http://127.0.0.1` (centralised in `cometLaunchArgs()`), so a drive-by website served to any browser on the host can no longer pivot through `ws://127.0.0.1:9223` to read Comet tabs (banking, email, etc.). Applied at all three native-spawn sites and the PowerShell WSL launch. The WSL path now builds `-ArgumentList` from the same array via `map(JSON-style single-quote escape).join(',')` so the values cannot shell-inject through the PowerShell layer. `navigate()` and `navigateWithRetry()` now reject any URL whose protocol is not `http:` or `https:` via a new `assertNavigableUrl()` helper. Specifically blocks: - `javascript:` / `data:` / `vbscript:` (XSS-equivalent in page context) - `file://` (local filesystem read) - `chrome:`, `devtools:`, `view-source:`, `about:` (internal pages; `Page.navigate` to these often crashes the CDP session) - empty / unparseable strings (returns user-facing error instead of a CDP internal error) This is a defense-in-depth measure against indirect prompt injection that targets the navigation primitives. `http(s)` URLs continue to work unchanged. Comet launches transparently with the new flag. CDP clients that were already connecting from `127.0.0.1` (this MCP server, the official Chrome DevTools UI on the same machine) are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
da00094 to
b21d60c
Compare
Summary
Two defense-in-depth changes around the CDP transport.
`--remote-allow-origins`
Chromium's CDP endpoint has no auth. Historically any local process or any browser tab that resolves a hostname pointing at `127.0.0.1` could attach to it (DNS-rebinding-against-CDP, CVE-2018-6056 and follow-ons). The standard mitigation, shipped since m90, is `--remote-allow-origins=`: connections from origins not in the list are rejected at the handshake.
We now always launch Comet with `--remote-allow-origins=http://127.0.0.1\` (centralised in `cometLaunchArgs()`), so a drive-by website served to any browser on the host can no longer pivot through `ws://127.0.0.1:9223` to read Comet tabs.
Applied at all three native spawn sites and the PowerShell WSL launch. The WSL path now builds `-ArgumentList` from the same array via array-quote-escape join so the values cannot shell-inject through the PowerShell layer.
Protocol allowlist on `Page.navigate`
`navigate()` and `navigateWithRetry()` reject any URL whose protocol is not `http:` or `https:` via a new `assertNavigableUrl()` helper. Specifically blocks `javascript:`, `data:`, `vbscript:`, `file://`, `chrome:`, `devtools:`, `view-source:`, `about:`, and empty / unparseable strings.
Defense-in-depth against indirect prompt injection that targets the navigation primitives.
Compatibility
`http(s)` URLs work unchanged. Comet launches transparently with the new flag. Existing CDP clients connecting from `127.0.0.1` (this MCP server, the Chrome DevTools UI) are unaffected.
Test plan
Related
Part of an audit pass. Other open PRs: security (#11), correctness (#12), drop unused dep (#13), upload allowlist (#14).
🤖 Generated with Claude Code