Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions lib/integrations/extension-ingest/origins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, test } from "bun:test";
import {
isChromeExtensionOrigin,
isTrustedCacheWebOrigin,
} from "@/lib/integrations/extension-ingest/origins";

describe("isTrustedCacheWebOrigin", () => {
test("accepts the app's own hosts", () => {
expect(isTrustedCacheWebOrigin("https://cachd.app")).toBe(true);
expect(isTrustedCacheWebOrigin("https://www.cachd.app")).toBe(true);
});

test("accepts localhost on any port", () => {
expect(isTrustedCacheWebOrigin("http://localhost:3000")).toBe(true);
expect(isTrustedCacheWebOrigin("http://localhost:8080")).toBe(true);
});

test("rejects cachd.app subdomains served by third parties", () => {
expect(isTrustedCacheWebOrigin("https://docs.cachd.app")).toBe(false);
expect(isTrustedCacheWebOrigin("https://preview.cachd.app")).toBe(
false
);
});

test("rejects unassigned cachd.app labels and lookalikes", () => {
expect(isTrustedCacheWebOrigin("https://api.cachd.app")).toBe(false);
expect(isTrustedCacheWebOrigin("https://cachd.app.evil.com")).toBe(
false
);
expect(isTrustedCacheWebOrigin("https://evilcachd.app")).toBe(false);
expect(isTrustedCacheWebOrigin("http://cachd.app")).toBe(false);
});
});

describe("isChromeExtensionOrigin", () => {
test("accepts a chrome extension origin", () => {
expect(
isChromeExtensionOrigin(
"chrome-extension://abcdefghijklmnopabcdefghijklmnop"
)
).toBe(true);
});

test("rejects other origins", () => {
expect(isChromeExtensionOrigin("https://cachd.app")).toBe(false);
expect(isChromeExtensionOrigin("chrome-extension://not-an-id")).toBe(
false
);
});
});
27 changes: 27 additions & 0 deletions lib/integrations/extension-ingest/origins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* The app's own web origins. The extension reads the ingest token from the
* page origin, so only these hosts are first-party for CORS. A wildcard once
* trusted every cachd.app label, including subdomains delegated to third-party
* services, which let those hosts read a signed-in user's ingest token.
*/
const TRUSTED_CACHE_WEB_ORIGINS = new Set([
"https://cachd.app",
"https://www.cachd.app",
]);

/** Local development serves the app on arbitrary localhost ports. */
const LOCALHOST_ORIGIN_PATTERN = /^http:\/\/localhost:\d+$/;

/** Chrome extension service-worker origins. Extension ids are `[a-p]{32}`. */
const CHROME_EXTENSION_ORIGIN_PATTERN = /^chrome-extension:\/\/[a-p]{32}$/;

export function isTrustedCacheWebOrigin(origin: string): boolean {
return (
TRUSTED_CACHE_WEB_ORIGINS.has(origin) ||
LOCALHOST_ORIGIN_PATTERN.test(origin)
);
}

export function isChromeExtensionOrigin(origin: string): boolean {
return CHROME_EXTENSION_ORIGIN_PATTERN.test(origin);
}
23 changes: 5 additions & 18 deletions lib/integrations/extension-ingest/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import "server-only";

import * as z from "zod";
import { createLogger } from "@/lib/common/logs/console/logger";
import {
isChromeExtensionOrigin,
isTrustedCacheWebOrigin,
} from "@/lib/integrations/extension-ingest/origins";
import { resolveExtensionIngestUserId } from "@/lib/integrations/extension-ingest/service";

const log = createLogger("integrations:extension-ingest");
Expand All @@ -22,20 +26,6 @@ const TOKEN_CORS_HEADERS = {
"Access-Control-Max-Age": "86400",
} as const;

const TRUSTED_CACHE_WEB_ORIGIN_PATTERNS = [
/^https:\/\/cachd\.app$/,
/^https:\/\/[a-z0-9-]+\.cachd\.app$/,
/^http:\/\/localhost:\d+$/,
];

const CHROME_EXTENSION_ORIGIN_PATTERN = /^chrome-extension:\/\/[a-p]{32}$/;

function isTrustedCacheWebOrigin(origin: string): boolean {
return TRUSTED_CACHE_WEB_ORIGIN_PATTERNS.some((pattern) =>
pattern.test(origin)
);
}

function trustedOriginForRequest(
request: Request,
config: { allowChromeExtensionOrigin: boolean }
Expand All @@ -47,10 +37,7 @@ function trustedOriginForRequest(
if (isTrustedCacheWebOrigin(origin)) {
return origin;
}
if (
config.allowChromeExtensionOrigin &&
CHROME_EXTENSION_ORIGIN_PATTERN.test(origin)
) {
if (config.allowChromeExtensionOrigin && isChromeExtensionOrigin(origin)) {
return origin;
}
return null;
Expand Down