83 lines
4.9 KiB
TypeScript
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" });
|
|
});
|
|
});
|