mitnick/laravel-security
A Laravel security package that provides middleware and configuration to harden your application with security headers, CSP, HSTS, and other common protections. It helps enforce safer defaults across routes and responses with lightweight integration for Laravel projects.
Install
composer require getspooky/laravel-mitnick
The service provider is auto-discovered on Laravel 5.5+. If you are on an older skeleton, add Getspooky\Mitnick\MitnickServiceProvider::class to config/app.php.
Publish the config
php artisan vendor:publish --provider="Getspooky\Mitnick\MitnickServiceProvider"
This drops a config file (typically config/mitnick.php) containing the header names and values the middleware will emit — X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Strict-Transport-Security, and a Content-Security-Policy slot. Read that file first; it is the whole package. There is no database, no migrations, no routes.
Register the middleware
Laravel 10 and below — in app/Http/Kernel.php:
protected $middleware = [
// ...
\Getspooky\Mitnick\Middleware\Mitnick::class,
];
Laravel 11+ — in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\Getspooky\Mitnick\Middleware\Mitnick::class);
})
Prefer the web group (or appendToGroup('web', ...)) over the global stack unless you also want headers on API and error responses — see Gotchas.
First use case
Deploy, then confirm the headers actually land:
curl -sI https://your-app.test | grep -iE 'x-frame|x-content|referrer|strict-transport|content-security'
If nothing shows up, the middleware is not in the stack, is registered after the response is already sent, or a proxy/CDN is stripping the headers. That one curl is your fastest triage step and should be in your deploy checklist.
Config-driven per-environment headers. Keep the values in config/mitnick.php and vary them with .env:
'content-security-policy' => env('CSP_POLICY', "default-src 'self'"),
'strict-transport-security' => env('APP_ENV') === 'production'
? 'max-age=31536000; includeSubDomains; preload'
: '',
This is the single most useful pattern: strict in production, permissive locally, so php artisan serve and Dusk don't break on a hard CSP.
Scope by route group, not globally, when CSP is involved. Apply the middleware to web for HTML responses, and either skip it or use a separate relaxed instance for API routes. APIs usually want X-Content-Type-Options: nosniff and Referrer-Policy but not a document CSP.
Route::middleware(['web', \Getspooky\Mitnick\Middleware\Mitnick::class])
->group(function () { /* ... */ });
Middleware parameterisation. If the package's middleware accepts parameters (header name or config key), instantiate it per group to get different policies for /admin versus the public site. If it doesn't, subclass it and override the header array — that is usually cleaner than maintaining two packages.
Feature-test the headers. Because headers are invisible until runtime, pin them with a test:
public function test_security_headers_are_set(): void
{
$this->get('/')
->assertHeader('X-Frame-Options', 'SAMEORIGIN')
->assertHeader('X-Content-Type-Options', 'nosniff');
}
This is how you catch a middleware accidentally removed from the stack during a refactor, or a package upgrade that renamed a config key.
Pair with HTTPS enforcement. HSTS is meaningless (and dangerous on plain HTTP) without a redirect. Combine with App\Http\Middleware\TrustProxies, URL::forceScheme('https') in a service provider, or your load balancer's redirect rule. Order matters: HSTS should only be emitted on responses that are already HTTPS.
Pair with CSP reporting. If the config exposes a Content-Security-Policy string, put a report-to/report-uri directive pointing at an endpoint so you get violations in logs instead of user bug reports. Start with Content-Security-Policy-Report-Only for a week before enforcing.
Cache-aware deployment. If you use a full-page cache (CDN, responsecache), the headers are baked into the cached response. Purge after changing config, and verify with curl -I against the CDN edge, not just the origin.
Low adoption — audit before trusting it. The package has a 0 score and 0 dependents, which means essentially no community review of its middleware code. Before putting it in a production stack, read the middleware file (it is short) and check: does it overwrite existing headers or append duplicates? Does it run on every response including exceptions and 404s? Does it respect app.debug? If anything looks off, the mature alternatives are bepsvpt/secure-headers (broader header coverage, actively maintained) or spatie/laravel-csp (CSP with nonce generation). Many teams end up re-implementing this as a 30-line middleware, which is a perfectly reasonable outcome.
Duplicated headers. If your web server (nginx add_header), your CDN, or another middleware already sets X-Frame-Options, you get two values and browsers may pick the intersection — which can silently deny framing you intended to allow. Pick one owner for security headers and remove the others.
HSTS is sticky and unforgiving. A max-age=31536000; preload header sent from a staging environment on a real domain can lock users out for a year if HTTPS ever breaks. Never emit HSTS when APP_ENV !== 'production', and never set preload until you have submitted to the preload list deliberately.
CSP will break something. Inline <script> blocks, onclick= attributes, javascript: URLs, Google Fonts, Hotwire/Livewire/Alpine, Laravel Nova, and Vite's dev server all violate a strict default-src 'self'. Symptom in the browser console: "Refused to execute inline script". Fix with nonces (spatie/laravel-csp does this automatically) or by shipping Content-Security-Policy-Report-Only first and reading the reports.
X-XSS-Protection is obsolete. Modern browsers ignore it, and 1; mode=block can introduce vulnerabilities in old ones. Set it to 0 or omit it; don't treat its presence as a security win.
Middleware ordering and proxies. Behind a load balancer, $request->isSecure() is false unless TrustProxies is configured, so any conditional logic keyed on HTTPS in the middleware will misbehave. Set TrustProxies (or Laravel 11's trustProxies(at: '*')) before debugging the package.
API and error responses. If the middleware is in the web group only, api/* routes and the default exception handler's JSON responses will lack headers. Decide deliberately whether that matters for your threat model.
Debugging checklist, in order:
php artisan route:list --middleware — confirm the middleware is attached.curl -sI http://localhost:8000 — confirm the origin emits headers.curl -sI https://prod — confirm the edge does too.php artisan config:clear — config changes are cached in production.Extension points. The config file is the intended surface. Beyond that, subclass the middleware to add headers (e.g. Permissions-Policy, Cross-Origin-Opener-Policy), or bind your subclass in the container in place of the package's class. If the package's service provider is not easily overridable, that is a signal to swap to a maintained package rather than fight it.
How can I help you explore Laravel packages today?