security: relayUrl validation, remove dead pending code, fix about:blank#12
Conversation
- getRelayUrl(): validate wss:// scheme — invalid storage value falls back to DEFAULT_RELAY_URL with console.warn; prevents token exfil to attacker relay if chrome.storage is compromised - options.js save(): reject non-wss:// URLs before writing to storage - Remove requestFromRelay, pending map, and all usage sites — never called; dead code introduced by PR #11 adding a timeout to an uncalled function - Target.createTarget: add about: to allowed URL schemes alongside http/https fixes regression from PR #10 where about:blank (DevTools default) was blocked
DeetGates
left a comment
There was a problem hiding this comment.
Architect/security review: requesting changes.
CI: lint is green. I checked PR review comments, including chatgpt-codex-connector[bot]; there are no inline review comments on this PR.
Blocker: invalid stored relayUrl should fail closed/loud instead of silently falling back to DEFAULT_RELAY_URL. The options page now rejects bad saves, which is good, but getRelayUrl() handles corrupted/externally-written storage by warning in the console and connecting to the default public relay. That can violate operator intent and can leak the saved bearer token to a relay the operator did not configure. For a security fix, prefer: clear connection state/badge, surface an actionable error, and do not connect until the operator fixes the URL. Falling back is OK only for an empty/unset value.
SECURITY.md is directionally accurate about the extension trusting the relay, but it currently mixes a loopback trust model (Relay server (127.0.0.1:18792), "v1 single-tenant with a loopback relay") with the extension default of wss://relay.artificeia.mx. Before multi-tenant/public relay, it should explicitly separate local plugin relay vs hosted relay assumptions and say that any relay the extension connects to is trusted with full attached-tab CDP authority unless/until the extension has its own CDP allowlist.
Also consider documenting token scope/handling in SECURITY.md: wss:// protects against plaintext token exposure in transit, but it does not make a malicious or unintended relay safe.
DeetGates
left a comment
There was a problem hiding this comment.
Re-review: requesting changes.
CI: lint is green. Local gate: node --check background.js and node --check options.js pass. I rechecked inline PR comments, including chatgpt-codex-connector[bot]; none found.
The previous relayUrl fallback finding is fixed: invalid stored URLs now throw instead of silently connecting to the default relay, so no unintended token leak there.
Remaining blocker: SECURITY.md still misdescribes the local relay perimeter. It documents Relay server (127.0.0.1:18792) → wss:// token-authenticated and says the token in transit never leaves the machine, but the plugin relay is a plain ws://127.0.0.1:18792 WebSocket server with no TLS (the plugin README still tells users to configure ws://127.0.0.1:18792). Meanwhile this extension now rejects every non-wss: URL, so either local plugin relay is no longer supported directly, or loopback ws://127.0.0.1 needs an explicit exception. The security doc needs to be internally consistent and consistent with the actual extension behavior before approval.
Suggested fix: choose and document one model clearly:
- Hosted/TLS-only extension: say direct local
ws://plugin relay is unsupported unless fronted by TLS/reverse proxy, and update the local-relay diagram/docs accordingly; or - Allow loopback
ws://127.0.0.1/localhostonly, while still rejecting non-loopback plaintext URLs.
The hosted/public relay trust model and extension-trusts-relay warning otherwise look directionally right.
DeetGates
left a comment
There was a problem hiding this comment.
Re-review: requesting changes.
CI: lint is green. Local gate: node --check background.js and node --check options.js pass. I rechecked inline PR comments, including chatgpt-codex-connector[bot]; none found.
The chosen policy is right: wss:// for remote, plaintext ws:// only for loopback. But two small consistency issues remain:
-
IPv6 loopback is documented as allowed but currently rejected. In JS
new URL('ws://[::1]:18792').hostnamereturns"[::1]", not"::1", so bothbackground.jsandoptions.jsreject the documentedws://[::1]form. Fix by accepting[::1]too, or remove::1from SECURITY.md. -
SECURITY.md's local relay diagram still says
↓ wss:// token-authenticatedfor127.0.0.1:18792, but the local plugin relay is plaintextws://. That was the prior blocker. The new validation section is correct, but the local diagram must sayws:// token-authenticated(or explicitly note plaintext loopback exception) so the document is internally consistent.
Once those two are fixed, I expect this PR to be approvable.
DeetGates
left a comment
There was a problem hiding this comment.
Re-review: approved.
CI: lint is green. Local gates:
node --check background.jsnode --check options.js- URL policy smoke:
wss://remote allowed;ws://127.0.0.1,ws://localhost, andws://[::1]allowed; non-loopbackws://andhttp://rejected.
I rechecked inline PR comments, including chatgpt-codex-connector[bot]; none found.
The previous blockers are resolved: IPv6 loopback now matches JS URL hostname behavior with [::1], and SECURITY.md now labels the local relay path as ws:// plaintext loopback while keeping hosted relay as wss://. Good to merge from my side.
Summary
getRelayUrl()validates wss:// scheme before connecting. Invalid storage value falls back to DEFAULT_RELAY_URL with console.warn — prevents token exfil to attacker relay if chrome.storage is compromised.save()validates wss:// scheme before writing. Shows error in UI instead of persisting bad URL.requestFromRelay,pendingmap, and all usage sites (onRelayClosed drain, onRelayMessage dispatch). Function was never called; PR Clear pending map entries after 35s timeout #11 added a timeout to an uncalled function.Target.createTargetaddsabout:to allowed URL schemes alongside http/https. PR Security: URL scheme allowlist for Target.createTarget; catch unhandled sendToRelay throws #10's allowlist blockedabout:blank(DevTools default for new tabs). Also restoresabout:blankas the fallback whenparams.urlis absent (PR Security: URL scheme allowlist for Target.createTarget; catch unhandled sendToRelay throws #10 had switched to empty string, which threw Invalid URL).Test plan
http://URL in extension options → save rejected with error messagewss://127.0.0.1:18792→ saves and connects normallyTarget.createTargetwith no URL → createsabout:blanktab and attachesTarget.createTargetwithjavascript:alert(1)→ throws "URL scheme not allowed"requestFromRelayorpendingreferences remain