- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
The Security Headers Most Sites Forget (and How to Add Them in Laravel)
About Post
You can spend weeks on authentication, validation and rate limiting, and still ship a site that any other website can load in an invisible iframe and trick your users into clicking.
The fix for that, and for a few other nasty attacks, is not a library or a rewrite. It's a handful of HTTP response headers. They take an hour to add, they cost nothing at runtime, and a surprising number of production sites still don't send them.
Here are the mistakes I see most often, why each header matters, and a Laravel middleware that covers the basics at the end.
Mistake 1: no Content-Security-Policy at all
What it protects: Content-Security-Policy (CSP) tells the browser which sources of scripts, styles, images and frames are allowed. If an attacker manages to inject a <script> tag through a comment field or a forgotten unescaped output, a good CSP stops the browser from running it. It's your second line of defence against XSS, for the day the first one fails.
Why people skip it: because a strict CSP breaks things. Inline scripts, analytics snippets, a chat widget, a font from a CDN: all blocked until you allow them. So teams either skip CSP or add one that allows everything, including 'unsafe-inline' scripts, which removes most of the protection.
The fix: roll it out in two steps.
- Start with
Content-Security-Policy-Report-Only. The browser enforces nothing, but reports every violation in the console (and to a reporting endpoint if you set one). Now you can see what would break. - Tighten the policy until the reports are clean, then switch to the real
Content-Security-Policyheader.
For inline scripts you really need, use nonces instead of 'unsafe-inline'. Laravel's Vite integration can generate a nonce for its script tags with Vite::useCspNonce(), which you can then add to your policy.
Mistake 2: relying only on X-Frame-Options
What it protects: clickjacking. An attacker's page loads your site in a transparent iframe and lines up your "Confirm payment" button under their "Claim prize" button. The user thinks they clicked one thing and clicked the other.
The mistake: X-Frame-Options was the original fix, and it still works for DENY and SAMEORIGIN. But its ALLOW-FROM option is obsolete and ignored by modern browsers, so it can't express "only my own partner domain may frame this".
The fix: use the CSP frame-ancestors directive, which replaces it and supports a list of allowed origins: frame-ancestors 'self' https://partner.example.com. Sending X-Frame-Options: SAMEORIGIN as well doesn't hurt and covers very old browsers.
Mistake 3: HTTPS without HSTS
What it protects: you redirect HTTP to HTTPS, so you're safe, right? Not quite. The very first request a user types as example.com goes out over plain HTTP before your redirect happens, and on a hostile network that first request can be intercepted. Strict-Transport-Security tells the browser to use HTTPS for your domain from now on, without ever trying HTTP first.
The mistakes: adding includeSubDomains before checking that every subdomain supports HTTPS (that old reports. subdomain on an HTTP-only server will stop working), and jumping straight to preload, which is hard to undo once browsers ship your domain in their preload list.
The fix: start with a short max-age, confirm nothing breaks, then raise it to a year. Add includeSubDomains after an audit. Consider preload last, deliberately.
Mistake 4: leaking URLs through the Referer header
What it protects: when a user clicks a link from your site to another site, the browser can send the page they came from in the Referer header. If that URL contains something sensitive, like a password reset token, a signed download link or an invoice ID, you've just shared it with a third party, or with every external script that loads on the page.
Modern browsers now default to a safer behaviour, but defaults differ and change. Being explicit costs one line.
The fix: Referrer-Policy: strict-origin-when-cross-origin sends only your origin (not the full path) to other sites, and nothing at all when going from HTTPS to HTTP. For especially sensitive pages, no-referrer. And, separately, keep secrets out of URLs where you can.
Mistake 5: never saying which browser features you use
What it protects: Permissions-Policy controls powerful browser features: camera, microphone, geolocation, payment and others. If your site never uses the camera, say so: camera=() disables it for your page and any iframes inside it. If a compromised third-party script ever tries to ask for it, the browser refuses.
It's a small header that shrinks the damage any injected script can do. Few sites send it.
Mistake 6: forgetting the small ones (and keeping the dead ones)
X-Content-Type-Options: nosniffstops browsers from "guessing" a file's type. Especially important if users can upload files: you don't want an uploaded "image" executed as a script.X-XSS-Protectionis dead. The browser filter it controlled has been removed from modern browsers and could itself cause problems. Don't add it; CSP is the replacement.X-Powered-By: PHP/8.xtells attackers your exact version for free. Setexpose_php = Offinphp.ini.
A Laravel middleware for the basics
Here's a simplified starting point. The CSP is in report-only mode on purpose, so it won't break anything on day one:
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
$response->headers->set('Content-Security-Policy-Report-Only',
"default-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'");
return $response;
}
}
Register it for web routes in bootstrap/app.php in Laravel 12:
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [SecurityHeaders::class]);
})
Adjust before copying: if your site legitimately uses the camera or must be framed by a partner, the values above will need changing, and the HSTS line assumes every subdomain is on HTTPS.
Pick one place for headers. Set them in the app or in Nginx/Apache, not both by accident. Two different CSP headers aren't merged; the browser enforces both, so the effective policy is stricter than either one, and very confusing to debug. Also remember that static files served directly by the web server never pass through Laravel middleware.
How to check your own site
Open the browser's developer tools, go to the Network tab, click the main document request and read the response headers. Or run curl -I https://yoursite.com. Free online scanners will also grade your headers and explain each missing one.
The short version:
- CSP in report-only first, then enforce, with nonces instead of
'unsafe-inline'. frame-ancestorsfor clickjacking, plusX-Frame-Optionsfor old browsers.- HSTS with a careful rollout of
includeSubDomainsandpreload. - Explicit
Referrer-PolicyandPermissions-Policy. nosniffon,X-XSS-ProtectionandX-Powered-Bygone.
Which header was the hardest to roll out on your site? For most teams I know, it's CSP, and I'm curious what broke first for you.

Be first to comment it...