Install
$ agentstack add skill-kalshamsi-claude-security-skills-security-headers-audit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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, orffuf-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-sastorsecurity-review) - When the
owasp-securityskill 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-revieworbandit-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
- Detect server framework — Inspect project files to determine which frameworks set HTTP headers:
package.jsoncontaininghelmet,cors,express→ Express/Helmet.jsnginx.conforsites-available/directory → Nginx.htaccessorhttpd.conforapache2.conf→ Apachenext.config.jsornext.config.ts→ Next.jsrequirements.txtcontainingFlaskorflask-talisman→ Flaskrequirements.txtcontainingDjangoordjango-csp→ Djangopom.xmlcontainingspring-bootorspring-security→ Spring Boot
- Identify header-relevant files — Locate the files most likely to contain header configuration:
- Express:
app.js,server.js,middleware/, any file callingapp.use() - Nginx:
nginx.conf,conf.d/*.conf,sites-enabled/* - Apache:
httpd.conf,apache2.conf,.htaccess,VirtualHostblocks - Next.js:
next.config.js,middleware.ts, API route handlers - Flask:
app.py,__init__.py, any@app.after_requesthook - Django:
settings.py,middleware.py,MIDDLEWARElist - Spring Boot:
SecurityConfig.java,WebMvcConfigurerimplementations
- Run all 10+ security header checks (see Checks section) against each identified file.
- 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
- Deduplicate and sort findings by severity: Critical > High > Medium > Low.
- Generate the findings report using the Findings Format below.
- 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:
// 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('...'));
// 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 — no CSP header in server block
server {
listen 443 ssl;
server_name example.com;
# Missing: add_header Content-Security-Policy "...";
}
# Flask — no CSP header on responses
@app.route('/')
def index():
return render_template('index.html')
# No Content-Security-Policy header added
SAFE:
// 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 — 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:
// Express — wildcard CORS open to all origins
const cors = require('cors');
app.use(cors()); // defaults to origin: '*'
// Or explicitly:
app.use(cors({ origin: '*' }));
// 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 — wildcard CORS
location /api/ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
}
# Flask — wildcard CORS via flask-cors
from flask_cors import CORS
CORS(app) # defaults to origins='*'
SAFE:
// 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 — 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;
}
}
# 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:
// Express — HSTS missing entirely
const helmet = require('helmet');
app.use(helmet({
hsts: false, // HSTS explicitly disabled
}));
// 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 — missing HSTS header entirely
server {
listen 443 ssl;
# No Strict-Transport-Security header
}
# Flask — no HSTS enforcement
@app.route('/login', methods=['POST'])
def login():
# No HSTS header — HTTP downgrade possible
return jsonify({'token': generate_token()})
SAFE:
// 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 — full HSTS header
server {
listen 443 ssl;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
# 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:
// 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 — X-Content-Type-Options not set
server {
listen 443 ssl;
root /var/www/html;
# Missing: add_header X-Content-Type-Options "nosniff";
}
SAFE:
// 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 — nosniff header
server {
listen 443 ssl;
add_header X-Content-Type-Options "nosniff" always;
}
# 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:
// Express — no frame protection
const helmet = require('helmet');
app.use(helmet({
frameguard: false, // Clickjacking protection disabled
contentSecurityPolicy: false,
}));
# Nginx — X-Frame-Options absent
server {
listen 443 ssl;
# No X-Frame-Options or frame-ancestors directive
}
# Flask — no X-Frame-Options header
@app.after_request
def headers(response):
# X-Frame-Options not set — clickjacking possible
return response
SAFE:
// 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 — 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;
}
# 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
- Source: kalshamsi/claude-security-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.