Skip to content

fix(extension-ingest): restrict credentialed CORS to the app's own hosts - #68

Merged
polylane[bot] merged 1 commit into
mainfrom
polylane/autofix/7wlcafz6r2n9
Sep 21, 2026
Merged

polylane[bot] merged 1 commit into
mainfrom
polylane/autofix/7wlcafz6r2n9

Conversation

@polylane

@polylane polylane Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Fixes: cache-app: any *.cachd.app origin can read a user's extension ingest token (docs.cachd.app is third-party hosted)

The endpoint that returns a user's extension ingest token granted credentialed cross-origin access to any single-label subdomain of cachd.app. Because docs.cachd.app and preview.cachd.app are served by third parties, a page on one of them could make a credentialed request to the app and read the signed-in user's ingest token, which permits writing library items into that account. Only the app's own hosts now receive that response.

flowchart LR
  A["Third-party page on docs.cachd.app"] --> B["credentialed fetch to www.cachd.app token endpoint"]
  B --> C["wildcard allowlist matches any cachd.app label"]
  C --> D["Allow-Origin + Allow-Credentials you"]
  D --> E["page reads signed-in user's ingest bearer token"]
  F["fix: explicit host set"] -.replaces.-> C
Loading

What caused this

Affected: acc_0b563dc42001x121b0ek7bgc

Why this fix

The credentialed CORS decision was a wildcard over cachd.app labels: TRUSTED_CACHE_WEB_ORIGIN_PATTERNS matched any [a-z0-9-]+ subdomain, and extensionTokenCorsHeaders echoed it with Access-Control-Allow-Credentials: true. The zone's wildcard DNS and cert make every label resolve and TLS-valid, and two are delegated to third parties today (docs CNAME to Mintlify returns 200; preview CNAME to unkey-dns.com). Unassigned labels do not serve the app (test/api probes returned 404), so the allowlist was wider than the hosts the app actually serves.

The fix replaces the label wildcard with an explicit first-party origin set (https://cachd.app, https://www.cachd.app, plus localhost for development). The extension reads the token from window.location.origin and posts ingest items from the extension's own origin, so only the app's hosts and chrome-extension origins need to be in the allowlist; the check for chrome-extension origins is unchanged. docs.cachd.app, preview.cachd.app, and any other delegated label now receive no allow-origin header, so the browser blocks the response read.

The origin policy moved into lib/integrations/extension-ingest/origins.ts so the decision is a pure, testable unit; the CORS header construction stays in the route. The regression test asserts the two app hosts and localhost are accepted, and that the third-party and unassigned subdomains and lookalikes are rejected.

The extension's own *.cachd.app match patterns are a separate client-side trust assumption and are left unchanged; they are not the credentialed read path and cannot produce the token from a third-party origin.

3 files changed (+82/-18)
  • lib/integrations/extension-ingest/origins.test.ts: added, +50/-0
  • lib/integrations/extension-ingest/origins.ts: added, +27/-0
  • lib/integrations/extension-ingest/route.ts: modified, +5/-18

View thread View autofix


Generated by Polylane. You can ask follow-ups by mentioning @polylane in a comment.

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot added polylane severity:medium Polylane autofix severity: medium labels Sep 21, 2026
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 374b8091-4f37-444b-8607-c458b8d603eb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Sep 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cache-app Ready Ready Preview Sep 21, 2026 4:38am UTC

@polylane

polylane Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Tip

Expected to resolve the linked issue.

Linked issue: iss_0c23c2a9800103uy2zijkxul — any *.cachd.app origin can read a user's extension ingest token (docs.cachd.app is third-party hosted)

The token endpoint's only callers fetch it same-origin from window.location.origin (extensions/cache-app/src/contents/cache-site.ts:33, src/page-world/cache-site-main.ts:6), so dropping the *.cachd.app wildcard costs no first-party host a credentialed read. Vercel observability returned no request or error series for the project to attach (available:false — no Observability Plus).

View the full analysis →

Also considered · 2 refuted
  • Refuted · Narrowed CORS allowlist strips credentialed access from a first-party host the extension uses · Every call site of /api/user/extension-ingest-token in the repository is same-origin: the content script and the page-world bridge both fetch ${window.location.origin}/api/user/extension-ingest-token (extensions/cache-app/src/contents/cache-site.ts:33, src/page-world/cache-site-main.ts:6), so no Origin-bearing cross-origin request is made and Access-Control-Allow-Origin is irrelevant to them; the extension's configured app origin is hardcoded to https://cachd.app (extensions/cache-app/lib/runtime.ts:73).
  • Refuted · Production vercel.app aliases lose credentialed CORS on the token endpoint · The aliases that point at the production deployment (cache-app-nine.vercel.app, cache-app-git-main-gilberts-projects-b97faf14.vercel.app) are not matched by the extension's content-script patterns, which are https://cachd.app/* and https://.cachd.app/ (extensions/cache-app/src/contents/cache-site.ts:6-8, src/background.ts:39); and where the bridge does run it fetches its own origin, a same-origin request that needs no CORS headers.
Analysed against 1 Project and 1 repository

View in Polylane Disable reviews

Polylane analysed 9103c4a for production impact. You can ask follow-ups by mentioning @polylane in a comment.

Did this help? React 👍 or 👎 so the next review is sharper.

@polylane
polylane Bot merged commit e7614ac into main Sep 21, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview 9103c4ad Deployed Sep 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

polylane severity:medium Polylane autofix severity: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant