header-grader 0.1.0
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/README.md +309 -0
- package/dist/chunk-GQYXZFYW.js +456 -0
- package/dist/chunk-IW5F2ZYM.js +111 -0
- package/dist/chunk-O3B2LYGP.js +45 -0
- package/dist/cli.cjs +652 -0
- package/dist/cli.js +116 -0
- package/dist/index.cjs +637 -0
- package/dist/index.d.cts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +38 -0
- package/dist/middleware-BsJGVvzp.d.cts +64 -0
- package/dist/middleware-BsJGVvzp.d.ts +64 -0
- package/dist/middleware.cjs +483 -0
- package/dist/middleware.d.cts +2 -0
- package/dist/middleware.d.ts +2 -0
- package/dist/middleware.js +9 -0
- package/package.json +53 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
generateExpress,
|
|
4
|
+
generateNginx
|
|
5
|
+
} from "./chunk-IW5F2ZYM.js";
|
|
6
|
+
import {
|
|
7
|
+
formatReport,
|
|
8
|
+
isGrade,
|
|
9
|
+
meetsGrade,
|
|
10
|
+
scan
|
|
11
|
+
} from "./chunk-GQYXZFYW.js";
|
|
12
|
+
|
|
13
|
+
// src/cli.ts
|
|
14
|
+
var HELP = `header-grader \u2014 grade your dev server's security headers
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
header-grader <url> [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--explain Show how each missing header could be exploited
|
|
21
|
+
--fix <express|nginx> Print a config snippet that fixes the failing headers
|
|
22
|
+
--json Output the full report as JSON
|
|
23
|
+
--min-grade <grade> Exit 1 if the grade is below this (A+, A, B, C, D) \u2014 for CI
|
|
24
|
+
-h, --help Show this help
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
header-grader localhost:3000
|
|
28
|
+
header-grader localhost:3000 --explain
|
|
29
|
+
header-grader http://localhost:8080 --fix nginx
|
|
30
|
+
header-grader localhost:3000 --min-grade B --json
|
|
31
|
+
`;
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = { url: "", json: false, explain: false };
|
|
34
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35
|
+
const arg = argv[i];
|
|
36
|
+
switch (arg) {
|
|
37
|
+
case "-h":
|
|
38
|
+
case "--help":
|
|
39
|
+
return null;
|
|
40
|
+
case "--json":
|
|
41
|
+
args.json = true;
|
|
42
|
+
break;
|
|
43
|
+
case "--explain":
|
|
44
|
+
args.explain = true;
|
|
45
|
+
break;
|
|
46
|
+
case "--fix": {
|
|
47
|
+
const target = argv[++i];
|
|
48
|
+
if (target !== "express" && target !== "nginx") {
|
|
49
|
+
throw new Error(`--fix expects "express" or "nginx", got "${target ?? ""}"`);
|
|
50
|
+
}
|
|
51
|
+
args.fix = target;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "--min-grade": {
|
|
55
|
+
const grade = argv[++i]?.toUpperCase();
|
|
56
|
+
if (!grade || !isGrade(grade)) {
|
|
57
|
+
throw new Error(`--min-grade expects one of A+, A, B, C, D, F \u2014 got "${grade ?? ""}"`);
|
|
58
|
+
}
|
|
59
|
+
args.minGrade = grade;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
64
|
+
if (args.url) throw new Error("Only one URL at a time.");
|
|
65
|
+
args.url = arg;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!args.url) throw new Error("Missing URL. Try: header-grader localhost:3000");
|
|
69
|
+
return args;
|
|
70
|
+
}
|
|
71
|
+
async function main() {
|
|
72
|
+
let args;
|
|
73
|
+
try {
|
|
74
|
+
args = parseArgs(process.argv.slice(2));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`Error: ${err.message}
|
|
77
|
+
`);
|
|
78
|
+
console.error(HELP);
|
|
79
|
+
process.exit(2);
|
|
80
|
+
}
|
|
81
|
+
if (args === null) {
|
|
82
|
+
console.log(HELP);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let report;
|
|
86
|
+
try {
|
|
87
|
+
report = await scan(args.url);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const cause = err.cause;
|
|
90
|
+
if (cause?.code === "ECONNREFUSED") {
|
|
91
|
+
console.error(`Could not connect to ${args.url} \u2014 is your dev server running?`);
|
|
92
|
+
} else {
|
|
93
|
+
console.error(`Failed to scan ${args.url}: ${err.message}`);
|
|
94
|
+
}
|
|
95
|
+
process.exit(2);
|
|
96
|
+
}
|
|
97
|
+
if (args.json) {
|
|
98
|
+
console.log(JSON.stringify(report, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
console.log(formatReport(report, { explain: args.explain }));
|
|
101
|
+
}
|
|
102
|
+
if (args.fix) {
|
|
103
|
+
const snippet = args.fix === "express" ? generateExpress(report) : generateNginx(report);
|
|
104
|
+
if (!args.json) console.log("\u2500".repeat(60) + "\n");
|
|
105
|
+
console.log(snippet);
|
|
106
|
+
console.log("");
|
|
107
|
+
}
|
|
108
|
+
if (args.minGrade && isGrade(args.minGrade) && !meetsGrade(report.grade, args.minGrade)) {
|
|
109
|
+
console.error(`Grade ${report.grade} is below the required minimum of ${args.minGrade}.`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
main().catch((err) => {
|
|
114
|
+
console.error(err);
|
|
115
|
+
process.exit(2);
|
|
116
|
+
});
|