Zum Inhalt springen

Open source / Sentinel / header-sentinel

header-sentinel

CI-friendly CLI and API that fails builds when deployed URLs are missing or misconfigure HTTP security headers (HSTS, CSP, COOP, cookies...) with specific fixes. JSON & SARIF output.

Auf npm
$ npm install header-sentinel

README

header-sentinel

npm CI license: MIT node >=18 zero deps

Fail your CI when deployed URLs are missing or misconfigure HTTP security headers — with a specific fix (example header + Nginx/Express/Vercel/Netlify hints) for every problem. Mozilla HTTP Observatory-style letter grades (A+ through F), zero runtime dependencies, Node ≥ 18, JSON and SARIF (GitHub code scanning) output.

Install

npm i -D header-sentinel        # or run ad-hoc:
npx header-sentinel https://example.com

Usage

header-sentinel <url...> [options]

  -c, --config <file>       JSON config (default: .headerguardrc.json / .headersentinelrc.json if present)
      --fail-on <level>     error | warn | none   (default: error)
      --json                Print JSON results (includes observatory score/tests)
      --sarif [file]        Print SARIF 2.1.0 (or write to file)
      --sitemap <url>       Load URLs from sitemap.xml (repeatable)
      --baseline <file>     Only fail on findings not in baseline
      --update-baseline     Write current fingerprints to --baseline
      --no-http-upgrade     Skip HTTP→HTTPS redirect analysis
      --no-preload-check    Skip HSTS preload list / eligibility lookups
  -t, --timeout <ms>        Request timeout (default: 10000)
  -H, --header "K: V"       Extra request header (repeatable)
      --no-follow-redirects Check the first response instead of the final one (default follows)

Exit codes: 0 pass, 1 findings at/above --fail-on, 2 usage error. Colours are disabled when NO_COLOR is set or stdout is not a TTY (FORCE_COLOR=1 forces them).

Scoring (Mozilla Observatory)

Sites start at 100. Penalties apply first; bonuses (for example strong CSP, Referrer-Policy, frame-ancestors, HSTS preload) apply only if the pre-bonus score is ≥ 90. Grades use Observatory buckets: A+ (100+), A, A-, B+ … F (0–24). Each scan also reports a likelihood indicator (LOW/MEDIUM) derived from the grade.

Detailed header lint findings (severity error/warn) are separate from Observatory test keys; CI failure is still driven by --fail-on and findings, while --json exposes score, grade, and observatory.tests[].

Sample output

$ npx header-sentinel https://github.com --no-http-upgrade --no-preload-check

https://github.com/ (HTTP 200)  Grade B+ score 80/100 · MEDIUM risk
  ⚠ warn  CSP style-src allows 'unsafe-inline' [csp-unsafe-inline]
  ...

Baseline (CI: fail only on new issues)

# First run on main — record accepted findings
npx header-sentinel https://example.com --baseline .header-baseline.json --update-baseline

# PR / nightly — exit 1 only if something new appears
npx header-sentinel https://example.com --baseline .header-baseline.json --fail-on error

Sitemap mode

npx header-sentinel --sitemap https://example.com/sitemap.xml --baseline .header-baseline.json

Checks

idseveritywhat
hstserrorStrict-Transport-Security on HTTPS with max-age ≥ 31536000
hsts-subdomainswarnHSTS includes includeSubDomains
hsts-preload-eligiblewarnHeader meets preload submission requirements (not yet on preload list)
redirectionerror/warnHTTP→HTTPS upgrade / redirect chain (Observatory-aligned)
csperrorContent-Security-Policy present (live header lint only; use csp-forge to build policies)
csp-unsafe-inline / csp-unsafe-evalwarnunsafe keywords in *-src
csp-wildcardwarnoverly broad sources
csp-missing-*warnimportant directives missing
frame-optionserrorX-Frame-Options or CSP frame-ancestors
content-type-optionserrorX-Content-Type-Options: nosniff
referrer-policywarn/errorsafe Referrer-Policy
permissions-policywarnPermissions-Policy present
coopwarnCross-Origin-Opener-Policy
info-leak-*warnServer / X-Powered-By leaks
cookieswarnSet-Cookie presence only — run cookie-sentinel for full cookie audits
x-xss-protectionwarndeprecated header enabled
fetcherrorURL could not be fetched

No TLS certificate or DNS checks (see tls-sentinel / dns-sentinel).

Config

.headerguardrc.json (or .headersentinelrc.json, or --config path):

{
  "urls": ["https://example.com"],
  "sitemap": "https://example.com/sitemap.xml",
  "baseline": ".header-baseline.json",
  "failOn": "error",
  "timeout": 10000,
  "checkHttpRedirection": true,
  "checkPreload": true,
  "headers": { "User-Agent": "my-ci" },
  "ignore": ["coop", "info-leak-server"],
  "severity": { "permissions-policy": "error", "csp-unsafe-inline": "off" }
}

CI

GitHub Actions (action)

name: Security headers
on:
  deployment_status:
  schedule: [{ cron: '0 6 * * 1' }]
  workflow_dispatch:
permissions: { contents: read, security-events: write }
jobs:
  headers:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: MrLouisWright/header-sentinel@v1
        with:
          urls: https://example.com https://example.com/login
          fail-on: error
          sarif: header-sentinel.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: header-sentinel.sarif }

GitHub Actions (npx)

- uses: actions/setup-node@v4
  with: { node-version: 20 }
- run: npx header-sentinel@1 https://staging.example.com --json --fail-on error

API

ESM

import { scan, scanAll, analyze, shouldFail, toSarif, computeObservatoryScore } from 'header-sentinel';

CommonJS

const { scan, shouldFail } = require('header-sentinel');
const results = await scanAll(['https://example.com'], {
  timeout: 5000,
  config: { ignore: ['coop'] },
  checkHttpRedirection: true,
});
console.log(results[0].grade, results[0].observatory.tests);
if (shouldFail(results, 'error')) process.exit(1);

Types are generated in dist/index.d.ts (import / require typings via dist/index.d.cts).

Support

If this project is useful to you, you can support its development with a donation: Buy me a coffee. It helps keep these tools maintained, documented and free for everyone.

Credit (optional)

Free to use under MIT, no attribution required. If header-sentinel helps you, a link back is appreciated:

<a href="https://louiswright.co.uk">Built with header-sentinel by Louis Wright</a>
[![Built with header-sentinel by Louis Wright](https://img.shields.io/badge/built%20with-header--sentinel-blue)](https://louiswright.co.uk)

License

MIT © Louis Wright (Louis Wright)