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 ADDED
@@ -0,0 +1,309 @@
1
+ # header-grader
2
+
3
+ > Security header grader for **local dev** — grade your dev server's HTTP security headers and generate the exact Express/Nginx config that fixes them.
4
+
5
+ [![CI](https://github.com/z1zzles/header-grader/actions/workflows/ci.yml/badge.svg)](https://github.com/z1zzles/header-grader/actions/workflows/ci.yml)
6
+ [![Node ≥ 18](https://img.shields.io/badge/node-%E2%89%A5%2018-brightgreen)](#requirements)
7
+ [![Zero dependencies](https://img.shields.io/badge/dependencies-0-blue)](#design-goals)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](#license)
9
+
10
+ ```
11
+ Grade: F (13/100) http://localhost:3000/
12
+
13
+ ✗ Content-Security-Policy
14
+ Missing. CSP is your strongest defense against XSS.
15
+ ✗ X-Content-Type-Options
16
+ Missing. Prevents MIME-type sniffing attacks.
17
+ ✗ X-Frame-Options
18
+ Missing (and no CSP frame-ancestors). Your site can be framed for clickjacking.
19
+ ...
20
+
21
+ Generate the fix: header-grader http://localhost:3000 --fix express
22
+ ```
23
+
24
+ ## Why this exists
25
+
26
+ Security headers are one of the highest-leverage, lowest-effort defenses in web development: a handful of response headers protect against XSS, clickjacking, MIME sniffing, protocol downgrade attacks, and cross-origin data leaks. Yet most projects ship without them — not because they're hard, but because nothing in the **development workflow** ever mentions them.
27
+
28
+ The existing tools all live at the wrong end of the pipeline:
29
+
30
+ - **[SecurityHeaders.com](https://securityheaders.com)** is excellent, but it scans public production URLs. It can't reach `localhost`, so you only find out after you deploy.
31
+ - **[Lighthouse](https://developer.chrome.com/docs/lighthouse)** proves the model this tool follows — automated, actionable audits against your local dev server — but security headers are a sidebar there. Its best-practices category includes a CSP-effectiveness check and a couple of related audits, and stops short of the full suite: no HSTS `max-age` grading, no Referrer-Policy, Permissions-Policy, or CORP checks, no flagging of `X-Powered-By`/`Server` version leaks — and no generated fix config. Lighthouse is to performance and accessibility what this tool aims to be for security headers.
32
+ - **Browser devtools** show you headers, but don't evaluate them or tell you what's missing.
33
+ - **Helmet's docs** tell you what to configure, but not what your app is *actually sending* after all your middleware runs.
34
+
35
+ By the time a production scanner flags the problem, the fix means a config change, a review, and a redeploy. The cheapest moment to fix a missing header is while the dev server is still running on your desk.
36
+
37
+ `header-grader` closes that gap with three ideas:
38
+
39
+ 1. **Grade the dev server, not prod.** Point it at `localhost` while you're building.
40
+ 2. **Don't just diagnose — generate the fix.** Every failing check maps to a concrete config snippet for Express (helmet) or Nginx. The snippet is a *minimal diff*: it only includes headers that are actually failing.
41
+ 3. **Be dev-aware.** A production scanner would fail you for missing HSTS on `http://localhost:3000` — but browsers ignore HSTS over plain HTTP anyway. This tool knows the difference between "wrong" and "expected in dev, don't forget it in prod."
42
+
43
+ The project grew out of web development coursework: the same philosophy as accessibility auditing (Lighthouse, axe) applied to security headers — automated, actionable feedback inside the dev loop instead of after deployment.
44
+
45
+ ## Requirements
46
+
47
+ - Node.js ≥ 18 (uses the built-in `fetch`)
48
+
49
+ ## Installation
50
+
51
+ No install needed for one-off checks:
52
+
53
+ ```sh
54
+ npx header-grader localhost:3000
55
+ ```
56
+
57
+ For the middleware or repeated use, add it as a dev dependency:
58
+
59
+ ```sh
60
+ npm install --save-dev header-grader
61
+ ```
62
+
63
+ Or globally for a system-wide CLI:
64
+
65
+ ```sh
66
+ npm install -g header-grader
67
+ ```
68
+
69
+ > **Note:** until the package is published to npm, install from a local clone:
70
+ > ```sh
71
+ > git clone <repo-url> && cd header-grader
72
+ > npm install && npm run build && npm link # makes `header-grader` available globally
73
+ > ```
74
+
75
+ ## Usage
76
+
77
+ ### 1. CLI — grade a running server
78
+
79
+ Start your dev server, then:
80
+
81
+ ```sh
82
+ header-grader localhost:3000
83
+ ```
84
+
85
+ You get a letter grade (A+ through F), a 0–100 score, and a per-header breakdown explaining what each missing header protects against.
86
+
87
+ **Generate the fix** for whatever failed:
88
+
89
+ ```sh
90
+ header-grader localhost:3000 --fix express
91
+ ```
92
+
93
+ ```js
94
+ // npm install helmet
95
+ import helmet from "helmet";
96
+
97
+ app.use(
98
+ helmet({
99
+ contentSecurityPolicy: {
100
+ directives: {
101
+ defaultSrc: ["'self'"],
102
+ scriptSrc: ["'self'"], // add CDN origins here as needed
103
+ objectSrc: ["'none'"],
104
+ baseUri: ["'self'"],
105
+ frameAncestors: ["'self'"],
106
+ },
107
+ },
108
+ ...
109
+ })
110
+ );
111
+
112
+ // Stop advertising Express:
113
+ app.disable("x-powered-by");
114
+ ```
115
+
116
+ Or for an Nginx reverse proxy:
117
+
118
+ ```sh
119
+ header-grader localhost:3000 --fix nginx
120
+ ```
121
+
122
+ ```nginx
123
+ # Add inside your server {} block
124
+ add_header Content-Security-Policy "default-src 'self'; ..." always;
125
+ add_header X-Content-Type-Options "nosniff" always;
126
+ server_tokens off;
127
+ ```
128
+
129
+ Apply the snippet, re-run the command, and watch the grade climb.
130
+
131
+ **Understand the stakes** — `--explain` adds a concrete attack scenario under every failing header:
132
+
133
+ ```sh
134
+ header-grader localhost:3000 --explain
135
+ ```
136
+
137
+ ```
138
+ ✗ X-Frame-Options
139
+ Missing (and no CSP frame-ancestors). Your site can be framed for clickjacking.
140
+ If exploited:
141
+ Clickjacking: an attacker's page loads your site in an invisible
142
+ full-screen iframe and positions a fake 'Play video' button exactly
143
+ over your real 'Delete account' or 'Transfer funds' button. The victim
144
+ clicks their page but presses yours — with their logged-in session.
145
+ ```
146
+
147
+ This is the teaching half of the tool: not just *what's* missing, but what an attacker does with the gap — session theft via injected scripts (CSP), sslstrip downgrades on public Wi-Fi (HSTS), stored XSS through file uploads (nosniff), reset-token leaks through the Referer header (Referrer-Policy), and so on.
148
+
149
+ **All CLI options:**
150
+
151
+ | Option | Description |
152
+ | --- | --- |
153
+ | `--explain` | Show how each missing header could be exploited |
154
+ | `--fix <express\|nginx>` | Print a config snippet that fixes the failing headers |
155
+ | `--json` | Output the full report as JSON |
156
+ | `--min-grade <grade>` | Exit with code 1 if the grade is below this — for CI |
157
+ | `-h, --help` | Show help |
158
+
159
+ ### 2. Express middleware — grade yourself on every boot
160
+
161
+ Instead of remembering to run a command, let your app grade itself in development:
162
+
163
+ ```js
164
+ import helmet from "helmet";
165
+ import { headerGrader } from "header-grader/middleware";
166
+
167
+ app.use(helmet());
168
+
169
+ // Mount AFTER helmet/header middleware so it sees the final headers:
170
+ if (app.get("env") === "development") {
171
+ app.use(headerGrader());
172
+ }
173
+ ```
174
+
175
+ The first time your app serves an HTML response, the report prints to the console — then it stays quiet. It grades the headers your app *actually sends*, after all middleware has run, which catches misconfigurations that reading your helmet config never would.
176
+
177
+ Works with any Connect-compatible framework (Express, plain `node:http` handlers, etc.).
178
+
179
+ **Middleware options:**
180
+
181
+ | Option | Default | Description |
182
+ | --- | --- | --- |
183
+ | `watch` | `false` | Keep grading; reprint whenever the grade changes |
184
+ | `explain` | `false` | Include the attack scenario for each failing header |
185
+ | `onReport` | — | `(report) => void` — receive the report object instead of console output |
186
+ | `isLocalHttp` | `true` | Relax HSTS scoring (browsers ignore HSTS over plain HTTP) |
187
+
188
+ ### 3. CI — enforce a minimum grade
189
+
190
+ Fail the build if headers regress:
191
+
192
+ ```yaml
193
+ # .github/workflows/ci.yml (excerpt)
194
+ - run: npm start &
195
+ - run: npx wait-on http://localhost:3000
196
+ - run: npx header-grader http://localhost:3000 --min-grade B
197
+ ```
198
+
199
+ `--json` gives you a machine-readable report if you want custom tooling:
200
+
201
+ ```sh
202
+ header-grader localhost:3000 --json | jq .score
203
+ ```
204
+
205
+ ### 4. Programmatic API
206
+
207
+ ```ts
208
+ import {
209
+ scan, // fetch a URL and grade it
210
+ gradeHeaders, // grade headers you already have (no network)
211
+ generateExpress,
212
+ generateNginx,
213
+ } from "header-grader";
214
+
215
+ const report = await scan("http://localhost:3000");
216
+ report.grade; // "F"
217
+ report.score; // 13
218
+ report.results; // per-header CheckResult[]: status, message, recommended value,
219
+ // and — for anything not passing — an `exploit` field with the
220
+ // concrete attack scenario (also present in --json output)
221
+
222
+ console.log(generateNginx(report));
223
+
224
+ // No network needed — useful in tests:
225
+ const r = gradeHeaders({ "x-content-type-options": "nosniff" }, { isLocalHttp: true });
226
+ ```
227
+
228
+ Full types (`Report`, `CheckResult`, `Grade`, `Rule`, …) are exported.
229
+
230
+ ## What it checks
231
+
232
+ Weighted checks (contribute to the score):
233
+
234
+ | Header | Weight | What passes |
235
+ | --- | ---: | --- |
236
+ | `Content-Security-Policy` | 25 | Present, without `unsafe-inline`/`unsafe-eval` in scripts or wildcard sources |
237
+ | `Strict-Transport-Security` | 20 | `max-age` ≥ 180 days; `includeSubDomains` recommended. Relaxed on plain-HTTP localhost |
238
+ | `X-Content-Type-Options` | 10 | Exactly `nosniff` |
239
+ | `X-Frame-Options` | 10 | `DENY`/`SAMEORIGIN` — or CSP `frame-ancestors`, which supersedes it |
240
+ | `Referrer-Policy` | 10 | `strict-origin-when-cross-origin` or stricter |
241
+ | `Permissions-Policy` | 10 | Present (disable features you don't use) |
242
+ | `Cross-Origin-Opener-Policy` | 5 | Present (window isolation, Spectre-class protection) |
243
+ | `Cross-Origin-Resource-Policy` | 5 | Present (controls who may embed your resources) |
244
+
245
+ Hygiene penalties (subtract points):
246
+
247
+ | Header | Penalty | Why |
248
+ | --- | ---: | --- |
249
+ | `X-Powered-By` | −3 | Advertises your framework to attackers |
250
+ | `Server` with a version number | −3 | Advertises exact software versions |
251
+ | `X-XSS-Protection` (non-zero) | −2 | Deprecated; can *introduce* vulnerabilities. Use CSP instead |
252
+
253
+ **Grade scale:** A+ ≥ 95 · A ≥ 88 · B ≥ 75 · C ≥ 60 · D ≥ 45 · F below.
254
+
255
+ Notable grading behaviors:
256
+
257
+ - `unsafe-inline` is only flagged in `script-src` (or an inherited `default-src`) — inline *styles* are a much smaller risk and common in dev.
258
+ - CSP `frame-ancestors` satisfies the clickjacking check even without `X-Frame-Options`, matching modern browser behavior.
259
+ - Missing HSTS on `http://localhost` is a soft warning, not a failure — browsers ignore HSTS over HTTP, so punishing dev servers for it is noise.
260
+
261
+ ## Design goals
262
+
263
+ - **Zero runtime dependencies.** The published package depends on nothing; `npx` startup stays fast and the supply-chain surface stays at zero.
264
+ - **One ruleset, three surfaces.** The CLI, the middleware, and the API all run the same rules, and the report and the generated snippets are derived from the same recommended values — they can never disagree.
265
+ - **Minimal-diff fixes.** Generated config only covers what's failing, so it composes with whatever you already have.
266
+
267
+ ## Project structure
268
+
269
+ ```
270
+ src/
271
+ ├── types.ts # Report, CheckResult, Grade, Rule
272
+ ├── rules.ts # one weighted rule per header + recommended values + exploit scenarios
273
+ ├── grade.ts # weighted score → letter grade
274
+ ├── scan.ts # fetch URL → headers → report
275
+ ├── report.ts # ANSI terminal formatting
276
+ ├── generators/
277
+ │ ├── express.ts # helmet config snippet
278
+ │ └── nginx.ts # add_header block
279
+ ├── cli.ts # argument parsing + exit codes
280
+ ├── middleware.ts # Express/Connect middleware
281
+ └── index.ts # public API
282
+ ```
283
+
284
+ ## Development
285
+
286
+ ```sh
287
+ npm install
288
+ npm test # vitest — rules, generators, middleware (real HTTP server)
289
+ npm run typecheck # tsc --noEmit
290
+ npm run build # tsup → dist/ (ESM + CJS + .d.ts)
291
+ ```
292
+
293
+ Try it against a deliberately bad server:
294
+
295
+ ```sh
296
+ node -e 'require("http").createServer((q,s)=>{s.setHeader("Content-Type","text/html");s.end("hi")}).listen(3456)' &
297
+ node dist/cli.js localhost:3456 --fix express
298
+ ```
299
+
300
+ ## Roadmap
301
+
302
+ - [ ] Caddy and Apache config generators
303
+ - [ ] `--watch` CLI mode — re-grade automatically as you edit config
304
+ - [ ] CSP builder: crawl the page's actual script/style origins and propose a tailored policy
305
+ - [ ] `Report-Only` CSP suggestion mode for safe rollout
306
+
307
+ ## License
308
+
309
+ MIT