analyze for URL-based checks and analyzeHeaders for raw header validation. This allows developers to write unit tests for their server middleware, ensuring headers are generated correctly without requiring a running server or network requests.
3. Grading Logic: The tool implements a standardized grading scale (A+ to F) based on a weighted score of seven security categories. This provides a quantifiable metric for security posture that is easy to communicate across teams.
Implementation: CLI Integration
For CI/CD pipelines, the CLI should be invoked against staging or production environments. The --json flag is recommended for programmatic consumption, though the colored terminal output is useful for local debugging.
Example: Pipeline Audit Script
This script demonstrates how to wrap the CLI in a Node.js module for enhanced control, such as custom logging or integration with notification systems.
// scripts/security-audit.mjs
import { execSync } from 'node:child_process';
import { exit } from 'node:process';
const TARGET_URL = process.env.AUDIT_URL;
if (!TARGET_URL) {
console.error('Error: AUDIT_URL environment variable is required.');
exit(1);
}
try {
console.log(`Starting security header audit for ${TARGET_URL}...`);
// Execute the audit tool with JSON output for structured analysis
const rawOutput = execSync(
`npx @hailbytes/security-headers "${TARGET_URL}" --json`,
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
);
const report = JSON.parse(rawOutput);
// Define acceptable threshold
const MINIMUM_GRADE = 'B';
const gradeOrder = ['F', 'D', 'C', 'B', 'A', 'A+'];
if (gradeOrder.indexOf(report.grade) > gradeOrder.indexOf(MINIMUM_GRADE)) {
console.error(`Security audit failed. Current grade: ${report.grade} (Minimum: ${MINIMUM_GRADE})`);
console.error(`Score: ${report.score}%`);
// Output specific failures for debugging
const failures = report.headers.filter(h => h.status === 'error' || h.status === 'missing');
failures.forEach(f => console.error(`- ${f.name}: ${f.remediation}`));
exit(1);
}
console.log(`Audit passed. Grade: ${report.grade} (${report.score}%)`);
exit(0);
} catch (error) {
console.error('Audit execution failed:', error.message);
exit(1);
}
Implementation: Library Unit Testing
Using the library allows for fast, deterministic tests of your application's middleware. This is particularly valuable for testing edge cases, such as ensuring headers vary correctly based on environment or request context.
Example: Middleware Test Suite
// tests/middleware/security-headers.test.ts
import { describe, it, expect } from 'vitest';
import { analyzeHeaders } from '@hailbytes/security-headers';
import { createSecurityMiddleware } from '../../src/middleware/security';
describe('Security Middleware', () => {
it('should generate compliant headers for production requests', () => {
// Simulate a request context
const mockRequest = {
headers: { host: 'app.example.com' },
protocol: 'https'
};
// Invoke middleware to get generated headers
const generatedHeaders = createSecurityMiddleware(mockRequest);
// Analyze the result using the library
const analysis = analyzeHeaders(generatedHeaders);
// Assertions
expect(analysis.grade).toBe('A+');
expect(analysis.score).toBeGreaterThanOrEqual(90);
// Verify specific critical headers
const hsts = analysis.headers['strict-transport-security'];
expect(hsts.status).toBe('good');
expect(hsts.value).toContain('includeSubDomains');
expect(hsts.value).toContain('preload');
const csp = analysis.headers['content-security-policy'];
expect(csp.status).toBe('good');
expect(csp.value).not.toContain('unsafe-inline');
});
it('should fail analysis if CSP is missing', () => {
const weakHeaders = {
'x-content-type-options': 'nosniff'
};
const result = analyzeHeaders(weakHeaders);
expect(result.grade).toMatch(/^[CDF]$/);
expect(result.headers['content-security-policy'].status).toBe('missing');
});
});
Pitfall Guide
Implementing security headers requires careful attention to detail. The following pitfalls are common in production environments and can lead to false confidence or application breakage.
| Pitfall | Explanation | Fix |
|---|
CSP unsafe-inline Dependency | Developers often add unsafe-inline to CSP to fix broken scripts, effectively nullifying XSS protection. This is a frequent cause of low grades. | Remove unsafe-inline. Use cryptographic nonces or hashes for inline scripts. Refactor code to use external scripts where possible. |
| HSTS Without Preload | Setting HSTS is good, but without the preload directive and submission to browser preload lists, the first request remains vulnerable to downgrade attacks. | Add preload to the HSTS header and submit your domain to the HSTS Preload List. Ensure max-age is at least 31536000. |
| Ignoring Cross-Origin Headers | Modern browser features like SharedArrayBuffer require Cross-Origin Isolation, enforced by Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy. Missing these can break functionality. | Implement COOP: same-origin and COEP: require-corp if your app requires cross-origin isolation. Verify third-party resources support these policies. |
| Over-Strict CSP Breakage | Applying a strict CSP without testing can break legitimate functionality, leading developers to disable the header entirely. | Use Content-Security-Policy-Report-Only during rollout. Monitor reports to identify legitimate resources before enforcing the policy. |
| Redundant X-Frame-Options | X-Frame-Options is legacy. Modern browsers prefer frame-ancestors in CSP. Maintaining both can cause confusion, though keeping both is safe for legacy support. | Prioritize frame-ancestors in CSP. Retain X-Frame-Options: DENY only if supporting very old browsers that ignore CSP framing directives. |
| Permissions-Policy Syntax Errors | The Permissions-Policy header has a complex syntax. Typos can cause the browser to ignore the entire header or specific directives. | Validate syntax rigorously. Use the library's remediation strings to correct errors. Test permissions behavior in the browser console. |
| Hardcoding URLs in CI | CI scripts that hardcode URLs become brittle when environments change (e.g., dynamic staging URLs). | Use environment variables or secrets management for URLs. Ensure the CI pipeline injects the correct target URL at runtime. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Pre-Commit Validation | Library analyzeHeaders | Fast execution, no network overhead, catches errors before push. | Zero |
| Staging Deployment | CLI with --json | Validates the actual deployed artifact in a realistic environment. | Low |
| Production Monitoring | CLI in Cron/Scheduled Job | Continuous assurance that production headers remain intact over time. | Low |
| Legacy Browser Support | Library with custom assertions | Allows testing specific header combinations required for older browsers. | Medium |
| Third-Party Integration | CLI against external URLs | Verifies that third-party services meet your security requirements. | Low |
Configuration Template
GitHub Actions Workflow
This template demonstrates a complete workflow that deploys to a staging environment and runs the security audit as a gate.
name: Security Header Validation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
security-audit:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Deploy to Staging
# Replace with your actual deployment step
run: ./scripts/deploy-staging.sh
env:
STAGING_URL: ${{ secrets.STAGING_URL }}
- name: Run Security Header Audit
run: |
npx @hailbytes/security-headers "${{ secrets.STAGING_URL }}" --json > audit-report.json
# Fail if grade is D or F
if grep -q '"grade": "[DF]"' audit-report.json; then
echo "::error::Security audit failed. Grade is D or F."
cat audit-report.json
exit 1
fi
env:
STAGING_URL: ${{ secrets.STAGING_URL }}
- name: Upload Audit Report
if: always()
uses: actions/upload-artifact@v4
with:
name: security-audit-report
path: audit-report.json
Quick Start Guide
-
Install the Tool:
npm install -D @hailbytes/security-headers
-
Run a Local Audit:
Point the CLI at your local development server to see immediate feedback.
npx @hailbytes/security-headers http://localhost:3000
-
Analyze Raw Headers:
If you have a set of headers from a proxy or CDN, analyze them directly without a running server.
echo '{"strict-transport-security": "max-age=31536000"}' | npx @hailbytes/security-headers --stdin
-
Integrate into Scripts:
Add an audit script to your package.json for easy execution.
{
"scripts": {
"audit:headers": "npx @hailbytes/security-headers https://staging.example.com --json"
}
}
-
Enforce in CI:
Add the audit command to your pipeline configuration and configure the step to fail on non-zero exit codes. This ensures no deployment can proceed if security headers regress.