-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserialize-cli.js
More file actions
116 lines (103 loc) · 3.18 KB
/
Copy pathserialize-cli.js
File metadata and controls
116 lines (103 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env node
// CLI for the complete APP-01 CSP/security-header/deployment-origin gate.
//
// Usage:
// node src/security/serialize-cli.js
// node src/security/serialize-cli.js --json
// node src/security/serialize-cli.js --check
// node src/security/serialize-cli.js --baseline <path>
//
// CSP_APPROVED_ORIGINS contains comma-separated canonical origins, not full MCP
// endpoint URLs. CSP_APPROVED_ENDPOINTS is a temporary deprecated alias.
import {
loadBaseline,
serializeCsp,
DEFAULT_BASELINE_PATH,
CspValidationError,
} from './csp.js';
import {
buildHardenedHeaderMap,
validateHardenedBaseline,
} from './header-values.js';
function failUsage(message) {
process.stderr.write(`${message}\n`);
process.exit(2);
}
function parseArgs(argv) {
const opts = { mode: 'header', baseline: DEFAULT_BASELINE_PATH };
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === '--json') opts.mode = 'json';
else if (arg === '--check') opts.mode = 'check';
else if (arg === '--header') opts.mode = 'header';
else if (arg === '--baseline') {
if (i + 1 >= argv.length || argv[i + 1].startsWith('--')) {
failUsage('--baseline requires a path');
}
opts.baseline = argv[++i];
} else {
failUsage(`unknown argument: ${arg}`);
}
}
return opts;
}
function parseOriginList(raw) {
if (!raw) return [];
return raw
.split(',')
.map((value) => value.trim())
.filter(Boolean);
}
function approvedOriginsFromEnv() {
const current = process.env.CSP_APPROVED_ORIGINS;
const legacy = process.env.CSP_APPROVED_ENDPOINTS;
if (current && legacy) {
failUsage(
'set only CSP_APPROVED_ORIGINS; CSP_APPROVED_ENDPOINTS is a deprecated alias and both cannot be used together',
);
}
if (legacy) {
process.stderr.write(
'warning: CSP_APPROVED_ENDPOINTS is deprecated; use CSP_APPROVED_ORIGINS (values are origins, not endpoint URLs)\n',
);
}
return parseOriginList(current || legacy);
}
function main() {
const opts = parseArgs(process.argv.slice(2));
const approvedEndpoints = approvedOriginsFromEnv();
let baseline;
try {
baseline = loadBaseline(opts.baseline);
} catch (err) {
process.stderr.write(`failed to load baseline: ${err.message}\n`);
process.exit(1);
}
if (opts.mode === 'check') {
const errors = validateHardenedBaseline(baseline, { approvedEndpoints });
if (errors.length > 0) {
process.stderr.write('CSP/security-header baseline REJECTED:\n');
for (const error of errors) process.stderr.write(` - ${error}\n`);
process.exit(1);
}
process.stdout.write(
'CSP/security-header baseline OK: exfiltration, endpoint and header-value boundaries enforced.\n',
);
process.exit(0);
}
try {
const headers = buildHardenedHeaderMap(baseline, { approvedEndpoints });
if (opts.mode === 'json') {
process.stdout.write(`${JSON.stringify(headers, null, 2)}\n`);
} else {
process.stdout.write(`${serializeCsp(baseline.directives)}\n`);
}
} catch (err) {
if (err instanceof CspValidationError) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
}
throw err;
}
}
main();