Initial curated CertScope source from fe2bbe3ca5c98bf64be26a09c6c58ee805a7f87e

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:30:09 +02:00
commit 7348faf6c0
65 files changed
+12509

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
export type HeaderValue = string | string[] | null | undefined;
export type HeaderMap = Record<string, HeaderValue>;
const DAY_SECONDS = 86_400;
const STRONG_HSTS_SECONDS = 31_536_000;
export type HeaderControlId = "hsts" | "csp" | "nosniff" | "referrer-policy" | "frame-protection";
export type HeaderControlStatus = "strong" | "good" | "weak" | "missing" | "invalid" | "unavailable";
export type HeaderControlPosture = {
id: HeaderControlId;
status: HeaderControlStatus;
reason: string;
};
export type HeaderAggregate = {
evidenceAvailable: boolean;
healthy: number;
attention: number;
unavailable: number;
total: number;
label: string;
};
function firstHeader(value: HeaderValue) {
if (Array.isArray(value)) return value.join(", ");
return typeof value === "string" ? value : null;
}
function directiveMap(value: string) {
const directives = new Map<string, string | null>();
const duplicate = new Set<string>();
for (const raw of value.split(";")) {
const part = raw.trim();
if (!part) continue;
const [name, ...rawValue] = part.split("=");
const normalizedName = name.trim().toLowerCase();
if (!/^[a-z][a-z0-9-]*$/.test(normalizedName)) return { directives, duplicate, invalid: true };
if (directives.has(normalizedName)) {
duplicate.add(normalizedName);
continue;
}
directives.set(normalizedName, rawValue.length ? rawValue.join("=").trim() : null);
}
return { directives, duplicate, invalid: false };
}
export type HstsPosture = {
status: "missing" | "invalid" | "disabled" | "weak" | "strong";
maxAge: number | null;
includeSubDomains: boolean;
preload: boolean;
reason: string;
};
export function parseHsts(value: HeaderValue): HstsPosture {
const raw = firstHeader(value);
if (!raw) return { status: "missing", maxAge: null, includeSubDomains: false, preload: false, reason: "Header is missing." };
const parsed = directiveMap(raw);
const maxAgeRaw = parsed.directives.get("max-age");
const maxAge = maxAgeRaw !== null && maxAgeRaw !== undefined && /^\d+$/.test(maxAgeRaw) ? Number(maxAgeRaw) : null;
const invalid = parsed.invalid || parsed.duplicate.has("max-age") || maxAge === null || !Number.isSafeInteger(maxAge)
|| (parsed.directives.has("includesubdomains") && parsed.directives.get("includesubdomains") !== null)
|| (parsed.directives.has("preload") && parsed.directives.get("preload") !== null);
const includeSubDomains = parsed.directives.has("includesubdomains");
const preload = parsed.directives.has("preload");
if (invalid) return { status: "invalid", maxAge, includeSubDomains, preload, reason: "max-age must be a single non-negative integer." };
if (maxAge === 0) return { status: "disabled", maxAge, includeSubDomains, preload, reason: "max-age=0 disables HSTS." };
if (maxAge < DAY_SECONDS) return { status: "weak", maxAge, includeSubDomains, preload, reason: "The policy expires in less than one day." };
if (maxAge < STRONG_HSTS_SECONDS) return { status: "weak", maxAge, includeSubDomains, preload, reason: "The policy is shorter than the recommended one-year duration." };
return { status: "strong", maxAge, includeSubDomains, preload, reason: "Long-lived HSTS policy observed." };
}
export type CspPosture = {
status: "missing" | "invalid" | "weak" | "strong";
directives: Record<string, string[]>;
duplicates: string[];
wildcard: boolean;
unsafeEval: boolean;
unsafeInline: boolean;
frameAncestors: string[];
reason: string;
};
const HIGH_RISK_DIRECTIVES = new Set(["default-src", "script-src", "script-src-elem", "object-src", "base-uri"]);
const BROAD_SOURCES = new Set(["*", "http:", "https:", "http://*", "https://*"]);
function sourceIsBroad(source: string, directive: string) {
if (BROAD_SOURCES.has(source)) return true;
return HIGH_RISK_DIRECTIVES.has(directive) && (source === "data:" || source === "blob:");
}
/**
* Parses one CSP policy. Browser CSP keeps the first occurrence of a duplicate
* directive, so later duplicates are retained only as configuration evidence.
*/
export function parseCsp(value: HeaderValue): CspPosture {
const raw = firstHeader(value);
if (!raw) return { status: "missing", directives: {}, duplicates: [], wildcard: false, unsafeEval: false, unsafeInline: false, frameAncestors: [], reason: "Header is missing." };
const directives: Record<string, string[]> = {};
const duplicates = new Set<string>();
let malformed = false;
for (const rawDirective of raw.split(";")) {
const tokens = rawDirective.trim().split(/\s+/).filter(Boolean);
if (!tokens.length) continue;
if (!/^[a-z][a-z0-9-]*$/i.test(tokens[0])) {
malformed = true;
continue;
}
const name = tokens.shift()!.toLowerCase();
if (name in directives) {
duplicates.add(name);
continue;
}
directives[name] = tokens.map((token) => token.toLowerCase());
}
const effectiveScript = directives["script-src"] ?? directives["default-src"] ?? [];
const effectiveScriptElement = directives["script-src-elem"] ?? effectiveScript;
const unsafeEval = effectiveScript.includes("'unsafe-eval'") || effectiveScriptElement.includes("'unsafe-eval'");
const unsafeInline = effectiveScript.includes("'unsafe-inline'") || effectiveScriptElement.includes("'unsafe-inline'");
const broadDirectives = Object.entries(directives).filter(([name, sources]) =>
sources.some((source) => sourceIsBroad(source, name)),
);
const wildcard = broadDirectives.length > 0;
const frameAncestors = directives["frame-ancestors"] ?? [];
const missingObjectSrc = !directives["object-src"]?.includes("'none'");
const missingBaseUri = !directives["base-uri"]?.includes("'self'") && !directives["base-uri"]?.includes("'none'");
const weak = wildcard || unsafeEval || unsafeInline || missingObjectSrc || missingBaseUri;
const reason = malformed
? "The policy contains malformed directives."
: unsafeEval
? "The effective script policy permits unsafe-eval."
: unsafeInline
? "The effective script policy permits unsafe-inline."
: wildcard
? "A high-impact directive permits broad sources."
: missingObjectSrc
? "object-src 'none' is missing."
: missingBaseUri
? "base-uri 'self' or 'none' is missing."
: "Restrictive policy observed.";
return { status: malformed ? "invalid" : weak ? "weak" : "strong", directives, duplicates: [...duplicates], wildcard, unsafeEval, unsafeInline, frameAncestors, reason };
}
export type ReferrerPolicyPosture = HeaderControlPosture & { effective: string | null };
const STRONG_REFERRER_POLICIES = new Set(["no-referrer", "same-origin"]);
const GOOD_REFERRER_POLICIES = new Set(["strict-origin", "strict-origin-when-cross-origin"]);
const WEAK_REFERRER_POLICIES = new Set(["origin", "origin-when-cross-origin", "no-referrer-when-downgrade", "unsafe-url"]);
export function parseReferrerPolicy(value: HeaderValue): ReferrerPolicyPosture {
const raw = firstHeader(value);
if (!raw?.trim()) return { id: "referrer-policy", status: "missing", effective: null, reason: "Header is missing." };
const policies = raw.split(",").map((policy) => policy.trim().toLowerCase()).filter(Boolean);
const recognized = policies.filter((policy) => STRONG_REFERRER_POLICIES.has(policy) || GOOD_REFERRER_POLICIES.has(policy) || WEAK_REFERRER_POLICIES.has(policy));
const effective = recognized.at(-1) ?? null;
if (!effective) return { id: "referrer-policy", status: "invalid", effective: null, reason: "No supported Referrer-Policy value was observed." };
if (STRONG_REFERRER_POLICIES.has(effective)) return { id: "referrer-policy", status: "strong", effective, reason: "The effective policy minimizes referrer disclosure." };
if (GOOD_REFERRER_POLICIES.has(effective)) return { id: "referrer-policy", status: "good", effective, reason: "The effective policy limits cross-origin referrer detail." };
return { id: "referrer-policy", status: "weak", effective, reason: effective === "unsafe-url" ? "The effective policy may disclose full URLs cross-origin." : "The effective policy permits more referrer detail than strict-origin policies." };
}
/** Backwards-compatible concise status for rule callers. */
export function referrerPolicyStatus(value: HeaderValue) {
return parseReferrerPolicy(value).status;
}
export type FrameProtectionPosture = HeaderControlPosture & { source: "csp" | "x-frame-options" | "none" };
function frameAncestorsPosture(csp: CspPosture): FrameProtectionPosture | null {
if (!("frame-ancestors" in csp.directives)) return null;
const sources = csp.frameAncestors;
if (!sources.length) return { id: "frame-protection", status: "invalid", source: "csp", reason: "frame-ancestors has no usable source expression." };
if (sources.includes("*")) return { id: "frame-protection", status: "weak", source: "csp", reason: "frame-ancestors permits every embedding origin." };
if (sources.some((source) => source === "http:" || source === "https:" || source === "data:" || source === "blob:" || source.includes("*"))) {
return { id: "frame-protection", status: "weak", source: "csp", reason: "frame-ancestors permits a broad scheme or wildcard origin." };
}
if (sources.includes("'none'")) return sources.length === 1
? { id: "frame-protection", status: "strong", source: "csp", reason: "frame-ancestors 'none' blocks framing." }
: { id: "frame-protection", status: "invalid", source: "csp", reason: "frame-ancestors 'none' cannot be combined with other sources." };
if (sources.includes("'self'")) return { id: "frame-protection", status: "strong", source: "csp", reason: "frame-ancestors restricts framing to the same origin." };
const explicitOrigins = sources.every((source) => /^https?:\/\/[a-z0-9.-]+(?::\d+)?$/i.test(source));
return explicitOrigins
? { id: "frame-protection", status: "good", source: "csp", reason: "frame-ancestors lists explicit embedding origins." }
: { id: "frame-protection", status: "invalid", source: "csp", reason: "frame-ancestors contains an unsupported source expression." };
}
export function parseFrameProtection(headers: HeaderMap): FrameProtectionPosture {
const csp = parseCsp(headers["content-security-policy"]);
const cspPosture = frameAncestorsPosture(csp);
if (cspPosture) return cspPosture;
const xFrame = firstHeader(headers["x-frame-options"])?.trim().toLowerCase();
if (xFrame === "deny") return { id: "frame-protection", status: "strong", source: "x-frame-options", reason: "X-Frame-Options: DENY blocks framing." };
if (xFrame === "sameorigin") return { id: "frame-protection", status: "good", source: "x-frame-options", reason: "X-Frame-Options restricts framing to the same origin." };
if (xFrame) return { id: "frame-protection", status: "invalid", source: "x-frame-options", reason: "X-Frame-Options has an unsupported value." };
return { id: "frame-protection", status: "missing", source: "none", reason: "No frame-ancestors policy or X-Frame-Options protection was observed." };
}
/** Backwards-compatible concise status for rule callers. */
export function frameProtectionStatus(headers: HeaderMap) {
return parseFrameProtection(headers).status;
}
function hstsControl(hsts: HstsPosture): HeaderControlPosture {
const status: HeaderControlStatus = hsts.status === "disabled" ? "weak" : hsts.status;
return { id: "hsts", status, reason: hsts.reason };
}
function nosniffControl(value: HeaderValue): HeaderControlPosture {
const raw = firstHeader(value);
if (!raw?.trim()) return { id: "nosniff", status: "missing", reason: "Header is missing." };
return raw.trim().toLowerCase() === "nosniff"
? { id: "nosniff", status: "strong", reason: "The required nosniff value was observed." }
: { id: "nosniff", status: "invalid", reason: "The header does not use the required nosniff value." };
}
export function isNosniff(value: HeaderValue) {
return nosniffControl(value).status === "strong";
}
function cspControl(csp: CspPosture): HeaderControlPosture {
return { id: "csp", status: csp.status, reason: csp.reason };
}
export function summarizeHeaderControls(controls: HeaderControlPosture[]): HeaderAggregate {
const healthy = controls.filter((control) => control.status === "strong" || control.status === "good").length;
const unavailable = controls.filter((control) => control.status === "unavailable").length;
const attention = controls.length - healthy - unavailable;
const evidenceAvailable = unavailable !== controls.length;
const label = !evidenceAvailable
? "Evidence unavailable"
: unavailable
? `${healthy} / ${controls.length - unavailable} controls healthy · evidence incomplete`
: attention
? `${attention} control${attention === 1 ? " needs" : "s need"} attention`
: `${healthy} / ${controls.length} controls healthy`;
return { evidenceAvailable, healthy, attention, unavailable, total: controls.length, label };
}
export function unavailableHeaderPosture() {
const controls: HeaderControlPosture[] = ["hsts", "csp", "nosniff", "referrer-policy", "frame-protection"].map((id) => ({
id: id as HeaderControlId,
status: "unavailable",
reason: "No terminal HTTPS response was available for evaluation.",
}));
return { controls, aggregate: summarizeHeaderControls(controls) };
}
export function analyzeHeaders(headers: HeaderMap) {
const hsts = parseHsts(headers["strict-transport-security"]);
const csp = parseCsp(headers["content-security-policy"]);
const nosniff = nosniffControl(headers["x-content-type-options"]);
const referrerPolicy = parseReferrerPolicy(headers["referrer-policy"]);
const frameProtection = parseFrameProtection(headers);
const controls = [hstsControl(hsts), cspControl(csp), nosniff, referrerPolicy, frameProtection];
return { hsts, csp, nosniff, referrerPolicy, frameProtection, controls, aggregate: summarizeHeaderControls(controls) };
}