tlc-claude-code 1.4.8 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/index.js +229 -14
- package/server/lib/compliance/control-mapper.js +401 -0
- package/server/lib/compliance/control-mapper.test.js +117 -0
- package/server/lib/compliance/evidence-linker.js +296 -0
- package/server/lib/compliance/evidence-linker.test.js +121 -0
- package/server/lib/compliance/gdpr-checklist.js +416 -0
- package/server/lib/compliance/gdpr-checklist.test.js +131 -0
- package/server/lib/compliance/hipaa-checklist.js +277 -0
- package/server/lib/compliance/hipaa-checklist.test.js +101 -0
- package/server/lib/compliance/iso27001-checklist.js +287 -0
- package/server/lib/compliance/iso27001-checklist.test.js +99 -0
- package/server/lib/compliance/multi-framework-reporter.js +284 -0
- package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
- package/server/lib/compliance/pci-dss-checklist.js +214 -0
- package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
- package/server/lib/compliance/trust-centre.js +187 -0
- package/server/lib/compliance/trust-centre.test.js +93 -0
- package/server/lib/dashboard/api-server.js +155 -0
- package/server/lib/dashboard/api-server.test.js +155 -0
- package/server/lib/dashboard/health-api.js +199 -0
- package/server/lib/dashboard/health-api.test.js +122 -0
- package/server/lib/dashboard/notes-api.js +234 -0
- package/server/lib/dashboard/notes-api.test.js +134 -0
- package/server/lib/dashboard/router-api.js +176 -0
- package/server/lib/dashboard/router-api.test.js +132 -0
- package/server/lib/dashboard/tasks-api.js +289 -0
- package/server/lib/dashboard/tasks-api.test.js +161 -0
- package/server/lib/dashboard/tlc-introspection.js +197 -0
- package/server/lib/dashboard/tlc-introspection.test.js +138 -0
- package/server/lib/dashboard/version-api.js +222 -0
- package/server/lib/dashboard/version-api.test.js +112 -0
- package/server/lib/dashboard/websocket-server.js +104 -0
- package/server/lib/dashboard/websocket-server.test.js +118 -0
- package/server/lib/deploy/branch-classifier.js +163 -0
- package/server/lib/deploy/branch-classifier.test.js +164 -0
- package/server/lib/deploy/deployment-approval.js +299 -0
- package/server/lib/deploy/deployment-approval.test.js +296 -0
- package/server/lib/deploy/deployment-audit.js +374 -0
- package/server/lib/deploy/deployment-audit.test.js +307 -0
- package/server/lib/deploy/deployment-executor.js +335 -0
- package/server/lib/deploy/deployment-executor.test.js +329 -0
- package/server/lib/deploy/deployment-rules.js +163 -0
- package/server/lib/deploy/deployment-rules.test.js +188 -0
- package/server/lib/deploy/rollback-manager.js +379 -0
- package/server/lib/deploy/rollback-manager.test.js +321 -0
- package/server/lib/deploy/security-gates.js +236 -0
- package/server/lib/deploy/security-gates.test.js +222 -0
- package/server/lib/k8s/gitops-config.js +188 -0
- package/server/lib/k8s/gitops-config.test.js +59 -0
- package/server/lib/k8s/helm-generator.js +196 -0
- package/server/lib/k8s/helm-generator.test.js +59 -0
- package/server/lib/k8s/kustomize-generator.js +176 -0
- package/server/lib/k8s/kustomize-generator.test.js +58 -0
- package/server/lib/k8s/network-policy.js +114 -0
- package/server/lib/k8s/network-policy.test.js +53 -0
- package/server/lib/k8s/pod-security.js +114 -0
- package/server/lib/k8s/pod-security.test.js +55 -0
- package/server/lib/k8s/rbac-generator.js +132 -0
- package/server/lib/k8s/rbac-generator.test.js +57 -0
- package/server/lib/k8s/resource-manager.js +172 -0
- package/server/lib/k8s/resource-manager.test.js +60 -0
- package/server/lib/k8s/secrets-encryption.js +168 -0
- package/server/lib/k8s/secrets-encryption.test.js +49 -0
- package/server/lib/monitoring/alert-manager.js +238 -0
- package/server/lib/monitoring/alert-manager.test.js +106 -0
- package/server/lib/monitoring/health-check.js +226 -0
- package/server/lib/monitoring/health-check.test.js +176 -0
- package/server/lib/monitoring/incident-manager.js +230 -0
- package/server/lib/monitoring/incident-manager.test.js +98 -0
- package/server/lib/monitoring/log-aggregator.js +147 -0
- package/server/lib/monitoring/log-aggregator.test.js +89 -0
- package/server/lib/monitoring/metrics-collector.js +337 -0
- package/server/lib/monitoring/metrics-collector.test.js +172 -0
- package/server/lib/monitoring/status-page.js +214 -0
- package/server/lib/monitoring/status-page.test.js +105 -0
- package/server/lib/monitoring/uptime-monitor.js +194 -0
- package/server/lib/monitoring/uptime-monitor.test.js +109 -0
- package/server/lib/network/fail2ban-config.js +294 -0
- package/server/lib/network/fail2ban-config.test.js +275 -0
- package/server/lib/network/firewall-manager.js +252 -0
- package/server/lib/network/firewall-manager.test.js +254 -0
- package/server/lib/network/geoip-filter.js +282 -0
- package/server/lib/network/geoip-filter.test.js +264 -0
- package/server/lib/network/rate-limiter.js +229 -0
- package/server/lib/network/rate-limiter.test.js +293 -0
- package/server/lib/network/request-validator.js +351 -0
- package/server/lib/network/request-validator.test.js +345 -0
- package/server/lib/network/security-headers.js +251 -0
- package/server/lib/network/security-headers.test.js +283 -0
- package/server/lib/network/tls-config.js +210 -0
- package/server/lib/network/tls-config.test.js +248 -0
- package/server/lib/security/auth-security.js +369 -0
- package/server/lib/security/auth-security.test.js +448 -0
- package/server/lib/security/cis-benchmark.js +152 -0
- package/server/lib/security/cis-benchmark.test.js +137 -0
- package/server/lib/security/compose-templates.js +312 -0
- package/server/lib/security/compose-templates.test.js +229 -0
- package/server/lib/security/container-runtime.js +456 -0
- package/server/lib/security/container-runtime.test.js +503 -0
- package/server/lib/security/cors-validator.js +278 -0
- package/server/lib/security/cors-validator.test.js +310 -0
- package/server/lib/security/crypto-utils.js +253 -0
- package/server/lib/security/crypto-utils.test.js +409 -0
- package/server/lib/security/dockerfile-linter.js +459 -0
- package/server/lib/security/dockerfile-linter.test.js +483 -0
- package/server/lib/security/dockerfile-templates.js +278 -0
- package/server/lib/security/dockerfile-templates.test.js +164 -0
- package/server/lib/security/error-sanitizer.js +426 -0
- package/server/lib/security/error-sanitizer.test.js +331 -0
- package/server/lib/security/headers-generator.js +368 -0
- package/server/lib/security/headers-generator.test.js +398 -0
- package/server/lib/security/image-scanner.js +83 -0
- package/server/lib/security/image-scanner.test.js +106 -0
- package/server/lib/security/input-validator.js +352 -0
- package/server/lib/security/input-validator.test.js +330 -0
- package/server/lib/security/network-policy.js +174 -0
- package/server/lib/security/network-policy.test.js +164 -0
- package/server/lib/security/output-encoder.js +237 -0
- package/server/lib/security/output-encoder.test.js +276 -0
- package/server/lib/security/path-validator.js +359 -0
- package/server/lib/security/path-validator.test.js +293 -0
- package/server/lib/security/query-builder.js +421 -0
- package/server/lib/security/query-builder.test.js +318 -0
- package/server/lib/security/secret-detector.js +290 -0
- package/server/lib/security/secret-detector.test.js +354 -0
- package/server/lib/security/secrets-validator.js +137 -0
- package/server/lib/security/secrets-validator.test.js +120 -0
- package/server/lib/security-testing/dast-runner.js +154 -0
- package/server/lib/security-testing/dast-runner.test.js +62 -0
- package/server/lib/security-testing/dependency-scanner.js +172 -0
- package/server/lib/security-testing/dependency-scanner.test.js +64 -0
- package/server/lib/security-testing/pentest-runner.js +230 -0
- package/server/lib/security-testing/pentest-runner.test.js +60 -0
- package/server/lib/security-testing/sast-runner.js +136 -0
- package/server/lib/security-testing/sast-runner.test.js +62 -0
- package/server/lib/security-testing/secret-scanner.js +153 -0
- package/server/lib/security-testing/secret-scanner.test.js +66 -0
- package/server/lib/security-testing/security-gate.js +216 -0
- package/server/lib/security-testing/security-gate.test.js +115 -0
- package/server/lib/security-testing/security-reporter.js +303 -0
- package/server/lib/security-testing/security-reporter.test.js +114 -0
- package/server/lib/standards/audit-checker.js +546 -0
- package/server/lib/standards/audit-checker.test.js +415 -0
- package/server/lib/standards/cleanup-executor.js +452 -0
- package/server/lib/standards/cleanup-executor.test.js +293 -0
- package/server/lib/standards/refactor-stepper.js +425 -0
- package/server/lib/standards/refactor-stepper.test.js +298 -0
- package/server/lib/standards/standards-injector.js +167 -0
- package/server/lib/standards/standards-injector.test.js +232 -0
- package/server/lib/user-management.test.js +284 -0
- package/server/lib/vps/backup-manager.js +157 -0
- package/server/lib/vps/backup-manager.test.js +59 -0
- package/server/lib/vps/caddy-config.js +159 -0
- package/server/lib/vps/caddy-config.test.js +48 -0
- package/server/lib/vps/compose-orchestrator.js +219 -0
- package/server/lib/vps/compose-orchestrator.test.js +50 -0
- package/server/lib/vps/database-config.js +208 -0
- package/server/lib/vps/database-config.test.js +47 -0
- package/server/lib/vps/deploy-script.js +211 -0
- package/server/lib/vps/deploy-script.test.js +53 -0
- package/server/lib/vps/secrets-manager.js +148 -0
- package/server/lib/vps/secrets-manager.test.js +58 -0
- package/server/lib/vps/server-hardening.js +174 -0
- package/server/lib/vps/server-hardening.test.js +70 -0
- package/server/package-lock.json +19 -0
- package/server/package.json +1 -0
- package/server/templates/CLAUDE.md +37 -0
- package/server/templates/CODING-STANDARDS.md +408 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Headers Manager
|
|
3
|
+
* Generates and validates security headers for HTTP responses
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const HEADER_PRESETS = {
|
|
7
|
+
STRICT: {
|
|
8
|
+
csp: {
|
|
9
|
+
strict: true,
|
|
10
|
+
},
|
|
11
|
+
hsts: {
|
|
12
|
+
maxAge: 31536000,
|
|
13
|
+
includeSubDomains: true,
|
|
14
|
+
preload: true,
|
|
15
|
+
},
|
|
16
|
+
xFrameOptions: 'DENY',
|
|
17
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
18
|
+
permissionsPolicy: {
|
|
19
|
+
strict: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
RELAXED: {
|
|
23
|
+
csp: {
|
|
24
|
+
strict: false,
|
|
25
|
+
},
|
|
26
|
+
hsts: {
|
|
27
|
+
maxAge: 31536000,
|
|
28
|
+
includeSubDomains: false,
|
|
29
|
+
},
|
|
30
|
+
xFrameOptions: 'SAMEORIGIN',
|
|
31
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate Content Security Policy header value
|
|
37
|
+
*/
|
|
38
|
+
export function generateCsp(options) {
|
|
39
|
+
const { strict, scriptSrc, styleSrc, reportUri, frameAncestors, upgradeInsecureRequests } =
|
|
40
|
+
options;
|
|
41
|
+
|
|
42
|
+
const directives = [];
|
|
43
|
+
|
|
44
|
+
// Default source
|
|
45
|
+
directives.push("default-src 'self'");
|
|
46
|
+
|
|
47
|
+
// Script source
|
|
48
|
+
if (scriptSrc) {
|
|
49
|
+
directives.push(`script-src ${scriptSrc.join(' ')}`);
|
|
50
|
+
} else if (strict) {
|
|
51
|
+
directives.push("script-src 'self'");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Style source
|
|
55
|
+
if (styleSrc) {
|
|
56
|
+
directives.push(`style-src ${styleSrc.join(' ')}`);
|
|
57
|
+
} else if (strict) {
|
|
58
|
+
directives.push("style-src 'self'");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Frame ancestors
|
|
62
|
+
if (frameAncestors) {
|
|
63
|
+
directives.push(`frame-ancestors ${frameAncestors.join(' ')}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Upgrade insecure requests
|
|
67
|
+
if (upgradeInsecureRequests) {
|
|
68
|
+
directives.push('upgrade-insecure-requests');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Report URI
|
|
72
|
+
if (reportUri) {
|
|
73
|
+
directives.push(`report-uri ${reportUri}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return directives.join('; ');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate HSTS header value
|
|
81
|
+
*/
|
|
82
|
+
export function generateHsts(options) {
|
|
83
|
+
const { maxAge = 31536000, includeSubDomains, preload } = options;
|
|
84
|
+
|
|
85
|
+
let value = `max-age=${maxAge}`;
|
|
86
|
+
|
|
87
|
+
if (includeSubDomains) {
|
|
88
|
+
value += '; includeSubDomains';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (preload) {
|
|
92
|
+
value += '; preload';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate Permissions-Policy header value
|
|
100
|
+
*/
|
|
101
|
+
export function generatePermissionsPolicy(options) {
|
|
102
|
+
const { strict, camera, microphone, geolocation, payment } = options;
|
|
103
|
+
|
|
104
|
+
const policies = [];
|
|
105
|
+
|
|
106
|
+
// Helper to format policy value
|
|
107
|
+
const formatValue = (value) => {
|
|
108
|
+
if (!value || value.length === 0) {
|
|
109
|
+
return '()';
|
|
110
|
+
}
|
|
111
|
+
const formatted = value
|
|
112
|
+
.map((v) => {
|
|
113
|
+
if (v === 'self') return 'self';
|
|
114
|
+
return `"${v}"`;
|
|
115
|
+
})
|
|
116
|
+
.join(' ');
|
|
117
|
+
return `(${formatted})`;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (strict) {
|
|
121
|
+
policies.push('camera=()');
|
|
122
|
+
policies.push('microphone=()');
|
|
123
|
+
policies.push('geolocation=()');
|
|
124
|
+
policies.push('payment=()');
|
|
125
|
+
policies.push('usb=()');
|
|
126
|
+
policies.push('magnetometer=()');
|
|
127
|
+
policies.push('gyroscope=()');
|
|
128
|
+
policies.push('accelerometer=()');
|
|
129
|
+
} else {
|
|
130
|
+
if (camera !== undefined) {
|
|
131
|
+
policies.push(`camera=${formatValue(camera)}`);
|
|
132
|
+
}
|
|
133
|
+
if (microphone !== undefined) {
|
|
134
|
+
policies.push(`microphone=${formatValue(microphone)}`);
|
|
135
|
+
}
|
|
136
|
+
if (geolocation !== undefined) {
|
|
137
|
+
policies.push(`geolocation=${formatValue(geolocation)}`);
|
|
138
|
+
}
|
|
139
|
+
if (payment !== undefined) {
|
|
140
|
+
policies.push(`payment=${formatValue(payment)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return policies.join(', ');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate Cross-Origin headers (COOP, COEP, CORP)
|
|
149
|
+
*/
|
|
150
|
+
export function generateCrossOriginHeaders(options) {
|
|
151
|
+
const { coopPolicy, coepPolicy, corpPolicy } = options;
|
|
152
|
+
|
|
153
|
+
const headers = {};
|
|
154
|
+
|
|
155
|
+
if (coopPolicy) {
|
|
156
|
+
headers['Cross-Origin-Opener-Policy'] = coopPolicy;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (coepPolicy) {
|
|
160
|
+
headers['Cross-Origin-Embedder-Policy'] = coepPolicy;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (corpPolicy) {
|
|
164
|
+
headers['Cross-Origin-Resource-Policy'] = corpPolicy;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return headers;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate all security headers
|
|
172
|
+
*/
|
|
173
|
+
export function generateSecurityHeaders(options) {
|
|
174
|
+
const { preset, referrerPolicy } = options;
|
|
175
|
+
|
|
176
|
+
const presetConfig = preset ? HEADER_PRESETS[preset.toUpperCase()] : null;
|
|
177
|
+
|
|
178
|
+
const headers = {
|
|
179
|
+
'X-Content-Type-Options': 'nosniff',
|
|
180
|
+
'X-Frame-Options': presetConfig?.xFrameOptions || 'DENY',
|
|
181
|
+
'Referrer-Policy': referrerPolicy || presetConfig?.referrerPolicy || 'strict-origin-when-cross-origin',
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (preset === 'strict' || presetConfig) {
|
|
185
|
+
headers['Content-Security-Policy'] = generateCsp(presetConfig?.csp || { strict: true });
|
|
186
|
+
headers['Strict-Transport-Security'] = generateHsts(presetConfig?.hsts || {});
|
|
187
|
+
headers['Permissions-Policy'] = generatePermissionsPolicy(
|
|
188
|
+
presetConfig?.permissionsPolicy || { strict: true }
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return headers;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate security headers
|
|
197
|
+
*/
|
|
198
|
+
export function validateHeaders(headers, options = {}) {
|
|
199
|
+
const { required = [], strict = false } = options;
|
|
200
|
+
|
|
201
|
+
const result = {
|
|
202
|
+
valid: true,
|
|
203
|
+
missing: [],
|
|
204
|
+
warnings: [],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Check required headers
|
|
208
|
+
for (const header of required) {
|
|
209
|
+
if (!headers[header]) {
|
|
210
|
+
result.missing.push(header);
|
|
211
|
+
result.valid = false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check for unsafe CSP directives in strict mode
|
|
216
|
+
if (strict === true && headers['Content-Security-Policy']) {
|
|
217
|
+
const cspValue = headers['Content-Security-Policy'];
|
|
218
|
+
// Check for unsafe-inline
|
|
219
|
+
if (cspValue.includes('unsafe-inline')) {
|
|
220
|
+
result.warnings.push('unsafe-inline detected in CSP - this weakens security');
|
|
221
|
+
}
|
|
222
|
+
// Check for unsafe-eval
|
|
223
|
+
if (cspValue.includes('unsafe-eval')) {
|
|
224
|
+
result.warnings.push('unsafe-eval detected in CSP - this weakens security');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create a security headers manager with default options
|
|
233
|
+
*/
|
|
234
|
+
export function createSecurityHeadersManager(config = {}) {
|
|
235
|
+
const { preset } = config;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
generate(options) {
|
|
239
|
+
return generateSecurityHeaders({ preset, ...options });
|
|
240
|
+
},
|
|
241
|
+
validate(headers, options) {
|
|
242
|
+
return validateHeaders(headers, options);
|
|
243
|
+
},
|
|
244
|
+
getCsp(options) {
|
|
245
|
+
return generateCsp(options);
|
|
246
|
+
},
|
|
247
|
+
getHsts(options) {
|
|
248
|
+
return generateHsts(options);
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Headers Manager Tests
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
generateSecurityHeaders,
|
|
7
|
+
generateCsp,
|
|
8
|
+
generateHsts,
|
|
9
|
+
generatePermissionsPolicy,
|
|
10
|
+
generateCrossOriginHeaders,
|
|
11
|
+
validateHeaders,
|
|
12
|
+
HEADER_PRESETS,
|
|
13
|
+
createSecurityHeadersManager,
|
|
14
|
+
} from './security-headers.js';
|
|
15
|
+
|
|
16
|
+
describe('security-headers', () => {
|
|
17
|
+
describe('HEADER_PRESETS', () => {
|
|
18
|
+
it('defines strict preset', () => {
|
|
19
|
+
expect(HEADER_PRESETS.STRICT).toBeDefined();
|
|
20
|
+
expect(HEADER_PRESETS.STRICT.csp).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('defines relaxed preset', () => {
|
|
24
|
+
expect(HEADER_PRESETS.RELAXED).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('generateCsp', () => {
|
|
29
|
+
it('generates strict CSP without unsafe-inline', () => {
|
|
30
|
+
const csp = generateCsp({ strict: true });
|
|
31
|
+
|
|
32
|
+
expect(csp).not.toContain("'unsafe-inline'");
|
|
33
|
+
expect(csp).not.toContain("'unsafe-eval'");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('includes default-src directive', () => {
|
|
37
|
+
const csp = generateCsp({});
|
|
38
|
+
|
|
39
|
+
expect(csp).toContain("default-src 'self'");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('configures script-src', () => {
|
|
43
|
+
const csp = generateCsp({
|
|
44
|
+
scriptSrc: ["'self'", 'https://cdn.example.com'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(csp).toContain('script-src');
|
|
48
|
+
expect(csp).toContain('https://cdn.example.com');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('configures style-src', () => {
|
|
52
|
+
const csp = generateCsp({
|
|
53
|
+
styleSrc: ["'self'"],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(csp).toContain('style-src');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('adds report-uri when specified', () => {
|
|
60
|
+
const csp = generateCsp({
|
|
61
|
+
reportUri: 'https://example.com/csp-report',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(csp).toContain('report-uri https://example.com/csp-report');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('supports frame-ancestors', () => {
|
|
68
|
+
const csp = generateCsp({
|
|
69
|
+
frameAncestors: ["'none'"],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(csp).toContain("frame-ancestors 'none'");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('supports upgrade-insecure-requests', () => {
|
|
76
|
+
const csp = generateCsp({
|
|
77
|
+
upgradeInsecureRequests: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(csp).toContain('upgrade-insecure-requests');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('generateHsts', () => {
|
|
85
|
+
it('sets max-age', () => {
|
|
86
|
+
const hsts = generateHsts({ maxAge: 31536000 });
|
|
87
|
+
|
|
88
|
+
expect(hsts).toContain('max-age=31536000');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('includes includeSubDomains', () => {
|
|
92
|
+
const hsts = generateHsts({
|
|
93
|
+
maxAge: 31536000,
|
|
94
|
+
includeSubDomains: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(hsts).toContain('includeSubDomains');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('includes preload directive', () => {
|
|
101
|
+
const hsts = generateHsts({
|
|
102
|
+
maxAge: 31536000,
|
|
103
|
+
includeSubDomains: true,
|
|
104
|
+
preload: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(hsts).toContain('preload');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('defaults to one year max-age', () => {
|
|
111
|
+
const hsts = generateHsts({});
|
|
112
|
+
|
|
113
|
+
expect(hsts).toContain('max-age=31536000');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('generatePermissionsPolicy', () => {
|
|
118
|
+
it('disables camera by default', () => {
|
|
119
|
+
const policy = generatePermissionsPolicy({ strict: true });
|
|
120
|
+
|
|
121
|
+
expect(policy).toContain('camera=()');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('disables microphone by default', () => {
|
|
125
|
+
const policy = generatePermissionsPolicy({ strict: true });
|
|
126
|
+
|
|
127
|
+
expect(policy).toContain('microphone=()');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('disables geolocation by default', () => {
|
|
131
|
+
const policy = generatePermissionsPolicy({ strict: true });
|
|
132
|
+
|
|
133
|
+
expect(policy).toContain('geolocation=()');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('allows specific features when configured', () => {
|
|
137
|
+
const policy = generatePermissionsPolicy({
|
|
138
|
+
camera: ['self'],
|
|
139
|
+
geolocation: ['self', 'https://maps.example.com'],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(policy).toContain('camera=(self)');
|
|
143
|
+
expect(policy).toContain('geolocation=(self "https://maps.example.com")');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('handles payment feature', () => {
|
|
147
|
+
const policy = generatePermissionsPolicy({
|
|
148
|
+
payment: ['self'],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(policy).toContain('payment=(self)');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('generateCrossOriginHeaders', () => {
|
|
156
|
+
it('generates COOP header', () => {
|
|
157
|
+
const headers = generateCrossOriginHeaders({
|
|
158
|
+
coopPolicy: 'same-origin',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(headers['Cross-Origin-Opener-Policy']).toBe('same-origin');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('generates COEP header', () => {
|
|
165
|
+
const headers = generateCrossOriginHeaders({
|
|
166
|
+
coepPolicy: 'require-corp',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(headers['Cross-Origin-Embedder-Policy']).toBe('require-corp');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('generates CORP header', () => {
|
|
173
|
+
const headers = generateCrossOriginHeaders({
|
|
174
|
+
corpPolicy: 'same-origin',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(headers['Cross-Origin-Resource-Policy']).toBe('same-origin');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('supports cross-origin-isolated', () => {
|
|
181
|
+
const headers = generateCrossOriginHeaders({
|
|
182
|
+
coopPolicy: 'same-origin',
|
|
183
|
+
coepPolicy: 'require-corp',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(headers['Cross-Origin-Opener-Policy']).toBe('same-origin');
|
|
187
|
+
expect(headers['Cross-Origin-Embedder-Policy']).toBe('require-corp');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('generateSecurityHeaders', () => {
|
|
192
|
+
it('includes X-Content-Type-Options', () => {
|
|
193
|
+
const headers = generateSecurityHeaders({});
|
|
194
|
+
|
|
195
|
+
expect(headers['X-Content-Type-Options']).toBe('nosniff');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('includes X-Frame-Options', () => {
|
|
199
|
+
const headers = generateSecurityHeaders({});
|
|
200
|
+
|
|
201
|
+
expect(headers['X-Frame-Options']).toBe('DENY');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('includes Referrer-Policy', () => {
|
|
205
|
+
const headers = generateSecurityHeaders({});
|
|
206
|
+
|
|
207
|
+
expect(headers['Referrer-Policy']).toBe('strict-origin-when-cross-origin');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('includes all headers for strict preset', () => {
|
|
211
|
+
const headers = generateSecurityHeaders({ preset: 'strict' });
|
|
212
|
+
|
|
213
|
+
expect(headers['Content-Security-Policy']).toBeDefined();
|
|
214
|
+
expect(headers['Strict-Transport-Security']).toBeDefined();
|
|
215
|
+
expect(headers['X-Content-Type-Options']).toBeDefined();
|
|
216
|
+
expect(headers['X-Frame-Options']).toBeDefined();
|
|
217
|
+
expect(headers['Referrer-Policy']).toBeDefined();
|
|
218
|
+
expect(headers['Permissions-Policy']).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('allows custom Referrer-Policy', () => {
|
|
222
|
+
const headers = generateSecurityHeaders({
|
|
223
|
+
referrerPolicy: 'no-referrer',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(headers['Referrer-Policy']).toBe('no-referrer');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('validateHeaders', () => {
|
|
231
|
+
it('validates correct headers', () => {
|
|
232
|
+
const headers = {
|
|
233
|
+
'Content-Security-Policy': "default-src 'self'",
|
|
234
|
+
'Strict-Transport-Security': 'max-age=31536000',
|
|
235
|
+
'X-Content-Type-Options': 'nosniff',
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const result = validateHeaders(headers);
|
|
239
|
+
expect(result.valid).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('detects missing required headers', () => {
|
|
243
|
+
const headers = {
|
|
244
|
+
'X-Content-Type-Options': 'nosniff',
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const result = validateHeaders(headers, { required: ['Content-Security-Policy'] });
|
|
248
|
+
expect(result.valid).toBe(false);
|
|
249
|
+
expect(result.missing).toContain('Content-Security-Policy');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('detects unsafe CSP directives', () => {
|
|
253
|
+
const headers = {
|
|
254
|
+
'Content-Security-Policy': "script-src 'unsafe-inline'",
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const result = validateHeaders(headers, { strict: true });
|
|
258
|
+
expect(result.warnings).toEqual(
|
|
259
|
+
expect.arrayContaining([expect.stringContaining('unsafe-inline')])
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('createSecurityHeadersManager', () => {
|
|
265
|
+
it('creates manager with methods', () => {
|
|
266
|
+
const manager = createSecurityHeadersManager();
|
|
267
|
+
|
|
268
|
+
expect(manager.generate).toBeDefined();
|
|
269
|
+
expect(manager.validate).toBeDefined();
|
|
270
|
+
expect(manager.getCsp).toBeDefined();
|
|
271
|
+
expect(manager.getHsts).toBeDefined();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('applies default preset', () => {
|
|
275
|
+
const manager = createSecurityHeadersManager({
|
|
276
|
+
preset: 'strict',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const headers = manager.generate({});
|
|
280
|
+
expect(headers['X-Frame-Options']).toBe('DENY');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|