119 lines
3.9 KiB
TypeScript
119 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildCsp, inspectCsp, inspectHeaders } from "../../src/network/http";
|
|
import { lookupMime } from "../../src/network/mime";
|
|
|
|
describe("HTTP helpers", () => {
|
|
it("parses duplicates and reports missing security headers", () => {
|
|
const result = inspectHeaders(
|
|
"Content-Type: text/html\nSet-Cookie: a=1\nSet-Cookie: b=2",
|
|
);
|
|
expect(result.duplicates).toContain("set-cookie");
|
|
expect(result.findings).toContain(
|
|
"No Content-Security-Policy header is present.",
|
|
);
|
|
});
|
|
|
|
it("rejects folded headers", () => {
|
|
expect(() => inspectHeaders("X-Test: one\n two")).toThrow(/folded/u);
|
|
});
|
|
|
|
it("builds a restrictive CSP and makes dangerous sources visible", () => {
|
|
const result = buildCsp({
|
|
defaultSrc: "'self'",
|
|
scriptSrc: "'self' 'unsafe-eval'",
|
|
styleSrc: "'self'",
|
|
imgSrc: "'self' data:",
|
|
connectSrc: "'self'",
|
|
workerSrc: "'self' blob:",
|
|
});
|
|
expect(result.policy).toContain("object-src 'none'");
|
|
expect(result.warnings.join(" ")).toMatch(/unsafe-eval/u);
|
|
});
|
|
|
|
it("analyses deployed CSP, cookies, HSTS and framing defenses", () => {
|
|
const result = inspectHeaders(
|
|
"Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'\n" +
|
|
"Strict-Transport-Security: max-age=60\n" +
|
|
"Set-Cookie: session=abc; SameSite=None\n" +
|
|
"X-Content-Type-Options: nosniff\n" +
|
|
"Referrer-Policy: strict-origin",
|
|
);
|
|
expect(result.securityFindings.map((finding) => finding.code)).toEqual(
|
|
expect.arrayContaining([
|
|
"csp-unsafe-eval",
|
|
"hsts-short",
|
|
"cookie-secure",
|
|
"cookie-http-only",
|
|
"cookie-none-insecure",
|
|
]),
|
|
);
|
|
expect(result.csp?.directives).toContainEqual({
|
|
name: "object-src",
|
|
values: ["'none'"],
|
|
});
|
|
});
|
|
|
|
it("identifies duplicate and dangerous CSP directives", () => {
|
|
const result = inspectCsp(
|
|
"default-src *; script-src data:; script-src 'self'",
|
|
);
|
|
expect(result.findings.map((finding) => finding.code)).toEqual(
|
|
expect.arrayContaining([
|
|
"csp-wildcard",
|
|
"csp-script-data",
|
|
"csp-duplicate-directive",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("analyses every enforcing CSP field rather than silently dropping later fields", () => {
|
|
const result = inspectHeaders(
|
|
"Content-Security-Policy: default-src 'self'; object-src 'none'\n" +
|
|
"Content-Security-Policy: script-src 'unsafe-eval'; object-src 'none'",
|
|
);
|
|
expect(result.cspFields).toHaveLength(2);
|
|
expect(result.securityFindings).toContainEqual(
|
|
expect.objectContaining({
|
|
code: "csp-unsafe-eval",
|
|
message: expect.stringContaining("CSP field 2"),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("uses the first duplicate directive and validates X-Frame-Options", () => {
|
|
const csp = inspectCsp(
|
|
"default-src 'self'; object-src *; object-src 'none'",
|
|
);
|
|
expect(csp.findings.map((finding) => finding.code)).toContain(
|
|
"csp-object-src-open",
|
|
);
|
|
const headers = inspectHeaders(
|
|
"X-Frame-Options: ALLOW-FROM https://example.test",
|
|
);
|
|
expect(headers.securityFindings.map((finding) => finding.code)).toContain(
|
|
"x-frame-options-invalid",
|
|
);
|
|
});
|
|
|
|
it("does not apply sources from ignored duplicate CSP directives", () => {
|
|
const csp = inspectCsp(
|
|
"default-src 'self'; script-src 'self'; script-src 'unsafe-eval' data:",
|
|
);
|
|
expect(csp.findings.map((finding) => finding.code)).not.toContain(
|
|
"csp-unsafe-eval",
|
|
);
|
|
expect(csp.findings.map((finding) => finding.code)).toContain(
|
|
"csp-duplicate-directive",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("MIME lookup", () => {
|
|
it("finds types by extension or media type", () => {
|
|
expect(lookupMime(".wasm")).toEqual([
|
|
{ extension: "wasm", mime: "application/wasm" },
|
|
]);
|
|
expect(lookupMime("epub")[0]?.mime).toBe("application/epub+zip");
|
|
});
|
|
});
|