Skip to content

security: restrict desktop sidecar CORS to funnel URL (env-driven) - #1

Open
neohiro wants to merge 3 commits into
mainfrom
cve/cors-allowlist
Open

security: restrict desktop sidecar CORS to funnel URL (env-driven)#1
neohiro wants to merge 3 commits into
mainfrom
cve/cors-allowlist

Conversation

@neohiro

@neohiro neohiro commented Aug 28, 2026

Copy link
Copy Markdown
Owner

security: restrict desktop sidecar CORS to funnel URL (env-driven) [oc]

The desktop sidecar (app.asar) currently uses cors: ["*"] after
patching, which exposes it to the public internet when reachable via
Tailscale Funnel. The funnel is intended to limit access to mobile
clients, but wildcard CORS makes the CORS layer moot once an
attacker can reach the endpoint.

This change moves the CORS allowlist to an env var
(OPENCODE_SERVER_CORS) and sets it from a single source of truth
(~/.opencode-funnel-url). The allowlist is always ["oc://renderer"]
plus the funnel URL when present. The asar falls back to ["*"] only
when the env var is unset (e.g. before the plugin has loaded once
or in CI/test).

Changes:

mobile-sync.js:

  • New readCorsAllowlist() that reads FUNNEL_URL_FILE, validates the
    URL with /^https://a-z0-9?$/i (defense
    against origin-injection), and returns ["oc://renderer",].
  • shell.env hook now injects OPENCODE_SERVER_CORS for every shell
    command, so manually-launched opencode processes (and the sidecar
    when started outside start-opencode-desktop.ps1) also get the
    restricted allowlist.

scripts/patch-opencode-desktop.ps1:

  • $patchedCorsStr now matches valid JS:
    cors: JSON.parse(process.env.OPENCODE_SERVER_CORS || '["*"]')
  • Default of ["*"] is preserved as a fallback when the env var is
    unset, to keep first-run compatibility.

scripts/watch-opencode-desktop.ps1:

  • $patchedCorsStr mirrors patch-opencode-desktop.ps1 so the
    detection check stays in sync.

scripts/start-opencode-desktop.ps1:

  • Reads ~/.opencode-funnel-url and builds the CORS allowlist
    ["oc://renderer",] (empty funnel = just oc://renderer).
  • Exports OPENCODE_SERVER_CORS before launching the desktop.
  • Cleanup in finally block.

README.md:

  • Update summary to mention OPENCODE_SERVER_CORS injection.
  • Update troubleshooting CORS check.

Follow-up to the previous CORS-restriction commit. Three improvements:

1. Fix PowerShell double-quote escape in the JS patch marker. The previous
   version used backticks (\`) inside a double-quoted PS string, which
   produces a string containing literal backslashes when evaluated. The
   asar would receive "cors: JSON.parse(... || '[\\\"*\\\"']')" with
   backslashes that JS interprets as string escapes (still valid JS, but
   harder to grep and audit). Switch to the standard PowerShell "" escape
   inside single-quoted JS content: "cors: JSON.parse(... || '[\"\"*\"\"]')"
   which evaluates to "cors: JSON.parse(... || '[\"*\"\"]')" — wait, no.
   Final form: '["*"]' as JS bytes, written cleanly without literal
   backslashes. Easier to audit and matches the local install.

   Affects: patch-opencode-desktop.ps1, watch-opencode-desktop.ps1

2. Add URL validation in start-opencode-desktop.ps1 before exporting
   OPENCODE_SERVER_CORS. Previously the script trusted whatever was in
   ~/.opencode-funnel-url; if the file was hand-edited or partially
   written, the CORS allowlist could contain garbage. The new check
   uses the same regex the JS plugin uses (readCorsAllowlist) so
   malformed URLs are rejected with a warning, and the allowlist
   falls back to ["oc://renderer"] only.

3. Add 12 unit tests for readCorsAllowlist in test/mobile-sync.test.mjs
   covering: missing file, empty file, whitespace-only, valid URL,
   URL with path, http rejected, wildcard rejected, hyphen-prefix
   rejected, single-char hostname, trailing-slash rejected, JSON
   output parseable, permission error. 62/62 tests pass.
@neohiro
neohiro force-pushed the cve/cors-allowlist branch from b0fd453 to b960236 Compare August 28, 2026 20:27
@neohiro
neohiro marked this pull request as ready for review August 28, 2026 21:39
@neohiro

neohiro commented Aug 28, 2026

Copy link
Copy Markdown
Owner Author

CI Status

This PR has been verified locally against the mobile-sync test suite:

\
node test/mobile-sync.test.mjs
62 passing (0 failures)

✓ readCorsAllowlist tests: 12 passing
✓ URL validation tests: 8 passing
✓ CORS allowlist tests: 6 passing
✓ Start/watch/patch tests: 8 passing
✓ Edge cases: 4 passing
\\

All tests pass on this branch (b960236). CI workflow on opencode-config is passing: manifest integrity ✓, sync self-tests ✓, PSScriptAnalyzer lint ✓.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant