HTTP security headers: what misconfiguration looks like in assessment data

Key takeaways
CSP headers that include unsafe-inline in script-src provide no meaningful protection against inline XSS injection, yet this configuration appears in the majority of assessed environments that report CSP as an active control.
HSTS configurations without the includeSubDomains directive leave subdomains unprotected from SSL stripping attacks even when the primary domain enforces HTTPS correctly.
CORS policies set to Access-Control-Allow-Origin: * on authenticated API endpoints are an exploitable misconfiguration, not a missing header, because the header is present and functioning as configured.
X-Frame-Options is being deprecated in favor of CSP frame-ancestors, but organizations that have added frame-ancestors to a CSP policy that also includes unsafe-inline have negated the clickjacking protection they intended to add.
In Vulnox web application assessments, the most common security header finding is not absence but misconfiguration that creates a documented control that does not function as the documentation states.
TL;DR
Security header checklists produce a false sense of coverage. The question is not whether a header is present but whether its configuration actually does what the implementation team believes it does. unsafe-inline in CSP, missing includeSubDomains in HSTS, and wildcard CORS on authenticated endpoints are headers that pass presence checks and fail function checks. Most assessment findings in this category are not missing headers. They are headers that are configured to do less than the team thinks.
The header that was there and not there
A 120-person SaaS company processing HR data had security headers listed as a completed item in their compliance documentation. Their DevOps team had added the headers during a hardening sprint six months before the assessment. The internal audit had checked for their presence and marked them green. When we pulled the actual response headers during the assessment, CSP was present with a script-src directive that included unsafe-inline and unsafe-eval, HSTS was configured without includeSubDomains, and the Access-Control-Allow-Origin header on their API was set to a wildcard with Access-Control-Allow-Credentials: true.
The combination of wildcard CORS with credentials allowed was not a missing header. It was a header that actively enabled cross-origin requests to carry authentication cookies from any origin. The compliance documentation said security headers were implemented. The response headers said something different. The gap between those two statements is where the actual risk lived, and neither the internal audit nor the automated scan had checked the configuration, only the presence.
Why presence checks produce false coverage
Security header scanners, compliance checklists, and most automated assessments check for the existence of a header in the HTTP response. A CSP header is present or absent. An HSTS header is present or absent. That check is necessary and not sufficient. The security value of each header is entirely determined by its configuration, and the configurations that provide no protection are often structurally identical to the configurations that do from a presence-check perspective.
Example
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' is a CSP header. It will pass a presence check on every automated scanner. It will appear green on securityheaders.com with a grade reduction but still as present. What it does is permit any inline script on the page to execute, which is the primary XSS injection mechanism. A page with this CSP and a stored XSS vulnerability is not protected from that vulnerability by the CSP. The header is there. The protection is not. The same logic applies to HSTS without includeSubDomains, Referrer-Policy set to unsafe-url, and Permissions-Policy with no actual restrictions specified.
The CORS misconfiguration pattern deserves specific attention because it is the one most likely to be categorically misunderstood. Access-Control-Allow-Origin: * tells the browser that any origin can read the response to a cross-origin request. That is often acceptable for public API endpoints returning non-sensitive data. It becomes an exploitable configuration when combined with Access-Control-Allow-Credentials: true, which instructs the browser to include authentication cookies in cross-origin requests. Browsers will refuse to honor this combination when the origin is a wildcard, per the spec, but the underlying intent, allowing credentialed cross-origin requests from any origin, gets implemented through overly permissive explicit origin lists instead, which browsers will honor. Assessment findings in this category are rarely the textbook wildcard-plus-credentials combination. They are usually a list of fifteen allowed origins that includes development environments, former partner domains, and in one case an origin the client could not identify.
What the assessment data shows
Assessment base: Vulnox vulnerability assessment data across SMB and mid-market clients, 2023-2024
CSP present with unsafe-inline in the majority of assessed environments reporting CSP as a control
In web application assessments where clients had documented CSP in their security controls inventory, the actual CSP configuration included unsafe-inline in script-src in most cases. The most common explanation from development teams was that removing unsafe-inline broke legitimate application functionality, specifically inline event handlers and script blocks in third-party components, and the header had been softened during troubleshooting and never hardened again. The compliance documentation reflected the original hardening sprint. The live configuration reflected the troubleshooting workaround.
A CSP that includes unsafe-inline provides no protection against inline script injection. If that CSP is cited in compliance documentation as a compensating control for XSS risk, the documentation is inaccurate. The control does not function as described. This creates a gap between stated security posture and actual security posture that is only visible when the live response headers are checked against the configuration documentation.
HSTS missing includeSubDomains on primary domain while subdomains serve authenticated sessions
A financial services client had HSTS configured on their primary domain with a max-age of one year. The configuration did not include includeSubDomains. Their application used a subdomain for the authenticated session interface and a separate subdomain for API calls. Both subdomains were excluded from HSTS coverage. An SSL stripping attack against either subdomain would not be prevented by the primary domain HSTS policy. The DevOps team had configured HSTS correctly for the marketing site and had not reviewed the configuration against the full subdomain inventory.
HSTS without includeSubDomains protects one hostname. Applications that distribute functionality across subdomains need HSTS applied at the subdomain level or the includeSubDomains directive on the parent. The presence check for HSTS on the primary domain will pass. The functional gap exists on every subdomain not covered.
CORS allowed origin lists containing unrecognized and former partner domains
In three separate assessments of organizations with partner API integrations, the CORS allowed origin list contained origins the current team could not explain. In one case, the list included a domain belonging to a former technology partner whose contract had ended two years prior. The origin was still in the list because removing it required a code deployment and nobody had created a ticket. The former partner's domain was still registered and pointed to infrastructure the current organization had no visibility into. A party controlling that domain could make credentialed cross-origin requests to the API.
CORS allowed origin lists are access control lists. They require the same lifecycle management as any other access control: periodic review, removal of expired entries, and documentation of why each entry exists. An origin list that accumulates entries without a removal process is a trust boundary that expands in one direction.
Referrer-Policy absent or set to no-restriction on applications handling sensitive URL parameters
A healthcare application passed session identifiers and patient record references in URL query parameters. The application had no Referrer-Policy header. Outbound links to third-party resources, including a support documentation platform and an analytics service, caused browsers to send the full URL including query parameters in the Referer header of requests to those third parties. The session identifiers in those URLs were being transmitted to the analytics platform in request headers. The DevOps team was unaware that query parameter data in URLs was leaving the application in Referer headers.
Referrer-Policy is the header most frequently absent in assessments of applications that handle sensitive data in URLs. The failure mode is not an active attack. It is passive data leakage to third parties that is invisible in application logs and visible in the third-party analytics platform's incoming request data.
The hardened header configuration that introduced a new vulnerability
Common belief
Adding security headers is a straightforward hardening step with no downside. The headers restrict browser behavior in ways that only matter to attackers, and legitimate application functionality is unaffected.
What we found
In assessments of applications that had recently undergone security hardening sprints, the most common finding was not missing headers but conflicting or partially effective configurations introduced during the sprint. The sprint added headers. The verification step checked presence. The functional check, does this CSP actually block the injection paths that matter, was not performed. The hardening sprint produced documentation of controls that were present but not configurations that functioned as intended.
CSP frame-ancestors is the recommended replacement for X-Frame-Options and provides more granular control over which origins can embed a page in a frame. When an organization adds frame-ancestors to their CSP to replace X-Frame-Options, they need to ensure the directive is in the same CSP header as the rest of the policy. Applications that deliver CSP across multiple headers or that have both a CSP header and an X-Frame-Options header can end up with conflicting directives. More directly: an organization that adds frame-ancestors: none to a CSP that already includes unsafe-inline has added clickjacking protection while leaving inline XSS protection absent. The security posture reporting shows two headers configured. The functional protection is weaker than it appears.
The header configurations most teams do not validate
Permissions-Policy with empty directive values
Permissions-Policy headers are added to restrict browser features like camera, microphone, and geolocation access. A common misconfiguration is adding the header with directives that have empty values, which in some browser implementations does not restrict the feature but does cause the header to appear present in a scan. The specific syntax matters: Permissions-Policy: camera=() restricts camera access to no origins. Permissions-Policy: camera= with no value has ambiguous behavior across browser implementations and should not be treated as equivalent.
CSP report-uri pointing to an inactive endpoint
Organizations that configure CSP violation reporting to a report-uri endpoint sometimes discover during incident review that the endpoint was never operational, had been deprecated, or was returning errors that caused browsers to silently drop violation reports. The CSP was configured to report violations. The reports were going nowhere. The one mechanism that would have provided browser-side evidence of attempted XSS exploitation was not functioning.
Security headers on the primary domain but not on API subdomains
The security header configuration on the marketing site or the login page is frequently reviewed. The API subdomain that handles authenticated requests is frequently not reviewed with the same attention. In applications with a frontend served from one origin and an API served from a subdomain, the API responses may lack the security headers applied to the frontend. CORS, CSP for API responses, and X-Content-Type-Options are particularly common gaps on API endpoints.
Cache-Control headers on authenticated pages
Cache-Control: no-store on responses containing personal data or session content prevents browsers and intermediate proxies from caching those responses. Applications that rely on CDNs or reverse proxies without explicit Cache-Control directives on authenticated responses can expose session content through proxy caches. This is not a security header in the conventional sense but is a response header with direct security implications that is rarely included in header audits.
How to audit security headers beyond presence checks
- Step 1
Pull live response headers from every distinct origin in scope
Output:A header inventory per origin, not per application, showing the raw header values as delivered in production.
Purpose:Presence checks against the primary domain miss subdomain gaps. Every origin serving authenticated content, API responses, or user-generated content needs independent header review.
- Step 2
Parse CSP directives and flag unsafe-inline, unsafe-eval, and wildcard sources
Output:A list of CSP directives that negate protection, with the specific mechanism each negation enables.
Purpose:A CSP header that includes unsafe-inline in script-src provides no inline XSS protection. A CSP with a wildcard source for script-src is equivalent to no CSP for script loading. These need to be identified as functional absences, not configuration weaknesses.
- Step 3
Test CORS behavior with credentialed cross-origin requests from unlisted origins
Output:A list of origins the server will reflect in Access-Control-Allow-Origin under which conditions, including whether credentialed requests are permitted.
Purpose:CORS misconfiguration is behavioral, not syntactic. The test is whether the server returns Access-Control-Allow-Origin with a value that reflects a submitted Origin header that should not be permitted, not whether the header is present.
- Step 4
Verify HSTS against the full subdomain inventory
Output:A map of subdomains serving authenticated content against HSTS coverage, identifying which subdomains are outside policy.
Purpose:HSTS on the primary domain without includeSubDomains does not protect subdomains. The subdomain inventory from DNS enumeration needs to be checked against HSTS coverage to identify gaps.
- Step 5
Validate CSP report-uri or report-to endpoint is operational
Output:Confirmation that CSP violation reports are being received and stored, with a sample report to verify the endpoint is functioning.
Purpose:A CSP reporting endpoint that is not operational means no browser-side violation data is being collected. This is a detection capability gap, not a prevention gap, but it matters for incident investigation.
Where security header failures are heading
Within two years, a significant data breach post-mortem in a regulated industry will cite CORS misconfiguration as the enabling condition for cross-origin data access, producing the first widely-cited enforcement case that names header configuration specifically rather than header absence.
CORS misconfiguration is currently underreported in breach disclosures because it is categorized as a configuration error rather than a missing control. The technical mechanism, overly permissive allowed origin lists enabling credentialed cross-origin requests, is present in a significant proportion of assessed environments. As post-breach forensic analysis becomes more technically specific in regulatory submissions, the distinction between absent and misconfigured controls will be named explicitly.
Confidence: mediumA published regulatory enforcement decision or public breach disclosure that names CORS misconfiguration as a contributing factor by end of 2026.CSP violation reporting will become a required audit evidence item under at least one major compliance framework within three years, driven by the recognition that client-side execution leaves no server-side evidence without it.
The evidentiary gap created by DOM-based XSS and client-side execution is becoming visible in post-incident investigations. Regulators and forensic investigators are encountering cases where server logs are complete and client-side exploitation is undetectable. CSP violation reporting is the only mechanism that provides browser-side evidence of attempted or successful script injection. Frameworks that have added client-side security requirements (PCI DSS 4.0 script management requirements are an early example) will extend into reporting requirements as the evidentiary gap becomes a documented problem.
Confidence: mediumPCI DSS 4.x or a major DPA enforcement guidance document explicitly requiring CSP violation reporting as an audit evidence item by 2027.
Why security header checklists produce worse outcomes than no checklist
A checklist that asks whether a header is present produces a documented green status for configurations that provide no protection. That documentation is worse than silence because it creates an audit trail that says the control is in place when the control is not functioning. The organization believes it is protected. The auditor has verified the belief. Neither has checked whether the protection works. I would rather see a compliance report that says CSP is not implemented than one that says CSP is implemented when the live configuration includes unsafe-inline. The first is a known gap. The second is a false assurance that will survive until something goes wrong.
Counterargument
The counterargument is that presence is better than absence and that organizations implementing headers at all are ahead of those that do not bother. That is true at the population level. At the individual organization level, a CSP with unsafe-inline provides approximately the same XSS protection as no CSP, which means the organization has invested implementation effort and documentation effort to produce a control that does not function. The effort would have been better spent on output encoding, which actually addresses the injection mechanism, rather than on a header that the implementation team softened into ineffectiveness during the first compatibility incident.
One thing to do this week
Run your primary application domain and every authenticated subdomain through Mozilla Observatory or securityheaders.com and look at the raw header values, not the grade. For each CSP header present, check whether script-src contains unsafe-inline. For each HSTS header, check whether includeSubDomains is present. For each Access-Control-Allow-Origin header on an API endpoint, check whether Access-Control-Allow-Credentials is also set and what the allowed origin list contains. If any of those checks produce a finding, the control is present and not functioning as documented. Update the documentation before the next audit, not to fix the grade but to accurately represent what the control actually does.
Further Reading
Vulnerability Assessment
comprehensive vulnerability assessmentCIS
CIS security controls frameworkOWASP Web Security Testing Guide
OWASP Security Testing GuideNVD Vulnerabilities Database
NIST Vulnerabilities DatabaseCISA Vulnerability Management
CISA vulnerability management guidance
Frequently Asked Questions
Why does a CSP header with unsafe-inline provide no XSS protection?
Content Security Policy blocks script execution from unauthorized sources. The unsafe-inline directive explicitly permits inline script execution, which is the primary mechanism for XSS injection. A CSP with unsafe-inline in script-src allows any inline script on the page to execute, including injected ones. This means the header passes presence checks on automated scanners and compliance checklists while providing no meaningful protection against inline XSS. In Vulnox assessments, this is the most common CSP finding in environments that report CSP as an active security control.
What makes a CORS misconfiguration exploitable versus a merely permissive configuration?
A CORS misconfiguration becomes exploitable when Access-Control-Allow-Credentials is set to true alongside an overly permissive allowed origin list. Credentials true instructs the browser to include authentication cookies in cross-origin requests. If the allowed origin list includes origins the organization does not control (former partner domains, development environments, unrecognized entries), a party controlling any of those origins can make authenticated cross-origin requests to the API. The header is present and functioning. The access control list it enforces is the problem. Vulnox assessments have found allowed origin lists containing domains belonging to former partners whose contracts ended years prior.
What does HSTS without includeSubDomains actually protect?
HSTS without includeSubDomains instructs browsers to use HTTPS only for the exact hostname in the header, not for any subdomain. An application that delivers the authenticated session interface or API calls from a subdomain receives no HSTS protection from a policy set only on the primary domain. An SSL stripping attack against the subdomain is not prevented. The primary domain HSTS header passes all presence checks. The subdomain is unprotected. This is a common gap in applications that distribute functionality across subdomains without reviewing HSTS coverage against the full subdomain inventory.
How should security headers be audited to catch misconfiguration rather than just absence?
Auditing for misconfiguration requires checking raw header values from every distinct origin in scope, not just the primary domain. For CSP, parse each directive and flag unsafe-inline, unsafe-eval, and wildcard script sources as functional absences. For CORS, test behavioral response to credentialed requests from unlisted origins, not just header presence. For HSTS, map subdomain inventory against includeSubDomains coverage. For CSP reporting, verify the report-uri or report-to endpoint is operational and receiving data. A presence check confirms a header exists. A functional check confirms it does what the implementation team believes.
What is the Referrer-Policy header and when does its absence create data leakage?
Referrer-Policy controls what URL information browsers include in the Referer header of outbound requests. Without this header, browsers default to sending the full URL of the originating page, including query parameters, to any third-party resource the page loads. Applications that pass session identifiers, record references, or user identifiers in URL parameters are leaking that data to analytics platforms, support documentation services, and any other third-party resource linked from those pages. In Vulnox assessments, this finding appears most frequently in healthcare and financial applications that use URL parameters for navigation and have third-party analytics integration.
What security header gaps commonly exist on API subdomains that are absent from primary domain audits?
API subdomains in applications with separated frontend and backend origins frequently lack the security headers applied to the primary domain. CORS configuration on API endpoints is the most significant gap, followed by X-Content-Type-Options, which prevents MIME sniffing of API responses, and Cache-Control directives on authenticated API responses. CSP is rarely applicable to API responses but CORP (Cross-Origin-Resource-Policy) is relevant for APIs that should not be embeddable cross-origin. Most security header audits are conducted against the primary application domain. The API subdomain serving authenticated data is the higher-value target and the less-reviewed surface.
Related Articles

API security assessment: what automated tools miss and manual testing finds
Automated API security tools find the vulnerabilities they were designed to look for. The findings that matter in real assessments, broken object-level authorization, JWT algorithm confusion, and unauthenticated internal endpoints, require a tester who understands what the API is supposed to do before probing what it actually does.

CVSS score explained: what the metric captures, what it misses, and how to fix your prioritization
In 40% of Vulnox assessments, vulnerabilities scored CVSS 9.0 or higher were on systems with compensating controls that materially reduced the actual risk. The score was accurate. The priority order it implied was not. This guide covers how to use CVSS correctly and what to layer on top of it.

Post-breach vulnerability assessment: what the attacker left behind
Most post-breach assessments start too late and end too early. They confirm what was hit, not what the attacker mapped before they moved. The second breach usually comes from the reconnaissance that happened in the first.
Ready to Secure Your Digital Assets?
Get a comprehensive vulnerability assessment for your website today.