GitHub Packages · MIT · Node ≥ 18

policy-drift-warden

Compare the trackers and third parties your built site actually loads with what its privacy policy says, and fail CI when they drift apart.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/policy-drift-warden
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx policy-drift-warden dist --base-url https://acme.examplepolicy-drift-warden — https://acme.example3 page(s); 3 third parties; policy: privacy-policy.html HIGH     index.html Meta Pixel (Meta) [advertising] is loaded on 1 page(s) but not named in the policy [policy-undisclosed-vendor]         ↳ fix: Name Meta Pixel / Meta, its purpose and legal basis in the privacy policy, or remove it.HIGH     index.html Hotjar (Hotjar) [session-replay] is loaded on 1 page(s) but not named in the policy [policy-undisclosed-vendor]         ↳ fix: Name Hotjar / Hotjar, its purpose and legal basis in the privacy policy, or remove it.HIGH     privacy-policy.html Vendors that sell/share data (Meta Pixel) are used, but the policy does not mention selling, sharing or targeted advertising [policy-sale-share-undisclosed]         ↳ fix: Disclose sale/sharing for targeted advertising and the opt-out (CCPA/CPRA, GDPR Art. 13).MEDIUM   index.html 2 page(s) do not link to the privacy policy, e.g. /, /shop.html [policy-not-linked]
Sample output from the policy-drift-warden README.

Why it exists

Privacy policies are written once and sites keep changing. Marketing adds a Meta pixel, product adds Hotjar, and the policy still only mentions Google Analytics. Regulators and complainants check exactly this gap. policy-drift-warden builds the third-party inventory from your built HTML (the same detection as tracker-warden), reads your privacy policy page or file, and reports every vendor, category or sale/share practice that is not disclosed, with a concrete fix.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. Works on any static build output (Astro, Next.js “output: 'export'”, Eleventy, Hugo, Jekyll, Docusaurus, VitePress, plain HTML) and runs offline: point it at the folder your build writes. By Louis Wright.

Features

Vendor disclosure

each loaded vendor must be named in the policy by name, company alias ("Facebook" for Meta Pixel) or domain. Unknown third-party domains must be named too.

Category disclosure

advertising, session recording/heatmaps, analytics and social plugins must be described.

Sale/sharing

if vendors that sell or share data for advertising are present, the policy must mention selling, sharing or targeted advertising.

Stale disclosures

tracking vendors named in the policy that the site no longer loads.

Freshness

"Last updated" / effective date present (ISO, 3 March 2025, January 5, 2024, 01.02.2023) and not older than --max-age-days (365).

Reachability

every page links to the policy.

Usage

# Example

 npx policy-drift-warden dist

# Example

 npx policy-drift-warden dist --policy legal/privacy.md --fail-on medium

# Example

 npx policy-drift-warden public --trackers-only --max-age-days 180

# Example

 npx policy-drift-warden out --policy-pages "/legal/privacy/**" --format sarif > policy.sarif

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  policy-drift-warden:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - run: npm ci && npm run build   # produce the static site first
      - uses: MrLouisWright/policy-drift-warden@v1
        with:
          path: dist
          fail-on: high
          sarif: policy-drift-warden.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: policy-drift-warden.sarif }

Options & flags

FlagDescription
Site
--base-url <url>Public URL of the site, e.g. https://example.com (default: inferred from canonical/og:url)
--include <globs>Only check these pages (file or URL path globs, comma-separated)
--exclude <globs>Skip these pages
--clean-urlsPages are served without .html (about.html -> /about)
Policy
--policy <file>Privacy policy file (HTML, Markdown or text) instead of a page in the build
--policy-pages <globs>Pages that make up the policy (default: privacy*, cookie-policy, datenschutz, ...)
--max-age-days <n>Report the policy as outdated after n days (default 365)
--trackers-onlyOnly require disclosure of tracking vendors (not fonts/CDN/video/payments)
Common
-c, --config <file>JSON config (default: .policy-drift-warden.json or "policyDriftWarden" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20