Skip to content

Security Headers & Content Security Policy

Security headers and Content Security Policy (CSP) are essential for protecting your web application from common attacks like cross-site scripting (XSS), clickjacking, and man-in-the-middle attacks. CloudFront allows you to configure these headers at the edge, ensuring they’re applied to all responses before they reach your users.

Security headers provide an additional layer of protection for your application:

  • Content Security Policy (CSP): Prevents XSS attacks by controlling which resources can be loaded
  • Strict-Transport-Security (HSTS): Forces browsers to use HTTPS connections
  • X-Frame-Options: Prevents clickjacking attacks
  • X-Content-Type-Options: Prevents MIME type sniffing
  • Referrer-Policy: Controls how much referrer information is sent with requests

CloudFront uses ResponseHeadersPolicy to add security headers to responses. You can create a custom policy and apply it to your CloudFront distribution’s default behavior.

Here’s a basic example of creating a ResponseHeadersPolicy with CSP:

lib/astro-site-stack.ts
import { Stack } from "aws-cdk-lib"
import type { StackProps } from "aws-cdk-lib"
import { Construct } from "constructs"
import { AstroAWS } from "@astro-aws/constructs"
import { ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront"
export class AstroSiteStack extends Stack {
public constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props)
const securityHeadersPolicy = new ResponseHeadersPolicy(
this,
"SecurityHeadersPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';",
override: true,
},
},
},
)
new AstroAWS(this, "AstroAWS", {
websiteDir: "../my-astro-project",
cdk: {
cloudfrontDistribution: {
defaultBehavior: {
responseHeadersPolicy: securityHeadersPolicy,
},
},
},
})
}
}

Content Security Policy (CSP) is one of the most important security headers. It controls which resources can be loaded and executed on your pages.

Common CSP directives include:

  • default-src: Fallback for other fetch directives
  • script-src: Controls which scripts can be executed
  • style-src: Controls which stylesheets can be applied
  • img-src: Controls which images can be loaded
  • connect-src: Controls which URLs can be loaded via fetch, XMLHttpRequest, WebSocket, etc.
  • font-src: Controls which fonts can be loaded
  • frame-src: Controls which URLs can be embedded as frames
  • upgrade-insecure-requests: Upgrades HTTP requests to HTTPS

For maximum security, use a strict CSP policy that only allows resources from your own domain:

const strictSecurityPolicy = new ResponseHeadersPolicy(
this,
"StrictSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests;",
override: true,
},
},
},
)

If your application needs to load resources from external domains (CDNs, APIs, etc.), you can include them in your CSP:

const cspWithExternalResources = new ResponseHeadersPolicy(
this,
"CSPWithExternalResources",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; img-src 'self' data: https:; upgrade-insecure-requests;",
override: true,
},
},
},
)

If you’re using AWS services like CloudWatch RUM or Cognito, you’ll need to include their domains:

const cspWithAWSServices = new ResponseHeadersPolicy(
this,
"CSPWithAWSServices",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self' cognito-identity.us-east-1.amazonaws.com dataplane.rum.us-east-1.amazonaws.com; upgrade-insecure-requests;",
override: true,
},
},
},
)

Note: The example above includes 'unsafe-inline' for styles and scripts, which is common for Astro applications but reduces security. Consider using nonces or hashes for better security in production.

In addition to CSP, CloudFront’s ResponseHeadersPolicy supports other security headers through the securityHeadersBehavior property.

Here’s an example that includes multiple security headers:

lib/astro-site-stack.ts
import { Stack, Duration } from "aws-cdk-lib"
import type { StackProps } from "aws-cdk-lib"
import { Construct } from "constructs"
import { AstroAWS } from "@astro-aws/constructs"
import { ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront"
export class AstroSiteStack extends Stack {
public constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props)
const comprehensiveSecurityPolicy = new ResponseHeadersPolicy(
this,
"ComprehensiveSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self'; upgrade-insecure-requests;",
override: true,
},
strictTransportSecurity: {
accessControlMaxAge: Duration.seconds(31536000), // 1 year
includeSubdomains: true,
override: true,
},
contentTypeOptions: {
override: true,
},
frameOptions: {
frameOption: "DENY",
override: true,
},
referrerPolicy: {
referrerPolicy: "strict-origin-when-cross-origin",
override: true,
},
},
},
)
new AstroAWS(this, "AstroAWS", {
websiteDir: "../my-astro-project",
cdk: {
cloudfrontDistribution: {
defaultBehavior: {
responseHeadersPolicy: comprehensiveSecurityPolicy,
},
},
},
})
}
}
  • strictTransportSecurity: Configures HSTS (HTTP Strict Transport Security)
    • accessControlMaxAge: How long browsers should remember to use HTTPS
    • includeSubdomains: Whether to apply to subdomains
  • contentTypeOptions: Prevents MIME type sniffing
  • frameOptions: Prevents clickjacking (DENY or SAMEORIGIN)
  • referrerPolicy: Controls referrer information (no-referrer, strict-origin-when-cross-origin, etc.)

For development environments, you might want a more permissive CSP:

const devSecurityPolicy = new ResponseHeadersPolicy(this, "DevSecurityPolicy", {
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' *; img-src 'self' data: *;",
override: true,
},
},
})

For production, use a strict policy:

const prodSecurityPolicy = new ResponseHeadersPolicy(
this,
"ProdSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;",
override: true,
},
},
},
)

If you’re using analytics services like Google Analytics or Plausible:

const analyticsSecurityPolicy = new ResponseHeadersPolicy(
this,
"AnalyticsSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; script-src 'self' https://www.googletagmanager.com https://plausible.io; connect-src 'self' https://www.google-analytics.com https://plausible.io; img-src 'self' data: https:;",
override: true,
},
},
},
)

You can apply different security policies to different CloudFront behaviors. For example, you might want stricter policies for your main site and different policies for API routes:

lib/astro-site-stack.ts
import { Stack } from "aws-cdk-lib"
import type { StackProps } from "aws-cdk-lib"
import { Construct } from "constructs"
import { AstroAWS } from "@astro-aws/constructs"
import { ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront"
export class AstroSiteStack extends Stack {
public constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props)
const defaultSecurityPolicy = new ResponseHeadersPolicy(
this,
"DefaultSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';",
override: true,
},
},
},
)
const apiSecurityPolicy = new ResponseHeadersPolicy(
this,
"ApiSecurityPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy: "default-src 'self';",
override: true,
},
},
},
)
new AstroAWS(this, "AstroAWS", {
websiteDir: "../my-astro-project",
cdk: {
cloudfrontDistribution: {
defaultBehavior: {
responseHeadersPolicy: defaultSecurityPolicy,
},
apiBehavior: {
responseHeadersPolicy: apiSecurityPolicy,
},
},
},
})
}
}

After deploying your stack, you can verify that security headers are being applied correctly.

  1. Open your website in a browser
  2. Open Developer Tools (F12)
  3. Go to the Network tab
  4. Reload the page
  5. Click on any request
  6. Check the Response Headers section

You should see headers like:

  • Content-Security-Policy
  • Strict-Transport-Security
  • X-Frame-Options
  • X-Content-Type-Options

You can test headers using curl:

Terminal window
curl -I https://your-domain.com

Look for security headers in the response.

Several online tools can help you test your security headers:

These tools will scan your site and provide a security score along with recommendations.

  1. Start Strict: Begin with a strict CSP and relax it only when necessary
  2. Use Nonces or Hashes: Instead of 'unsafe-inline', use nonces or hashes for inline scripts and styles
  3. Test Thoroughly: Test your CSP in development before deploying to production
  4. Monitor Violations: Use CSP reporting to catch violations (report-uri or report-to)
  5. Keep Updated: Review and update your security headers regularly
  6. Environment-Specific: Use different policies for development, staging, and production

Here’s a complete example combining all the concepts:

lib/astro-site-stack.ts
import { Stack, Duration } from "aws-cdk-lib"
import type { StackProps } from "aws-cdk-lib"
import { Construct } from "constructs"
import { AstroAWS } from "@astro-aws/constructs"
import {
ResponseHeadersPolicy,
ViewerProtocolPolicy,
} from "aws-cdk-lib/aws-cloudfront"
export class AstroSiteStack extends Stack {
public constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props)
const securityHeadersPolicy = new ResponseHeadersPolicy(
this,
"SecurityHeadersPolicy",
{
securityHeadersBehavior: {
contentSecurityPolicy: {
contentSecurityPolicy:
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self' cognito-identity.us-east-1.amazonaws.com dataplane.rum.us-east-1.amazonaws.com; upgrade-insecure-requests;",
override: true,
},
strictTransportSecurity: {
accessControlMaxAge: Duration.seconds(31536000),
includeSubdomains: true,
override: true,
},
contentTypeOptions: {
override: true,
},
frameOptions: {
frameOption: "DENY",
override: true,
},
referrerPolicy: {
referrerPolicy: "strict-origin-when-cross-origin",
override: true,
},
},
},
)
new AstroAWS(this, "AstroAWS", {
websiteDir: "../my-astro-project",
cdk: {
cloudfrontDistribution: {
defaultBehavior: {
responseHeadersPolicy: securityHeadersPolicy,
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
},
},
})
}
}