Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
96 lines
3.7 KiB
TypeScript
96 lines
3.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { contentSecurityPolicy, resolveApiOrigin } from "./csp.ts";
|
|
|
|
describe("resolveApiOrigin", () => {
|
|
it("returns null for empty or same-origin API URL", () => {
|
|
assert.equal(resolveApiOrigin("", "http://localhost:5174"), null);
|
|
assert.equal(resolveApiOrigin("http://localhost:5174", "http://localhost:5174"), null);
|
|
assert.equal(resolveApiOrigin("http://localhost:5174/", "http://localhost:5174"), null);
|
|
});
|
|
|
|
it("returns cross-origin API origin", () => {
|
|
assert.equal(
|
|
resolveApiOrigin("http://localhost:8080", "http://localhost:5174"),
|
|
"http://localhost:8080"
|
|
);
|
|
assert.equal(
|
|
resolveApiOrigin("https://api.example.com/v1", "https://app.example.com"),
|
|
"https://api.example.com"
|
|
);
|
|
});
|
|
|
|
it("returns null for invalid URLs", () => {
|
|
assert.equal(resolveApiOrigin("not a url", "http://localhost:5174"), null);
|
|
});
|
|
});
|
|
|
|
describe("contentSecurityPolicy", () => {
|
|
it("production policy omits Google hosts when GTM id is unset", () => {
|
|
const csp = contentSecurityPolicy({ dev: false });
|
|
assert.match(csp, /default-src 'self'/);
|
|
assert.match(csp, /frame-ancestors 'none'/);
|
|
assert.match(csp, /object-src 'none'/);
|
|
assert.match(csp, /script-src 'self' 'unsafe-inline'/);
|
|
assert.doesNotMatch(csp, /googletagmanager\.com/);
|
|
assert.doesNotMatch(csp, /google-analytics\.com/);
|
|
assert.doesNotMatch(csp, /analytics\.google\.com/);
|
|
assert.doesNotMatch(csp, /'unsafe-eval'/);
|
|
assert.doesNotMatch(csp, /\bws:/);
|
|
assert.match(csp, /connect-src 'self' blob:/);
|
|
assert.match(csp, /font-src 'self' data:/);
|
|
assert.doesNotMatch(csp, /scalar\.com/);
|
|
});
|
|
|
|
it("production policy allowlists Google hosts when GTM id is valid", () => {
|
|
const csp = contentSecurityPolicy({ dev: false, gtmId: "GTM-ABC123" });
|
|
assert.match(csp, /script-src 'self' 'unsafe-inline' https:\/\/www\.googletagmanager\.com/);
|
|
assert.match(csp, /https:\/\/www\.google-analytics\.com/);
|
|
assert.match(csp, /frame-src 'self' https:\/\/www\.googletagmanager\.com/);
|
|
assert.match(csp, /connect-src 'self' blob: https:\/\/www\.googletagmanager\.com/);
|
|
});
|
|
|
|
it("invalid or empty gtmId does not allowlist Google hosts", () => {
|
|
for (const gtmId of ["", " ", "G-XXXX", "gtm-bad!", null, undefined]) {
|
|
const csp = contentSecurityPolicy({ dev: false, gtmId });
|
|
assert.doesNotMatch(csp, /googletagmanager\.com/);
|
|
assert.doesNotMatch(csp, /google-analytics\.com/);
|
|
}
|
|
});
|
|
|
|
it("production connect-src includes cross-origin API without GTM hosts by default", () => {
|
|
const csp = contentSecurityPolicy({
|
|
dev: false,
|
|
apiOrigin: "http://localhost:8080"
|
|
});
|
|
assert.match(csp, /connect-src 'self' blob: http:\/\/localhost:8080/);
|
|
assert.doesNotMatch(csp, /googletagmanager\.com/);
|
|
assert.doesNotMatch(csp, /scalar\.com/);
|
|
});
|
|
|
|
it("production connect-src includes API and GTM hosts when gtmId is valid", () => {
|
|
const csp = contentSecurityPolicy({
|
|
dev: false,
|
|
apiOrigin: "http://localhost:8080",
|
|
gtmId: "GTM-ABC123"
|
|
});
|
|
assert.match(
|
|
csp,
|
|
/connect-src 'self' blob: https:\/\/www\.googletagmanager\.com .*http:\/\/localhost:8080/
|
|
);
|
|
assert.doesNotMatch(csp, /scalar\.com/);
|
|
});
|
|
|
|
it("development policy allows Vite HMR eval and websockets", () => {
|
|
const csp = contentSecurityPolicy({ dev: true });
|
|
assert.match(csp, /'unsafe-eval'/);
|
|
assert.match(csp, /\bws:/);
|
|
assert.match(csp, /\bwss:/);
|
|
assert.match(csp, /http:\/\/localhost:\*/);
|
|
assert.match(csp, /http:\/\/127\.0\.0\.1:\*/);
|
|
assert.match(csp, /connect-src 'self' blob:/);
|
|
assert.match(csp, /font-src 'self' data:/);
|
|
assert.doesNotMatch(csp, /googletagmanager\.com/);
|
|
assert.doesNotMatch(csp, /scalar\.com/);
|
|
});
|
|
}); |