Skip to content

Security Headers

HTTP security headers are an essential part of web application security. They instruct browsers how to handle your application’s content and help protect against common web vulnerabilities. Fluxbase automatically sets secure headers on all responses.

Fluxbase sets the following security headers by default:

HeaderValuePurpose
Content-Security-PolicyRestrictive policyPrevents XSS attacks
X-Frame-OptionsDENYPrevents clickjacking
X-Content-Type-OptionsnosniffPrevents MIME sniffing
X-XSS-Protection1; mode=blockLegacy XSS protection
Strict-Transport-Securitymax-age=31536000Forces HTTPS
Referrer-Policystrict-origin-when-cross-originControls referrer information
Permissions-PolicyRestrictive policyControls browser features

CSP is the most powerful security header, preventing XSS attacks by controlling which resources can be loaded.

Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self' data:;
connect-src 'self' ws: wss:;
frame-ancestors 'none'
  • default-src ‘self’: Only load resources from same origin
  • script-src: JavaScript sources (includes ‘unsafe-inline’ and ‘unsafe-eval’ for Admin UI)
  • style-src: CSS sources (includes ‘unsafe-inline’ for Admin UI)
  • img-src: Image sources (includes data: URLs and blob:)
  • font-src: Font sources
  • connect-src: AJAX, WebSocket, and EventSource connections (includes ws: and wss: for realtime)
  • frame-ancestors ‘none’: Prevents page from being embedded in frames

Via fluxbase.yaml:

security:
headers:
content_security_policy: >
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://fonts.googleapis.com;
img-src 'self' https: data:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' wss://realtime.example.com;
frame-ancestors 'none'

Via Environment Variable:

Terminal window
FLUXBASE_SECURITY_HEADERS_CSP="default-src 'self'; script-src 'self'"

If you’re hosting a React/Vue/Angular app, you may need to relax CSP:

security:
headers:
content_security_policy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' https: data: blob:;
connect-src 'self' ws: wss: https:;
frame-ancestors 'none'

⚠️ Warning: 'unsafe-inline' and 'unsafe-eval' reduce security. Use nonces or hashes for production:

<!-- Use nonce for inline scripts -->
<script nonce="random-nonce-here">
console.log("This script is allowed");
</script>

Use browser DevTools Console to see CSP violations:

Refused to load the script 'https://evil.com/script.js' because it violates
the following Content Security Policy directive: "script-src 'self'"

CSP Report URI (optional):

security:
headers:
content_security_policy: >
default-src 'self';
report-uri /api/v1/csp-report;
report-to csp-endpoint

Prevents your site from being embedded in an iframe, protecting against clickjacking attacks.

X-Frame-Options: DENY
security:
headers:
x_frame_options: "DENY" # Never allow framing
# OR
x_frame_options: "SAMEORIGIN" # Allow framing from same origin
# OR
x_frame_options: "ALLOW-FROM https://trusted.com" # Allow specific origin
  • DENY: Most secure, use for most applications
  • SAMEORIGIN: Use if you need to iframe your own content
  • ALLOW-FROM: Use for specific trusted partners (deprecated, use CSP frame-ancestors instead)
security:
headers:
content_security_policy: "frame-ancestors 'none'" # Equivalent to DENY
# OR
content_security_policy: "frame-ancestors 'self'" # Equivalent to SAMEORIGIN
# OR
content_security_policy: "frame-ancestors https://trusted.com" # Allow specific origin

Prevents browsers from MIME-sniffing responses, forcing them to respect the Content-Type header.

X-Content-Type-Options: nosniff

Without this header, browsers might execute JavaScript disguised as images:

<!-- Attacker uploads "image.jpg" that's actually JavaScript -->
<img src="/uploads/image.jpg" />
<!-- Browser might execute it as JS without nosniff -->

With nosniff, the browser will only execute files with Content-Type: application/javascript.

security:
headers:
x_content_type_options: "nosniff" # Always use this

Legacy header for older browsers to enable XSS filtering. Modern browsers rely on CSP instead.

X-XSS-Protection: 1; mode=block
security:
headers:
x_xss_protection: "1; mode=block" # Enable XSS filter and block
# OR
x_xss_protection: "1" # Enable XSS filter (sanitize)
# OR
x_xss_protection: "0" # Disable XSS filter

Instead of relying on X-XSS-Protection, use a strong Content Security Policy:

security:
headers:
content_security_policy: "default-src 'self'; script-src 'self'"
x_xss_protection: "0" # Disable legacy protection, rely on CSP

Forces browsers to only connect via HTTPS, preventing protocol downgrade attacks.

Strict-Transport-Security: max-age=31536000; includeSubDomains
security:
headers:
strict_transport_security: "max-age=31536000; includeSubDomains; preload"
  • max-age: Duration in seconds (31536000 = 1 year)
  • includeSubDomains: Apply to all subdomains
  • preload: Eligible for HSTS preload list

Submit your domain to the HSTS Preload List to be hardcoded into browsers:

security:
headers:
strict_transport_security: "max-age=63072000; includeSubDomains; preload"

Requirements:

  1. Valid TLS certificate
  2. Redirect all HTTP to HTTPS
  3. Serve HSTS header on base domain
  4. Set max-age to at least 1 year
  5. Include includeSubDomains
  6. Include preload directive

⚠️ Warning: Once preloaded, removal takes months. Test thoroughly first!

HSTS header is only sent on HTTPS connections:

server:
tls:
enabled: true
cert_file: /path/to/cert.pem
key_file: /path/to/key.pem
security:
headers:
strict_transport_security: "max-age=31536000; includeSubDomains"

Controls how much referrer information is sent with requests.

Referrer-Policy: strict-origin-when-cross-origin
security:
headers:
referrer_policy: "no-referrer" # Never send referrer
# OR
referrer_policy: "no-referrer-when-downgrade" # Don't send on HTTPS→HTTP
# OR
referrer_policy: "same-origin" # Only send to same origin
# OR
referrer_policy: "origin" # Only send origin (not full URL)
# OR
referrer_policy: "strict-origin" # Origin only, not on HTTPS→HTTP
# OR
referrer_policy: "origin-when-cross-origin" # Full URL same-origin, origin cross-origin
# OR
referrer_policy: "strict-origin-when-cross-origin" # Balanced approach (default)
# OR
referrer_policy: "unsafe-url" # Always send full URL (not recommended)
PolicySame-OriginCross-Origin HTTPSCross-Origin HTTP
no-referrer
same-origin✅ Full URL
origin✅ Origin only✅ Origin only✅ Origin only
strict-origin✅ Origin only✅ Origin only
strict-origin-when-cross-origin✅ Full URL✅ Origin only

Maximum Privacy:

referrer_policy: "no-referrer"

Analytics-Friendly:

referrer_policy: "strict-origin-when-cross-origin" # Default

Internal Links Only:

referrer_policy: "same-origin"

Controls which browser features and APIs can be used (formerly Feature-Policy).

Permissions-Policy: geolocation=(), microphone=(), camera=()
security:
headers:
permissions_policy: >
geolocation=(),
microphone=(),
camera=(),
payment=(),
usb=(),
magnetometer=(),
gyroscope=(),
accelerometer=()

Common features you can control:

  • geolocation: GPS location
  • camera: Camera access
  • microphone: Microphone access
  • payment: Payment Request API
  • usb: WebUSB API
  • bluetooth: Web Bluetooth API
  • midi: Web MIDI API
  • fullscreen: Fullscreen API
  • picture-in-picture: Picture-in-Picture API
  • display-capture: Screen capture
  • autoplay: Media autoplay
# Deny all origins (most secure)
permissions_policy: "geolocation=()"
# Allow same origin only
permissions_policy: "geolocation=(self)"
# Allow specific origins
permissions_policy: "geolocation=(self 'https://trusted.com')"
# Allow all origins (not recommended)
permissions_policy: "geolocation=*"
security:
headers:
permissions_policy: >
geolocation=(self),
camera=(self),
microphone=(self),
payment=(self 'https://payment-provider.com'),
usb=(),
bluetooth=()

fluxbase.yaml
server:
port: 443
tls:
enabled: true
cert_file: /etc/letsencrypt/live/example.com/fullchain.pem
key_file: /etc/letsencrypt/live/example.com/privkey.pem
security:
headers:
# Content Security Policy
content_security_policy: >
default-src 'self';
script-src 'self';
style-src 'self' https://fonts.googleapis.com;
img-src 'self' https: data: blob:;
font-src 'self' data: https://fonts.gstatic.com;
connect-src 'self' wss://example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
# Clickjacking protection
x_frame_options: "DENY"
# MIME sniffing protection
x_content_type_options: "nosniff"
# XSS protection (legacy)
x_xss_protection: "1; mode=block"
# Force HTTPS (1 year, include subdomains, preload)
strict_transport_security: "max-age=31536000; includeSubDomains; preload"
# Referrer policy (balanced)
referrer_policy: "strict-origin-when-cross-origin"
# Permissions policy (restrict sensitive features)
permissions_policy: >
geolocation=(),
microphone=(),
camera=(),
payment=(),
usb=(),
bluetooth=()
fluxbase.yaml
security:
headers:
# Relaxed CSP for development
content_security_policy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' https: data: blob:;
connect-src 'self' ws: wss: http: https:;
frame-ancestors 'self'
x_frame_options: "SAMEORIGIN"
x_content_type_options: "nosniff"
x_xss_protection: "1; mode=block"
# No HSTS in development (HTTP allowed)
strict_transport_security: ""
referrer_policy: "no-referrer-when-downgrade"
permissions_policy: "" # Allow all in development

Terminal window
# Check all headers
curl -I https://yourapp.com/
# Check specific header
curl -I https://yourapp.com/ | grep -i "content-security-policy"
  1. Security Headers (https://securityheaders.com/)

    • Comprehensive security header analysis
    • Letter grade rating
    • Recommendations for improvement
  2. Mozilla Observatory (https://observatory.mozilla.org/)

    • Security and privacy analysis
    • Detailed scoring
    • Specific recommendations
  3. SSL Labs (https://www.ssllabs.com/ssltest/)

    • TLS/SSL configuration testing
    • HSTS validation
    • Certificate chain analysis
import { describe, it, expect } from "vitest";
describe("Security Headers", () => {
it("should set Content-Security-Policy", async () => {
const response = await fetch("https://yourapp.com/");
expect(response.headers.get("content-security-policy")).toContain(
"default-src 'self'"
);
});
it("should set X-Frame-Options", async () => {
const response = await fetch("https://yourapp.com/");
expect(response.headers.get("x-frame-options")).toBe("DENY");
});
it("should set HSTS on HTTPS", async () => {
const response = await fetch("https://yourapp.com/");
expect(response.headers.get("strict-transport-security")).toContain(
"max-age="
);
});
});

Symptom: Resources failing to load, console errors

Solution: Add specific origins to CSP:

security:
headers:
content_security_policy: >
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://fonts.googleapis.com

Symptom: React/Vue app broken, CSP violations

Solution: Use relaxed CSP for Admin UI:

security:
headers:
# Admin UI needs 'unsafe-inline' and 'unsafe-eval'
content_security_policy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline'

Or use route-specific headers:

// Apply relaxed headers only to Admin UI routes
app.Use("/admin", AdminUISecurityHeaders())

Symptom: iframes, embedded videos failing

Solution: Update CSP frame-src:

security:
headers:
content_security_policy: >
default-src 'self';
frame-src https://www.youtube.com https://player.vimeo.com

Symptom: Realtime features not working

Solution: Add ws: and wss: to connect-src:

security:
headers:
content_security_policy: >
default-src 'self';
connect-src 'self' ws: wss:

# Start with most restrictive policy
content_security_policy: "default-src 'self'"
# Add specific exceptions as needed
content_security_policy: >
default-src 'self';
img-src 'self' https://cdn.example.com
# Test CSP without breaking functionality
Content-Security-Policy-Report-Only: default-src 'self'

3. Avoid ‘unsafe-inline’ and ‘unsafe-eval’

Section titled “3. Avoid ‘unsafe-inline’ and ‘unsafe-eval’”

Use nonces or hashes instead:

<!-- Generate random nonce per request -->
<script nonce="2726c7f26c">
// Inline script allowed
</script>
content_security_policy: "script-src 'nonce-2726c7f26c'"

Set up reporting:

content_security_policy: >
default-src 'self';
report-uri /api/v1/csp-report
// Log CSP violations
app.Post("/api/v1/csp-report", func(c *fiber.Ctx) error {
var report map[string]interface{}
c.BodyParser(&report)
log.Warn().Interface("csp_violation", report).Msg("CSP violation reported")
return c.SendStatus(204)
})

Different browsers have different CSP support:

  • Test on Chrome, Firefox, Safari, Edge
  • Check mobile browsers (iOS Safari, Chrome Mobile)
  • Verify old browser fallbacks
# Document why each exception is needed
content_security_policy: >
default-src 'self';
script-src 'self' https://cdn.example.com; # Third-party analytics
style-src 'self' 'unsafe-inline'; # Required for Admin UI
img-src 'self' https:; # User-uploaded images from CDN

  • Content-Security-Policy configured
  • X-Frame-Options set to DENY or SAMEORIGIN
  • X-Content-Type-Options set to nosniff
  • HSTS enabled with appropriate max-age
  • Referrer-Policy configured
  • Permissions-Policy restricts unnecessary features
  • Tested on securityheaders.com (A+ rating)
  • Tested on Mozilla Observatory (A+ rating)
  • CSP violations monitored
  • Headers documented and reviewed


Security headers are a critical defense layer:

  • Content Security Policy - Prevents XSS attacks
  • X-Frame-Options - Prevents clickjacking
  • X-Content-Type-Options - Prevents MIME sniffing
  • HSTS - Forces HTTPS
  • Referrer-Policy - Controls referrer information
  • Permissions-Policy - Restricts browser features

Fluxbase sets secure defaults, but customize them for your specific needs. Test thoroughly and monitor for violations.