Files

83 lines
4.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { analyzeHeaders, frameProtectionStatus, isNosniff, parseCsp, parseFrameProtection, parseHsts, parseReferrerPolicy, referrerPolicyStatus } from "./headers";
describe("security header parsing", () => {
it("distinguishes missing, disabled, weak and strong HSTS", () => {
expect(parseHsts(null).status).toBe("missing");
expect(parseHsts("max-age=0").status).toBe("disabled");
expect(parseHsts("max-age=3600").status).toBe("weak");
expect(parseHsts("max-age=31536000; includeSubDomains; preload")).toMatchObject({
status: "strong",
maxAge: 31_536_000,
includeSubDomains: true,
preload: true,
});
expect(parseHsts("max-age=abc").status).toBe("invalid");
expect(parseHsts("max-age=10; max-age=20").status).toBe("invalid");
expect(parseHsts("max-age=31536000; includeSubDomains=true").status).toBe("invalid");
});
it("evaluates CSP sources in their effective directive context", () => {
expect(parseCsp(null).status).toBe("missing");
expect(parseCsp("default-src *").status).toBe("weak");
expect(parseCsp("default-src https://*").wildcard).toBe(true);
expect(parseCsp("default-src https:").wildcard).toBe(true);
expect(parseCsp("default-src 'self'; script-src 'self' 'unsafe-eval'").unsafeEval).toBe(true);
expect(parseCsp("default-src 'self'; script-src 'self' 'unsafe-inline'").status).toBe("weak");
expect(parseCsp("default-src 'self'; script-src data:; object-src 'none'; base-uri 'self'").status).toBe("weak");
expect(parseCsp("default-src 'self'; img-src data: blob:; object-src 'none'; base-uri 'self'").status).toBe("strong");
expect(parseCsp("default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'").status).toBe("strong");
});
it("keeps only the first duplicate CSP directive as browsers do", () => {
const csp = parseCsp("default-src 'self'; object-src 'none'; base-uri 'self'; script-src 'self'; script-src 'unsafe-eval'");
expect(csp.directives["script-src"]).toEqual(["'self'"]);
expect(csp.duplicates).toEqual(["script-src"]);
expect(csp.unsafeEval).toBe(false);
expect(csp.status).toBe("strong");
});
it("evaluates value semantics and modern frame protection", () => {
expect(isNosniff("NoSniff")).toBe(true);
expect(isNosniff("text/plain")).toBe(false);
expect(referrerPolicyStatus("strict-origin-when-cross-origin")).toBe("good");
expect(referrerPolicyStatus("unsafe-url")).toBe("weak");
expect(referrerPolicyStatus("not-a-policy")).toBe("invalid");
expect(frameProtectionStatus({ "content-security-policy": "default-src 'self'; frame-ancestors 'none'" })).toBe("strong");
expect(frameProtectionStatus({ "x-frame-options": "SAMEORIGIN" })).toBe("good");
expect(frameProtectionStatus({ "x-frame-options": "ALLOW-FROM https://example.com" })).toBe("invalid");
expect(frameProtectionStatus({})).toBe("missing");
});
it("uses the last recognized Referrer-Policy fallback value", () => {
expect(parseReferrerPolicy("no-referrer, strict-origin-when-cross-origin")).toMatchObject({ effective: "strict-origin-when-cross-origin", status: "good" });
expect(parseReferrerPolicy("future-policy, no-referrer")).toMatchObject({ effective: "no-referrer", status: "strong" });
expect(parseReferrerPolicy("strict-origin, future-policy")).toMatchObject({ effective: "strict-origin", status: "good" });
expect(parseReferrerPolicy(" unsafe-url , unknown ")).toMatchObject({ effective: "unsafe-url", status: "weak" });
expect(parseReferrerPolicy("future-policy")).toMatchObject({ effective: null, status: "invalid" });
});
it("distinguishes broad and restrictive frame-ancestor policies", () => {
expect(parseFrameProtection({ "content-security-policy": "frame-ancestors 'self'" }).status).toBe("strong");
expect(parseFrameProtection({ "content-security-policy": "frame-ancestors https://portal.example" }).status).toBe("good");
expect(parseFrameProtection({ "content-security-policy": "frame-ancestors https:" }).status).toBe("weak");
expect(parseFrameProtection({ "content-security-policy": "frame-ancestors *" }).status).toBe("weak");
expect(parseFrameProtection({ "content-security-policy": "frame-ancestors 'none' https://portal.example" }).status).toBe("invalid");
});
it("returns a complete normalized posture object", () => {
const posture = analyzeHeaders({
"strict-transport-security": "max-age=31536000",
"content-security-policy": "default-src 'self'; object-src 'none'; base-uri 'self'",
"x-content-type-options": "nosniff",
"referrer-policy": "no-referrer",
"x-frame-options": "DENY",
});
expect(posture.hsts.status).toBe("strong");
expect(posture.csp.status).toBe("strong");
expect(posture.nosniff.status).toBe("strong");
expect(posture.frameProtection.status).toBe("strong");
expect(posture.aggregate).toMatchObject({ healthy: 5, attention: 0, label: "5 / 5 controls healthy" });
});
});