Security Headers & nginx Configuration
Overview
HTTP security headers instruct browsers how to handle content and prevent common attacks. This document covers StockEase Frontend's nginx configuration for clickjacking prevention, HSTS, MIME type sniffing, and other security headers.
Security Headers in StockEase
Current Implementation
StockEase Frontend has security headers configured in
ops/nginx/nginx.conf:
# =========================================================================
# Security Headers
# =========================================================================
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Header Overview
| Header | Purpose | Current Value |
|---|---|---|
| X-Content-Type-Options | MIME type sniffing prevention | nosniff |
| X-XSS-Protection | XSS protection in old browsers | 1; mode=block |
| X-Frame-Options | Clickjacking prevention | DENY |
| Referrer-Policy | Referrer leakage prevention | strict-origin-when-cross-origin |
| HSTS | HTTPS enforcement | β Not configured (recommended) |
| CSP | Content Security Policy | β Not configured (recommended) |
X-Content-Type-Options: MIME Sniffing Prevention
What is MIME Sniffing?
MIME (Multipurpose Internet Mail Extensions) describes file types:
image/png β Image file
text/javascript β JavaScript file
application/json β JSON data
Sniffing attack:
Server sends: Content-Type: text/plain
<script>alert('xss')</script>
Browser (without protection):
ββ Reads content
ββ Ignores Content-Type header (sniffs)
ββ Sees <script> tag
ββ Interprets as JavaScript
ββ Executes script β
X-Content-Type-Options Header
X-Content-Type-Options: nosniff
What it does:
- Tells browser: "Trust the Content-Type header"
- Browser will NOT sniff the actual content
- If header says
text/plain, treat as plain text (even if it's script)
Example:
Server sends:
Content-Type: text/plain
X-Content-Type-Options: nosniff
<script>alert('xss')</script>
Browser:
ββ Reads Content-Type: text/plain
ββ Sees X-Content-Type-Options: nosniff
ββ Does NOT sniff content
ββ Treats as plain text
ββ Displays as text (script not executed) β
Current Configuration
add_header X-Content-Type-Options "nosniff" always;
Status: β Correctly configured
X-Frame-Options: Clickjacking Prevention
What is Clickjacking?
Clickjacking (UI redressing) tricks users into clicking hidden elements:
Attack scenario:
<!-- Attacker's site -->
<div style="position: absolute; opacity: 0; top: 0; left: 0; width: 100%; height: 100%;">
<iframe src="https://stockease.com/api/delete-product?id=123"></iframe>
<!-- Invisible iframe on top -->
</div>
<!-- Visible content below -->
<div style="position: relative; z-index: -1; padding: 100px;">
<h1>Free Prize! Click Here to Claim</h1>
<button>Click Me!</button>
<!-- User clicks visible button -->
<!-- Actually clicks hidden iframe button -->
<!-- Accidental API call executed β -->
</div>X-Frame-Options Header
X-Frame-Options: DENY
Possible values:
| Value | Effect |
|---|---|
DENY |
Cannot be embedded in ANY iframe |
SAMEORIGIN |
Can embed in iframe from same origin |
ALLOW-FROM origin |
Can embed in iframe from specific origin (deprecated) |
How it works:
Browser loads: <iframe src="https://stockease.com">
Check: X-Frame-Options header
ββ DENY β Refuse to load in iframe β
ββ SAMEORIGIN β Check if same origin
β ββ iframe is from same origin β Load β
β ββ iframe is from different origin β Refuse β
ββ ALLOW-FROM β Check if from allowed origin
Current Configuration
add_header X-Frame-Options "DENY" always;
Status: β Correctly configured (most restrictive)
Implications:
- StockEase cannot be embedded in iframes (anywhere)
- Prevents clickjacking completely
- May affect some legitimate use cases (e.g., dashboard widgets)
Adjust if Needed
Allow same-origin iframes:
add_header X-Frame-Options "SAMEORIGIN" always;
Use case: Admin dashboard in iframe on same domain
X-XSS-Protection: Legacy XSS Protection
What is X-XSS-Protection?
Legacy header for XSS protection in older browsers (IE, older Chrome):
X-XSS-Protection: 1; mode=block
Modes:
| Value | Effect |
|---|---|
0 |
Disable XSS protection |
1 |
Enable XSS protection |
1; mode=block |
Block page if XSS detected |
1; report=uri |
Report to URI if XSS detected |
How it works:
Browser detects XSS (reflected):
ββ With "1; mode=block" β Don't render page β
ββ With "1" β Sanitize and render β οΈ
Current Configuration
add_header X-XSS-Protection "1; mode=block" always;
Status: β Correctly configured
Modern browsers: Ignore this header (use CSP
instead)
Legacy browsers: Benefit from protection
Referrer-Policy: Prevent Referrer Leakage
What is Referrer Leakage?
When user navigates from one site to another, browser sends
Referer header:
User on https://stockease.com/admin/secret-page
Clicks link to https://example.com
Browser sends:
Referer: https://stockease.com/admin/secret-page
Attacker now knows:
ββ User visited StockEase
ββ They viewed /admin/secret-page
Sensitive data in URLs:
https://stockease.com/user/settings?email=john@example.com&password=temp123
^ ^ Leaks email and password to next site!
Referrer-Policy Header
Referrer-Policy: strict-origin-when-cross-origin
Possible values:
| Policy | HTTPSβHTTPS | HTTPSβHTTP | HTTPβHTTPS | HTTPβHTTP |
|---|---|---|---|---|
| no-referrer | None | None | None | None |
| strict-origin | Origin | None | Origin | Origin |
| no-referrer-when-downgrade | Full | None | Full | Full |
| same-origin | Full | None | None | Full |
| origin | Origin | Origin | Origin | Origin |
| strict-origin-when-cross-origin | Full | None | Origin | Full |
Current Configuration
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
What it does:
- HTTPS to HTTPS: Send full URL
- HTTPS to HTTP: Send nothing (prevent downgrade)
- HTTP to HTTPS: Send origin only
- HTTP to HTTP: Send full URL
Status: β Good balance of privacy and functionality
HSTS (HTTP Strict Transport Security)
What is HSTS?
HSTS forces browsers to use HTTPS only:
Strict-Transport-Security: max-age=31536000; includeSubDomains
How HSTS Works
Browser visits: http://stockease.com (plain HTTP)
Server responds:
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
Browser remembers:
ββ For 31536000 seconds (1 year)
ββ Always use HTTPS for this domain
ββ Apply to all subdomains too
Next request to: http://stockease.com
ββ Before HSTS: Browser sends HTTP β
ββ With HSTS: Browser upgrades to HTTPS β
HSTS Directives
| Directive | Purpose | Example |
|---|---|---|
max-age |
How long to enforce (seconds) | 31536000 (1 year) |
includeSubDomains |
Apply to all subdomains | Recommended |
preload |
Add to HSTS preload list | Optional (stricter) |
Current Configuration
β NOT CONFIGURED
StockEase should add:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HSTS Benefits
β Prevents downgrade attacks
- Attacker cannot force HTTP
- Forces HTTPS upgrade automatically
β Improves performance
- Skips HTTP request (goes straight to HTTPS)
- Reduces one round-trip
β Enhances security
- Protects against MITM (Man-in-the-Middle)
- Works even if first visit is HTTP
HSTS Risks
β οΈ max-age too long
- If you stop using HTTPS, users stuck
- Can't access site until max-age expires
β οΈ includeSubDomains
- All subdomains must support HTTPS
- Enforce consistently
Recommended Implementation
Start conservative:
# 2 weeks (test period)
add_header Strict-Transport-Security "max-age=1209600" always;
After testing:
# 1 month (adjust when comfortable)
add_header Strict-Transport-Security "max-age=2592000" always;
Production (final):
# 1 year (after confirming HTTPS is permanent)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Recommended Headers for Production
Enhanced Configuration
# =========================================================================
# Enhanced Security Headers (Recommended)
# =========================================================================
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# Prevent clickjacking
add_header X-Frame-Options "DENY" always;
# Legacy XSS protection (old browsers)
add_header X-XSS-Protection "1; mode=block" always;
# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Force HTTPS (only after full HTTPS migration)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Content Security Policy (see CSP documentation)
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com data:;
img-src 'self' data: https:;
connect-src 'self' https://api.stockease.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
" always;
# Additional security headers
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
nginx Configuration Locations
File Location
ops/nginx/nginx.conf
ββ Server block
β ββ Security headers
β ββ Caching rules
β ββ Compression
β ββ Routing
Current Security Section
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
# ... SPA routing, caching, compression ...
# =========================================================================
# Security Headers
# =========================================================================
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# =========================================================================
# Error Pages
# =========================================================================
error_page 404 /index.html;
}
Testing Security Headers
Using curl
# Get all response headers
curl -i https://frontend.stockease.com
# Get specific header
curl -i https://frontend.stockease.com | grep -i "X-Frame-Options"
# Output:
# X-Frame-Options: DENYUsing Online Tools
Expected Output
Checking https://frontend.stockease.com
Headers found:
β X-Content-Type-Options: nosniff
β X-XSS-Protection: 1; mode=block
β X-Frame-Options: DENY
β Referrer-Policy: strict-origin-when-cross-origin
β Strict-Transport-Security: MISSING (recommended)
β Content-Security-Policy: MISSING (recommended)
Score: A (good)
Grade: B+ (add HSTS and CSP for A+)
Common Issues & Solutions
Issue 1: HSTS Causes Redirect Loop
Problem:
Browser tries: http://stockease.com
HSTS forces: https://stockease.com
nginx redirects: http://stockease.com
Loop β
Cause: HSTS set but no HTTPβHTTPS redirect in nginx
Solution:
# Ensure HTTP listener redirects
server {
listen 80;
server_name _;
return 301 https://$server_name$request_uri;
}
# HTTPS server with headers
server {
listen 443 ssl http2;
server_name _;
add_header Strict-Transport-Security "max-age=31536000" always;
}
Issue 2: DENY Too Restrictive
Problem: Legitimate admin dashboard cannot embed in iframe
Solution:
# Allow same-origin embedding
add_header X-Frame-Options "SAMEORIGIN" always;
Issue 3: Headers Not Appearing
Problem:
curl -i https://stockease.com | grep "X-Frame-Options"
# No outputCause:
- nginx not reloaded after config change
- Headers only added for specific routes
Solution:
# Reload nginx
docker exec nginx nginx -s reload
# Or restart container
docker restart nginx-container
# Verify
curl -i https://stockease.com | head -20Best Practices
β DO:
- β Include all recommended security headers
- β
Set
alwaysflag (even for error responses) - β Test headers before production
- β Document why each header is needed
- β Keep headers consistent across all pages
- β Monitor header compliance
β DON'T:
- β Disable security headers for convenience
- β Set max-age too high initially
- β Forget to reload nginx after config changes
- β Ignore header warnings
- β Set conflicting headers
Related Files
- nginx Config:
ops/nginx/nginx.conf - Docker Setup:
Dockerfile - CI/CD:
.github/workflows/deploy-frontend.yml - CSP Documentation: See Frontend Security - CSP
References
- OWASP Security Headers Cheat Sheet
- MDN HTTP Headers
- nginx Documentation
- Security Headers Scanner
- HSTS Preload List
Last Updated: November 13, 2025
Status: Partially Configured (HSTS & CSP
Recommended)
Priority: High (Defense against common
attacks)