# Security Headers Audit

> Use when reviewing HTTP security headers, checking a Content-Security-Policy, auditing CORS or HSTS configuration, evaluating X-Frame-Options or Permissions-Policy, inspecting header middleware, or hardening a web application's response headers.

- **Type:** Skill
- **Install:** `agentstack add skill-kalshamsi-claude-security-skills-security-headers-audit`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [kalshamsi](https://agentstack.voostack.com/s/kalshamsi)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kalshamsi](https://github.com/kalshamsi)
- **Source:** https://github.com/kalshamsi/claude-security-skills/tree/main/skills/security-headers-audit

## Install

```sh
agentstack add skill-kalshamsi-claude-security-skills-security-headers-audit
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Security Headers Audit

This skill performs static code analysis for HTTP security header misconfigurations across Express/Helmet.js, Nginx, Apache, Next.js, Flask, Django, and Spring Boot projects. HTTP response headers are the first line of defence against a wide class of client-side attacks — clickjacking, MIME-sniffing, cross-site scripting amplification, cross-origin data leakage, and protocol downgrade attacks. A single missing or misconfigured header can expose users to attacks that a compliant browser would otherwise block. This skill audits 10+ header-level controls, maps each finding to CWE and OWASP Top 10:2021 identifiers, and produces UNSAFE/SAFE code pairs across multiple frameworks so developers can apply fixes immediately.

## When to Use

- When the user asks to "audit security headers", "check HTTP headers", "review header config", or "harden web headers"
- When the user mentions "CSP", "Content-Security-Policy", "unsafe-inline", "unsafe-eval", or "CSP report-only"
- When the user asks about "CORS", "Access-Control-Allow-Origin", or "cross-origin policy"
- When the user asks about "HSTS", "Strict-Transport-Security", "HTTPS enforcement", or "preload"
- When the user asks about "X-Frame-Options", "clickjacking protection", or "frame-ancestors"
- When the user asks about "X-Content-Type-Options", "MIME sniffing", or "nosniff"
- When the user asks about "Referrer-Policy", "Permissions-Policy", or "Feature-Policy"
- When reviewing Express middleware, Nginx server blocks, Apache VirtualHost configs, Flask response objects, or Spring Boot security config
- When preparing a web application for a security audit, penetration test, or compliance review (PCI-DSS, HIPAA, FedRAMP)
- When a pull request modifies server configuration, middleware stacks, or HTTP response handling

## When NOT to Use

- When the user wants runtime testing of live server responses (use a DAST tool such as `dast-nuclei`, OWASP ZAP, or `ffuf-web-fuzzing`)
- When the issue is application logic — authentication bypasses, SQL injection, or broken access control — rather than header configuration
- When the user wants a full vulnerability scan including code-level SAST (use `bandit-sast` or `security-review`)
- When the `owasp-security` skill already covers the request at a broader OWASP level
- When the user asks about **mobile security, certificate pinning, or mobile app issues** — you **MUST** decline and recommend `mobile-security`
- When the user asks about **SQL injection, XSS, or input validation** in application code — you **MUST** decline and recommend `security-review` or `bandit-sast`

## Prerequisites

### Tool Installed (Preferred)

No external tool required. This is a pure analysis skill.

All 10+ checks are performed through pattern matching and code inspection of server configuration files, middleware definitions, and application code — no CLI tool needs to be installed, configured, or invoked. The skill works offline and requires no API keys.

### Tool Not Installed (Fallback)

This skill is always available as a pure analysis skill. There is no fallback mode because there is no external tool dependency. All checks run directly through code review and pattern analysis.

## Workflow

1. **Detect server framework** — Inspect project files to determine which frameworks set HTTP headers:
   - `package.json` containing `helmet`, `cors`, `express` → Express/Helmet.js
   - `nginx.conf` or `sites-available/` directory → Nginx
   - `.htaccess` or `httpd.conf` or `apache2.conf` → Apache
   - `next.config.js` or `next.config.ts` → Next.js
   - `requirements.txt` containing `Flask` or `flask-talisman` → Flask
   - `requirements.txt` containing `Django` or `django-csp` → Django
   - `pom.xml` containing `spring-boot` or `spring-security` → Spring Boot
2. **Identify header-relevant files** — Locate the files most likely to contain header configuration:
   - Express: `app.js`, `server.js`, `middleware/`, any file calling `app.use()`
   - Nginx: `nginx.conf`, `conf.d/*.conf`, `sites-enabled/*`
   - Apache: `httpd.conf`, `apache2.conf`, `.htaccess`, `VirtualHost` blocks
   - Next.js: `next.config.js`, `middleware.ts`, API route handlers
   - Flask: `app.py`, `__init__.py`, any `@app.after_request` hook
   - Django: `settings.py`, `middleware.py`, `MIDDLEWARE` list
   - Spring Boot: `SecurityConfig.java`, `WebMvcConfigurer` implementations
3. **Run all 10+ security header checks** (see Checks section) against each identified file.
4. **For each finding:**
   a. Determine severity (Critical / High / Medium / Low) using the Reference Tables
   b. Map to the relevant CWE identifier
   c. Map to the relevant OWASP Top 10:2021 category
   d. Record file path and approximate line number or config block
   e. Document the UNSAFE configuration observed and the corresponding SAFE fix
   f. Draft a remediation recommendation with framework-specific code
5. **Deduplicate and sort** findings by severity: Critical > High > Medium > Low.
6. **Generate the findings report** using the Findings Format below.
7. **Summarize** — State total findings, breakdown by severity, and top 3 remediation priorities.

## Checks

---

### Check 1: Missing or Misconfigured Content-Security-Policy (CSP)

**CWE-693** (Protection Mechanism Failure) | **A05:2021** - Security Misconfiguration | Severity: **High**

**WHY:** A Content-Security-Policy header instructs the browser to only load resources from trusted origins, blocking inline script injection, data-URI attacks, and third-party resource hijacking. Without CSP, a successful XSS attack has unrestricted access. Directives such as `unsafe-inline` and `unsafe-eval` negate the policy's XSS protection entirely, and wildcard sources (`*`) allow an attacker to load resources from any domain they control.

**UNSAFE:**

```javascript
// Express — no CSP header set at all
const express = require('express');
const app = express();
// No helmet() or manual Content-Security-Policy — CSP missing entirely
app.get('/', (req, res) => res.send('...'));
```

```javascript
// Express/Helmet — CSP disabled or using unsafe directives
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'", '*'],          // wildcard allows any origin
    scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],  // XSS protection nullified
    styleSrc: ["'self'", "'unsafe-inline'"],
  }
}));
```

```nginx
# Nginx — no CSP header in server block
server {
    listen 443 ssl;
    server_name example.com;
    # Missing: add_header Content-Security-Policy "...";
}
```

```python
# Flask — no CSP header on responses
@app.route('/')
def index():
    return render_template('index.html')
    # No Content-Security-Policy header added
```

**SAFE:**

```javascript
// Express/Helmet — strict CSP with nonce-based inline script allowance
const helmet = require('helmet');
const crypto = require('crypto');

app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomBytes(16).toString('hex');
  next();
});

app.use((req, res, next) => {
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
      styleSrc: ["'self'"],
      imgSrc: ["'self'", 'data:', 'https://cdn.example.com'],
      connectSrc: ["'self'", 'https://api.example.com'],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  })(req, res, next);
});
```

```nginx
# Nginx — strict CSP header
server {
    listen 443 ssl;
    server_name example.com;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; upgrade-insecure-requests;" always;
}
```

---

### Check 2: Permissive CORS Policy (Access-Control-Allow-Origin: *)

**CWE-942** (Permissive Cross-Origin Resource Sharing Policy) | **A05:2021** - Security Misconfiguration | Severity: **High**

**WHY:** Setting `Access-Control-Allow-Origin: *` allows any website on the internet to make cross-origin requests to the API and read the response. For APIs serving authenticated data, this combined with `Access-Control-Allow-Credentials: true` permits session-riding attacks where a malicious page reads sensitive user data. Even without credentials, wildcard CORS on internal APIs exposes business data to any third-party site.

**UNSAFE:**

```javascript
// Express — wildcard CORS open to all origins
const cors = require('cors');
app.use(cors()); // defaults to origin: '*'

// Or explicitly:
app.use(cors({ origin: '*' }));
```

```javascript
// Express — manual wildcard header
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  next();
});
```

```nginx
# Nginx — wildcard CORS
location /api/ {
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
}
```

```python
# Flask — wildcard CORS via flask-cors
from flask_cors import CORS
CORS(app)  # defaults to origins='*'
```

**SAFE:**

```javascript
// Express — allowlist-based CORS
const cors = require('cors');
const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com',
];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,  // only if needed; never combine with wildcard origin
}));
```

```nginx
# Nginx — origin allowlist via map
map $http_origin $cors_origin {
    default "";
    "https://app.example.com" "$http_origin";
    "https://admin.example.com" "$http_origin";
}
server {
    location /api/ {
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
    }
}
```

```python
# Flask — explicit origin allowlist
from flask_cors import CORS
CORS(app, origins=['https://app.example.com', 'https://admin.example.com'])
```

---

### Check 3: Missing or Weak HTTP Strict-Transport-Security (HSTS)

**CWE-319** (Cleartext Transmission of Sensitive Information) | **A02:2021** - Cryptographic Failures | Severity: **High**

**WHY:** Without HSTS, a browser that first visits a site over HTTP is vulnerable to SSL-stripping attacks. An on-path attacker intercepts the HTTP request before it can redirect to HTTPS, silently downgrading the session. HSTS instructs browsers to always connect over HTTPS for a specified duration. A `max-age` under 1 year is insufficient for meaningful protection; missing `includeSubDomains` leaves subdomains vulnerable; missing `preload` means first-visit HTTPS is not enforced by browser preload lists.

**UNSAFE:**

```javascript
// Express — HSTS missing entirely
const helmet = require('helmet');
app.use(helmet({
  hsts: false,  // HSTS explicitly disabled
}));
```

```javascript
// Express — HSTS with too-short max-age (1 day = trivially bypassable)
app.use(helmet.hsts({
  maxAge: 86400,  // 1 day — far too short; attacker can wait for expiry
  // Missing: includeSubDomains, preload
}));
```

```nginx
# Nginx — missing HSTS header entirely
server {
    listen 443 ssl;
    # No Strict-Transport-Security header
}
```

```python
# Flask — no HSTS enforcement
@app.route('/login', methods=['POST'])
def login():
    # No HSTS header — HTTP downgrade possible
    return jsonify({'token': generate_token()})
```

**SAFE:**

```javascript
// Express/Helmet — HSTS with 2-year max-age, includeSubDomains, preload
const helmet = require('helmet');
app.use(helmet.hsts({
  maxAge: 63072000,        // 2 years in seconds
  includeSubDomains: true,
  preload: true,
}));
```

```nginx
# Nginx — full HSTS header
server {
    listen 443 ssl;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
```

```python
# Flask — HSTS via after_request hook or flask-talisman
from flask_talisman import Talisman
Talisman(app,
    strict_transport_security=True,
    strict_transport_security_max_age=63072000,
    strict_transport_security_include_subdomains=True,
    strict_transport_security_preload=True,
)
```

---

### Check 4: Missing X-Content-Type-Options

**CWE-16** (Configuration) | **A05:2021** - Security Misconfiguration | Severity: **Medium**

**WHY:** Without `X-Content-Type-Options: nosniff`, browsers perform MIME-type sniffing — inferring the content type from the response body rather than the declared `Content-Type`. An attacker who can upload a file (e.g., a JPEG with embedded JavaScript) can trick the browser into executing it as a script, bypassing CSP. The `nosniff` directive prevents this by forcing strict MIME-type enforcement.

**UNSAFE:**

```javascript
// Express — X-Content-Type-Options header absent
const express = require('express');
const app = express();
// No helmet() — X-Content-Type-Options not sent
app.use(express.static('public'));
```

```nginx
# Nginx — X-Content-Type-Options not set
server {
    listen 443 ssl;
    root /var/www/html;
    # Missing: add_header X-Content-Type-Options "nosniff";
}
```

**SAFE:**

```javascript
// Express/Helmet — nosniff enabled (default in helmet())
const helmet = require('helmet');
app.use(helmet()); // X-Content-Type-Options: nosniff included by default

// Or manually:
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  next();
});
```

```nginx
# Nginx — nosniff header
server {
    listen 443 ssl;
    add_header X-Content-Type-Options "nosniff" always;
}
```

```python
# Flask — explicit nosniff header
@app.after_request
def set_security_headers(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    return response
```

---

### Check 5: Missing X-Frame-Options or Permissive CSP frame-ancestors

**CWE-1021** (Improper Restriction of Rendered UI Layers or Frames) | **A05:2021** - Security Misconfiguration | Severity: **Medium**

**WHY:** Without framing protection, an attacker can embed the target page in a hidden `` on a malicious site and trick authenticated users into performing actions they did not intend (clickjacking). `X-Frame-Options: DENY` or `SAMEORIGIN` prevents this. The modern equivalent is CSP `frame-ancestors 'none'` or `'self'`, which offers more granular control. Both should be set for maximum browser compatibility.

**UNSAFE:**

```javascript
// Express — no frame protection
const helmet = require('helmet');
app.use(helmet({
  frameguard: false,  // Clickjacking protection disabled
  contentSecurityPolicy: false,
}));
```

```nginx
# Nginx — X-Frame-Options absent
server {
    listen 443 ssl;
    # No X-Frame-Options or frame-ancestors directive
}
```

```python
# Flask — no X-Frame-Options header
@app.after_request
def headers(response):
    # X-Frame-Options not set — clickjacking possible
    return response
```

**SAFE:**

```javascript
// Express/Helmet — deny all framing
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));

// For CSP frame-ancestors (modern, preferred):
app.use(helmet.contentSecurityPolicy({
  directives: {
    frameAncestors: ["'none'"],
    // ... other directives
  }
}));
```

```nginx
# Nginx — X-Frame-Options + CSP frame-ancestors
server {
    listen 443 ssl;
    add_header X-Frame-Options "DENY" always;
    add_header Content-Security-Policy "frame-ancestors 'none';" always;
}
```

```python
# Flask — X-Frame-Options via after_request
@app.after_request
def set_security_headers(response):
    response.headers['X-Frame-Options'] = 'DENY'
    return response
```

---

### Check 6: Missing Referrer-Policy

**CWE-200** (Exposure of Sensitive Information to an Unauthorized Actor) | **A01:2021** - Broken Access Control | Severity: **Medium**

**WHY:** Without a `Referrer-Policy`, browsers send the full URL of the originating page in the `Referer` header when navigating to external sites. This

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kalshamsi](https://github.com/kalshamsi)
- **Source:** [kalshamsi/claude-security-skills](https://github.com/kalshamsi/claude-security-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kalshamsi-claude-security-skills-security-headers-audit
- Seller: https://agentstack.voostack.com/s/kalshamsi
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
