Files
Charon/tests/security-enforcement/security-headers-enforcement.spec.ts
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

109 lines
3.6 KiB
TypeScript
Executable File

/**
* Security Headers Enforcement Tests
*
* Tests that verify security headers are properly set on responses.
*
* NOTE: Security headers are applied at Caddy layer. These tests verify
* the expected headers through the API proxy.
*
* @see /projects/Charon/docs/plans/current_spec.md - Security Headers Enforcement Tests
*/
import { test, expect } from '../fixtures/test';
import { request } from '@playwright/test';
import type { APIRequestContext } from '@playwright/test';
import { STORAGE_STATE } from '../constants';
test.describe('Security Headers Enforcement', () => {
let requestContext: APIRequestContext;
test.beforeAll(async () => {
requestContext = await request.newContext({
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:8080',
storageState: STORAGE_STATE,
});
});
test.afterAll(async () => {
await requestContext.dispose();
});
test('should return X-Content-Type-Options header', async () => {
const response = await requestContext.get('/api/v1/health');
expect(response.ok()).toBe(true);
// X-Content-Type-Options should be 'nosniff'
const header = response.headers()['x-content-type-options'];
if (header) {
expect(header).toBe('nosniff');
} else {
// If backend doesn't set it, Caddy should when configured
console.log(
'X-Content-Type-Options not set directly (may be set at Caddy layer)'
);
}
});
test('should return X-Frame-Options header', async () => {
const response = await requestContext.get('/api/v1/health');
expect(response.ok()).toBe(true);
// X-Frame-Options should be 'DENY' or 'SAMEORIGIN'
const header = response.headers()['x-frame-options'];
if (header) {
expect(['DENY', 'SAMEORIGIN', 'deny', 'sameorigin']).toContain(header);
} else {
// If backend doesn't set it, Caddy should when configured
console.log(
'X-Frame-Options not set directly (may be set at Caddy layer)'
);
}
});
test('should document HSTS behavior on HTTPS', async () => {
// HSTS (Strict-Transport-Security) is only set on HTTPS responses
// In test environment, we typically use HTTP
//
// Expected header on HTTPS:
// Strict-Transport-Security: max-age=31536000; includeSubDomains
//
// This test verifies HSTS is not incorrectly set on HTTP
const response = await requestContext.get('/api/v1/health');
expect(response.ok()).toBe(true);
const hsts = response.headers()['strict-transport-security'];
// On HTTP, HSTS should not be present (browsers ignore it anyway)
if (process.env.PLAYWRIGHT_BASE_URL?.startsWith('https://')) {
expect(hsts).toBeDefined();
expect(hsts).toContain('max-age');
} else {
// HTTP is fine without HSTS in test env
console.log('HSTS not present on HTTP (expected behavior)');
}
});
test('should verify Content-Security-Policy when configured', async () => {
// CSP is optional and configured per-host
// This test verifies CSP header handling when present
const response = await requestContext.get('/');
// May be 200 or redirect
expect(response.status()).toBeLessThan(500);
const csp = response.headers()['content-security-policy'];
if (csp) {
// CSP should contain valid directives
expect(
csp.includes("default-src") ||
csp.includes("script-src") ||
csp.includes("style-src")
).toBe(true);
} else {
// CSP is optional - document its behavior when configured
console.log('CSP not configured (optional - set per proxy host)');
}
});
});