Security Headers for SOC 2 and ISO 27001: What Laravel Teams Need to Know
SOC 2 and ISO 27001 audits increasingly flag missing or misconfigured security headers. Learn which headers auditors look for, how to implement them in Laravel middleware, and how to monitor compliance continuously.
If your Laravel application is heading toward SOC 2 Type II certification or ISO 27001 compliance, security headers will come up during the audit. Not as a maybe. As a certainty.
Auditors treat HTTP security headers as observable evidence that your application enforces browser-level protections. Missing headers signal missing controls. And because headers are visible to anyone with a browser's developer tools, they are one of the first things an auditor checks.
This guide covers exactly which headers matter for SOC 2 and ISO 27001, how to implement each one in Laravel using middleware, what auditors flag most often, and how to keep your headers compliant between audits.
Why Security Headers Matter for Compliance
SOC 2 and ISO 27001 take different approaches, but both care about the same outcome: demonstrating that your application has controls in place to protect data in transit and prevent common web attacks.
SOC 2 evaluates your controls against the Trust Services Criteria. The relevant criteria for security headers include:
- CC6.1: Logical access security over protected information assets
- CC6.6: Security measures against threats outside system boundaries
- CC6.7: Restricting data transmission to authorised parties
ISO 27001 maps to Annex A controls, specifically:
- A.8.9: Web filtering, requiring controls on web-based threats
- A.8.24: Use of cryptography, covering secure transmission
In both frameworks, security headers serve as technical controls that enforce protection at the browser level. An auditor looking at your application will open their browser, inspect the response headers, and compare what they see against what your security documentation claims.
If the documentation says you enforce HSTS and the response headers say otherwise, that is a finding.
The Six Essential Security Headers
Here are the six headers that SOC 2 and ISO 27001 auditors consistently look for, along with what each one does.
1. Content-Security-Policy (CSP)
CSP tells the browser which sources of content are allowed to load on your pages. It is the single most effective header for preventing cross-site scripting (XSS) attacks.
A properly configured CSP blocks inline scripts, restricts which domains can serve JavaScript, and prevents your pages from loading content from unauthorised origins.
Audit relevance: Auditors check whether CSP is present and whether it is meaningfully restrictive. A policy of default-src * is effectively the same as having no CSP at all.
2. Strict-Transport-Security (HSTS)
HSTS tells browsers to only communicate with your application over HTTPS. Once a browser receives this header, it will refuse to load your site over plain HTTP for the duration specified in the max-age directive.
Audit relevance: HSTS is a direct control for CC6.7 (SOC 2) and A.8.24 (ISO 27001). Auditors verify that max-age is set to at least one year (31536000 seconds) and that includeSubDomains is present.
3. X-Content-Type-Options
This header has exactly one valid value: nosniff. It prevents browsers from MIME-type sniffing, which stops attacks where a malicious file is disguised as a safe content type.
Audit relevance: Simple to implement, always expected. If this header is missing, auditors will flag it immediately because there is no valid reason to omit it.
4. X-Frame-Options
This header controls whether your application can be embedded in an iframe. Setting it to DENY or SAMEORIGIN prevents clickjacking attacks where an attacker overlays your application with a transparent iframe to capture user clicks.
Audit relevance: Clickjacking prevention is a standard expectation. Most auditors want to see DENY unless you have a documented business need for iframe embedding.
5. Referrer-Policy
This header controls how much referrer information is included when users navigate away from your application. Setting it to strict-origin-when-cross-origin or no-referrer prevents your URLs (which may contain sensitive parameters) from leaking to third-party sites.
Audit relevance: This falls under data leakage prevention. Auditors check whether sensitive URL parameters could be exposed through referrer headers.
6. Permissions-Policy
Permissions-Policy (formerly Feature-Policy) controls which browser features your application can use, including the camera, microphone, geolocation, and payment APIs. Restricting unused features reduces your attack surface.
Audit relevance: Auditors look for evidence that you follow the principle of least privilege at every layer, including browser APIs. Disabling features you do not use demonstrates that principle.
Implementing Security Headers in Laravel
The cleanest approach is a dedicated middleware class. This keeps your security headers version-controlled, testable, and environment-aware.
Step 1: Create the Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set(
'Content-Security-Policy',
$this->buildCsp()
);
$response->headers->set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
$response->headers->set(
'X-Content-Type-Options',
'nosniff'
);
$response->headers->set(
'X-Frame-Options',
'DENY'
);
$response->headers->set(
'Referrer-Policy',
'strict-origin-when-cross-origin'
);
$response->headers->set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=()'
);
return $response;
}
private function buildCsp(): string
{
$directives = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
];
return implode('; ', $directives);
}
}
Step 2: Register the Middleware
In your bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\SecurityHeaders::class,
]);
})
For Laravel 10 and earlier, add the middleware to the $middlewareGroups['web'] array in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ... existing middleware
\App\Http\Middleware\SecurityHeaders::class,
],
];
Step 3: Make CSP Environment-Aware
Your development environment likely needs a more permissive CSP than production (for Vite hot reloading, Debugbar, etc.). Use your .env file to control this:
private function buildCsp(): string
{
if (app()->environment('local')) {
return "default-src 'self' 'unsafe-inline' 'unsafe-eval'; " .
"connect-src 'self' ws://localhost:*";
}
$directives = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
];
return implode('; ', $directives);
}
Step 4: Use Report-Only Mode First
Before enforcing CSP, run it in report-only mode to catch violations without breaking your application:
$headerName = config('app.csp_enforce', false)
? 'Content-Security-Policy'
: 'Content-Security-Policy-Report-Only';
$response->headers->set($headerName, $this->buildCsp());
Add a reporting endpoint to collect violations:
$directives[] = "report-uri /api/csp-report";
$directives[] = "report-to csp-endpoint";
Then toggle enforcement in your .env when you are confident the policy is correct:
CSP_ENFORCE=true
Step 5: Write a Test
Verify your headers are present with a feature test:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class SecurityHeadersTest extends TestCase
{
public function test_response_contains_security_headers(): void
{
$response = $this->get('/');
$response->assertHeader('Content-Security-Policy');
$response->assertHeader('Strict-Transport-Security');
$response->assertHeader('X-Content-Type-Options', 'nosniff');
$response->assertHeader('X-Frame-Options', 'DENY');
$response->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->assertHeader('Permissions-Policy');
}
public function test_hsts_max_age_is_at_least_one_year(): void
{
$response = $this->get('/');
$hsts = $response->headers->get('Strict-Transport-Security');
preg_match('/max-age=(\d+)/', $hsts, $matches);
$this->assertGreaterThanOrEqual(31536000, (int) $matches[1]);
}
}
This test file is audit-ready. When an auditor asks for evidence that security headers are enforced, you can show them the middleware class and the passing test suite.
Common Audit Findings Related to Headers
These are the issues that auditors flag most often. Every one of them is preventable.
1. HSTS max-age too short. Setting max-age=86400 (one day) technically enables HSTS, but auditors consider anything under one year to be insufficient. Browsers that have not visited your site in over a day would fall back to accepting HTTP connections.
2. CSP set to report-only in production. Report-only mode is useful during rollout, but leaving it in production indefinitely means your CSP is not actually enforcing anything. Auditors will check whether the header is Content-Security-Policy or Content-Security-Policy-Report-Only.
3. Missing Permissions-Policy. Many teams configure the first four or five headers and forget Permissions-Policy because their application does not use browser APIs. That is exactly the point. Explicitly disabling unused features demonstrates least-privilege enforcement.
4. Headers set in code but overridden by the web server. Your Laravel middleware might set X-Frame-Options: DENY, but if your Nginx config also sets X-Frame-Options: SAMEORIGIN, the browser may receive conflicting values. Audit your full response chain, including CDN and reverse proxy layers.
5. Inconsistent headers across routes. API routes, health check endpoints, and asset routes often bypass the web middleware group. If an auditor checks /api/health and finds no security headers, that is a finding, even if every other route has them. Apply headers globally or ensure every route group is covered.
6. No evidence of monitoring. Auditors want to see that you are not just setting headers once and forgetting about them. They look for continuous monitoring that detects when headers change or disappear. A deployment that accidentally removes your middleware should trigger an alert, not sit unnoticed until the next audit cycle.
How StackShield Monitors Header Compliance
Setting security headers is step one. Keeping them in place across deployments, infrastructure changes, and configuration updates is where teams struggle.
StackShield checks your security headers on every scan by making real HTTP requests to your Laravel application, exactly the way an auditor would. Each scan verifies:
- Presence: Are all six recommended headers present in the response?
- Values: Is HSTS max-age at least one year? Is X-Content-Type-Options set to nosniff? Is X-Frame-Options set to DENY or SAMEORIGIN?
- CSP quality: Is the Content-Security-Policy meaningfully restrictive, or is it effectively open?
- Consistency: Are headers present on all routes, not just the homepage?
When a header goes missing or a value changes, StackShield flags the issue with the specific header name, the expected value, and what was actually returned. You get notified through Slack, email, or webhooks before the next audit cycle catches it.
This gives you continuous compliance evidence. Instead of scrambling before an audit to verify your headers are still correct, you have a log of every scan showing that your headers have been consistently enforced.
Run a free scan to see how your security headers measure up right now. It takes less than 60 seconds and checks all six headers covered in this guide.
Preparing for Your Audit
Here is a practical approach for audit preparation:
- Implement the middleware using the code examples above. Commit it to your repository so auditors can review the source.
- Write tests that verify every header. Include these in your CI/CD pipeline so headers are checked on every pull request.
- Run in report-only mode for CSP for at least two weeks. Fix any violations before switching to enforcement.
- Document your controls in your SOC 2 control matrix or ISO 27001 Statement of Applicability. Reference the middleware class, the test file, and your monitoring tool.
- Set up continuous monitoring with StackShield so you have ongoing evidence that headers remain in place between audit periods.
- Test the full response chain. Use
curl -I https://yourdomain.comto verify that headers are present after passing through your CDN, load balancer, and reverse proxy.
Security headers are one of the most visible controls an auditor evaluates. They take less than an hour to implement correctly in Laravel, and with continuous monitoring, they stay compliant without ongoing manual effort.
Frequently Asked Questions
Which security headers are required for SOC 2 compliance?
SOC 2 does not prescribe a specific list of headers, but auditors evaluate your controls against the Common Criteria (CC6.1, CC6.6, CC6.7) which cover logical access, transmission security, and boundary protection. In practice, auditors expect to see Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy. Missing any of these will likely result in an observation or finding in your Type II report.
Do I need all six security headers for ISO 27001 certification?
ISO 27001 Annex A controls A.8.9 (web filtering) and A.8.24 (use of cryptography) require you to demonstrate that web application traffic is secured and that you have controls preventing common attack vectors. While the standard does not name specific headers, your Statement of Applicability should reference them, and certification auditors will check your live application. All six headers listed in this article are considered baseline expectations by most certification bodies.
Can I set security headers in Laravel without middleware?
Yes. You can configure headers at the web server level (Nginx, Apache) or through a CDN like Cloudflare. However, using Laravel middleware gives you version-controlled, environment-aware configuration that travels with your codebase. This is especially useful for audits because you can point auditors directly to the middleware class as evidence of your control implementation.
What happens if my Content-Security-Policy breaks my application?
Start with Content-Security-Policy-Report-Only instead of Content-Security-Policy. This header tells browsers to report violations without actually blocking anything. Set up a reporting endpoint, monitor violations for a few weeks, and adjust your policy before switching to enforcement mode. Laravel makes it easy to toggle between report-only and enforced modes using environment variables in your middleware.
How often should I check my security headers for compliance drift?
Security headers can change unexpectedly after deployments, infrastructure updates, or CDN configuration changes. Manual quarterly checks are not enough. Continuous monitoring is the standard auditors prefer to see. StackShield checks your security headers on every scan and alerts you immediately when a header is missing, misconfigured, or weakened.
Related Security Terms
Related Articles
Laravel Debug Mode in Production: Why It's Dangerous and How to Fix It
Debug mode in production exposes stack traces, database credentials, environment variables, and internal paths. Learn exactly what it reveals, how attackers use it, and how to make sure it never reaches production.
SecurityOWASP Top 10 for Laravel: Every Vulnerability Explained with Code Fixes (2026)
Map every OWASP Top 10 category to real Laravel vulnerabilities. Includes code examples of what goes wrong, how to detect each issue, and step-by-step fixes.
SecurityIs Your Laravel .env File Exposed? How to Check and Fix It
Your .env file contains database credentials, API keys, and encryption secrets. If it's accessible from the web, attackers already have everything they need. Here's how to check and fix it.
Compare StackShield
Security Checklists
Laravel Production Deployment Security Checklist
A comprehensive security checklist for deploying Laravel applications to production. Covers environment config, server hardening, access control, and monitoring.
20 itemsLaravel API Security Checklist
Secure your Laravel API endpoints against common vulnerabilities. Covers authentication, input validation, rate limiting, and response security.
Stay Updated on Laravel Security
Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.