Advanced rule set for configuring, validating, and maintaining HTTP security headers in Java/Spring Boot applications.
You're tired of security audits failing because of missing headers. Your Spring Boot app works perfectly, but every security scan throws red flags for basic header configurations. Meanwhile, you're manually checking SecurityHeaders.com and scrambling to fix issues that should have been automated months ago.
Most Spring Security tutorials give you the bare minimum—a few headers() calls and you're done. But production applications need comprehensive header management that actually works across different deployment scenarios:
You need headers that work reliably across your entire application stack—not just the happy path.
These Cursor Rules transform security header management from manual configuration to automated, testable infrastructure. Instead of wondering if your headers are correct, you'll have type-safe, validated, and automatically tested header configurations that work consistently across all environments.
What you get:
// Before: Manual string concatenation and hoping for the best
http.headers(headers -> headers
.contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'"));
// After: Type-safe, validated configuration
http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives(CSP_POLICIES.STRICT_DEFAULT_WITH_SELF_SCRIPTS)));
Your build pipeline automatically validates headers:
stage('Security Headers') {
steps {
sh 'npx @securityheaders/cli https://staging.example.com --fail-on-grade < A'
}
}
Result: Catch security misconfigurations before they reach production, not during security audits.
// Precomputed header values for high-throughput APIs
private static final Map<HeaderName, String> CACHED_HEADERS =
Map.of(HeaderName.CONTENT_SECURITY_POLICY, buildOptimalCSP());
Impact: Eliminate per-request header computation overhead in performance-critical applications.
Before: Research header values, manually update Spring configuration, deploy to staging, manually test with SecurityHeaders.com, hope it works in production.
After:
@Configuration
public class SecurityHeadersConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.headers(headers -> headers
.defaultsDisabled()
.contentTypeOptions(Customizer.withDefaults())
.permissionsPolicy(perm -> perm.policy(PERMISSIONS_POLICY.RESTRICTIVE))
.crossOriginOpenerPolicy(coop -> coop.policy(SAME_ORIGIN))
)
.build();
}
}
Your CI automatically validates the configuration and fails the build if headers don't meet your security grade requirements.
Before: Headers work in development but mysteriously disappear behind Nginx in production. Spend hours debugging header conflicts and duplicate emissions.
After: Environment-aware configuration that detects proxy presence:
@ConditionalOnProperty(name = "app.proxy.enabled", havingValue = "false")
@Bean
SecurityFilterChain standaloneHeaders(HttpSecurity http) throws Exception {
// Full header configuration for standalone deployment
}
@ConditionalOnProperty(name = "app.proxy.enabled", havingValue = "true")
@Bean
SecurityFilterChain proxyHeaders(HttpSecurity http) throws Exception {
// Minimal headers - let proxy handle the rest
}
Before: Manually track OWASP recommendations, update random header values across multiple configuration files, hope you didn't break anything.
After: Centralized header constants with built-in deprecation warnings:
public enum HeaderValues {
HSTS_TWO_YEARS("max-age=63072000; includeSubDomains; preload"),
@Deprecated(since = "2024.1", forRemoval = true)
EXPECT_CT_LEGACY("enforce, max-age=604800"); // Auto-removed in filters
}
Copy the complete rule set to your .cursorrules file. The rules include type-safe header definitions, Spring Security configurations, and automated testing patterns.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.headers(headers -> headers
.defaultsDisabled() // Start from blank slate
.contentTypeOptions(Customizer.withDefaults())
.xssProtection(xss -> xss.block(true))
.frameOptions(frame -> frame.sameOrigin())
.referrerPolicy(ref -> ref.policy(ReferrerPolicy.SAME_ORIGIN))
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.preload(true)
.maxAgeInSeconds(HSTS_MAX_AGE_TWO_YEARS))
.contentSecurityPolicy(csp -> csp
.policyDirectives(CSP_STRICT_DEFAULT))
)
.build();
}
}
@SpringBootTest
@AutoConfigureTestDatabase
class SecurityHeadersIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldIncludeAllRequiredSecurityHeaders() throws Exception {
mockMvc.perform(get("/api/health").secure(true))
.andExpect(header().string("X-Content-Type-Options", "nosniff"))
.andExpect(header().string("X-Frame-Options", "SAMEORIGIN"))
.andExpect(header().exists("Strict-Transport-Security"));
}
}
Add to your pipeline:
- name: Validate Security Headers
run: |
docker run --rm securityheadersio/scanner $STAGING_URL --exit-code-on "missing,low"
Your security headers become infrastructure—reliable, tested, and automatically maintained. No more manual SecurityHeaders.com checks, no more scrambling to fix audit findings, no more wondering if your headers actually work in production.
You are an expert in Java 17+, Spring Boot 3.x, Spring Security 6, Maven, Docker, Nginx, and Cloudflare.
Key Principles
- Treat every HTTP response as untrusted until all required security headers are attached.
- Prefer **deny-by-default**: start with the strictest header values, then selectively relax per endpoint.
- Configuration-as-code: declare headers exclusively in Java config classes or version-controlled proxy configs—never manual UI toggles.
- Immutable, self-documenting code: expose every header value through `static final` constants or enums; avoid magic strings.
- Shift-left security: enforce header checks in CI using scanners (SecurityHeaders.com API, Invicti CLI).
Java-Specific Rules
- Use records or immutable POJOs for header definition objects.
- Model header names with `enum HeaderName { STRICT_TRANSPORT_SECURITY("Strict-Transport-Security"), ... }` for type safety.
- Keep header values in UPPER_SNAKE_CASE `static final` strings inside a `HeaderValues` utility.
- Avoid reflection or annotation hacks; prefer fluent builder on `HttpSecurity`.
- Never concatenate header strings—use `String.join("; ", ...)` to avoid trailing semicolons.
Error Handling and Validation
- Validate header presence in an integration test executed on every build. Fail the build on any missing/extra header.
- Early-return pattern in filters:
```java
if (!request.isSecure()) {
chain.doFilter(request, response); // let HSTS upgrade handle next time
return;
}
```
- Log misconfigurations at `WARN` level and surface to Prometheus/Grafana via custom `MeterRegistry` counter `security_header_miss_total`.
- Automatically remove deprecated headers (e.g., `Expect-CT`) during application start-up with a filter that strips legacy entries.
Spring Security Rules
- Use a single `SecurityFilterChain` bean; avoid multiple chains unless absolutely required.
- Enable default headers then override:
```java
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.defaultsDisabled() // start from blank slate
.contentTypeOptions(Customizer.withDefaults())
.xssProtection(xss -> xss.block(true))
.frameOptions(frame -> frame.sameOrigin())
.referrerPolicy(ref -> ref.policy(ReferrerPolicy.SAME_ORIGIN))
.permissionsPolicy(perm -> perm.policy("geolocation=(), microphone=()"))
.crossOriginOpenerPolicy(coop -> coop.policy(CrossOriginOpenerPolicyPolicy.SAME_ORIGIN))
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.preload(true)
.maxAgeInSeconds(63072000))
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none';"))
);
return http.build();
}
```
- For REST-only services (no HTML), still set CSP with `default-src 'none'; frame-ancestors 'none';` to mitigate future UI addition without headers.
- Keep HSTS `max-age` ≥ 2 years (63072000 s) and enable `preload` once tests pass.
- When behind Cloudflare/Nginx, disable duplicate header emission using `headers().disable()` and delegate to proxy to avoid clashes.
Proxy / Reverse Proxy Rules
Nginx
- Add fallback headers in `server` block to catch static files bypassing Spring:
```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
```
- Use `more_clear_headers` (from `headers-more` module) to strip `Server` and `X-Powered-By`.
Cloudflare
- Create Transform Rules to inject headers for assets served directly from cache.
- Enable **Automatic HTTPS Rewrites** to complement HSTS.
Testing
- Unit test header constants: ensure they match official names with regex `^[A-Z][a-zA-Z-]+$`.
- Integration test with Spring MockMvc:
```java
mockMvc.perform(get("/actuator/health").secure(true))
.andExpect(header().string("X-Content-Type-Options", "nosniff"));
```
- Pipeline step: `npx @securityheaders/cli https://staging.example.com --fail-on-grade < A`.
Performance
- Cache header definitions in an unmodifiable map; lookup by `HeaderName` enum ordinal for O(1) performance.
- For high-throughput APIs, precompute CSP string once instead of rebuilding per request.
DevSecOps & Automation
- Jenkinsfile snippet:
```groovy
stage('Security Headers') {
steps {
sh 'docker run --rm securityheadersio/scanner $STAGING_URL --exit-code-on "missing,low"'
}
}
```
- If any scanner fails, block merge via GitHub Status.
Documentation & Maintenance
- Maintain a markdown `SECURITY_HEADERS.md` listing each header, its rationale, and last review date.
- Schedule quarterly review using OWASP cheat sheet updates.
- Deprecate legacy headers via feature flags with a 2-release sunset period.