Insecure or unset HTTP headers - Content-Security-Policy
Need
Enforcement of a Content-Security-Policy without unsafe directives
Context
• Usage of Kotlin 1.9+ on the JVM for building web applications
• Usage of the javax.servlet API for response filters
Description
1. Non compliant code
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletResponse
class SecurityHeadersFilter : Filter {
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {...The filter below adds a `Content-Security-Policy` header to every response, but the policy allows `'unsafe-inline'` scripts. A Content-Security-Policy is the browser-side defense that stops injected scripts from running when an encoding mistake lets them into a page. `'unsafe-inline'` allows every `<script>` block and every event handler attribute such as `onerror=` in the document, which are exactly what a cross-site scripting payload uses. The header is present, so scanners and reviewers see a CSP, but an attacker who finds an injection runs their script as if there were none. `'unsafe-eval'` and wildcard sources such as `script-src *` weaken the policy in the same way.
2. Steps
• Remove `'unsafe-inline'` and `'unsafe-eval'` from every `Content-Security-Policy` header.
• Replace wildcard sources in `default-src` and `script-src` with `'self'` and the specific hosts the application needs.
• Move inline scripts and event handler attributes to separate files, or allow the few that remain with a per-response nonce.
• Add `object-src 'none'`, `base-uri 'self'` and `frame-ancestors 'none'` to the policy.
• Set the header in a single filter, or with Spring Security `headers { contentSecurityPolicy }`, so every response carries it.
3. Secure code example
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletResponse
private const val CSP = "default-src 'self'; script-src 'self'; object-src 'none'; " +
"base-uri 'self'; frame-ancestors 'none'"...The corrected filter sends a policy without `'unsafe-inline'` or `'unsafe-eval'`. `default-src 'self'` and `script-src 'self'` only allow scripts loaded from files on the same origin, so inline blocks and event handler attributes injected into a page do not execute. `object-src 'none'` disables plugins, `base-uri 'self'` prevents an injected `<base>` tag from redirecting relative script URLs, and `frame-ancestors 'none'` prevents the pages from being framed. Inline scripts must move to separate files. When a page really needs an inline block, the policy can allow it with a random nonce generated for each response, never with `'unsafe-inline'`. Because the header is set in a filter, it covers every response, including error pages.