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/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
+ });