@misar/compliance-guards (0.1.0)
Installation
@misar:registry=npm install @misar/compliance-guards@0.1.0"@misar/compliance-guards": "0.1.0"About this package
@misar/compliance-guards
Zero-dependency, advisory-first CI validator for the legal / compliance /
security posture of a Misar repo — plus a scaffold that writes the missing
evidence & legal artifacts. Sibling of @misar/guards; same
architecture (config-driven guards, Forgejo annotations, --advisory/--only/
--json). Pure Node.js ESM — Node built-ins only.
A CI guard validates presence and configuration. It does not certify. A green run is hygiene evidence, not a SOC 2 report, ISO 27001 certificate, or legal sign-off. See
docs/DEVOPS/compliance-validation.mdfor the full CAN-validate vs CANNOT-certify table per framework.
Install / run
Published to the private Forgejo registry (@misar scope):
pnpm add -D @misar/compliance-guards
pnpm misar-compliance-guards # advisory by default (warns, exit 0)
Or without adding a dep:
npx @misar/compliance-guards # advisory
node packages/compliance-guards/bin/misar-compliance-guards.mjs --help
The CLI reads compliance-guards.config.json from the current working
directory (repo root).
CLI contract
misar-compliance-guards [options]
--advisory Force advisory: ::warning annotations, ALWAYS exit 0. (DEFAULT.)
--blocking Force blocking mode (findings fail the build) regardless of config.
--only=<ids> Run only these guard ids (comma-separated). Bypasses config on/off.
--json Machine-readable JSON: { ran, counts, advisory, findings }.
--config=<path> Use a config file at <path> instead of ./compliance-guards.config.json.
--list List all guard ids + whether each is default-on. Exit 0.
-h, --help Show help.
Exit codes: 0 no findings (or advisory) · 1 findings in blocking mode ·
2 usage/config error.
Advisory-first: unlike @misar/guards (blocking by default), this package
defaults to advisory. Make it blocking with "advisory": false in config or
--blocking.
Output is Forgejo-Actions annotations, one per finding:
::warning file=src/app/layout.tsx,line=252,title=misar-compliance-guards/no-pre-consent-trackers::Pre-consent tracker: raw <script src="…"> …
Config (compliance-guards.config.json)
{
"product": "MisarReach", // used in messages + scaffold identity
"advisory": true, // DEFAULT true; set false to block
"ignore": ["packages/compliance-guards", "packages/guards"],
"requiredPages": ["privacy","terms","cookies","acceptable-use","refund-policy","imprint","accessibility-statement"],
"cmsSlugs": ["dpa","subprocessors"], // legal slugs served via CMS /legal/[slug]
"guards": { "<guard-id>": true | false | { ...options } }
}
Resolution (same as @misar/guards): a listed guard is true/{opts} (on) or
false (off); an unlisted guard runs iff it is default-on. Copy
compliance-guards.config.example.json (it ships per-product requiredPages
profiles under _profiles).
Guards (all default-on — advisory-first)
| id | checks | options |
|---|---|---|
no-pre-consent-trackers |
analytics/marketing <script> (ahrefs/GTM/GA/PostHog/Hotjar/Clarity/Segment) in a layout/<head> not consent-gated. A raw <script src> is always flagged; a <Script>/loader needs a consent token within ~14 lines. The headline guard — catches the MisarMail + assisters Ahrefs bug. |
component |
cookie-consent-mounted |
shared CookieConsent banner rendered somewhere under each Next app root. |
component (default CookieConsent) |
legal-page-presence |
each requiredPages slug has a route (app/<slug>/page.*) or CMS /legal/[slug] (in cmsSlugs), and is footer-linked; flags dead footer links (legal href, no route). |
pages, cmsSlugs |
gdpr-endpoints-present |
data export (api/gdpr/export, `api/privacy/export |
request) + **erasure** (api/gdpr/delete, api/internal/user/[userId]` DELETE) endpoints. Silent if the repo exposes no API. |
security-headers-baseline |
the 6 headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) in each app's next.config.* headers() or middleware; flags apps setting none. |
— |
compliance-evidence-present |
evidence docs exist + non-empty (a .gitkeep-only file counts as missing): SECURITY, INCIDENT_RESPONSE, DATA_RETENTION/LOG_RETENTION, SUBPROCESSORS, DPA/ROPA, CONTROLS_MATRIX. Evidence check, not certification. |
require (array of ids) |
codeowners-present |
a CODEOWNERS with ≥1 ownership rule (root, .forgejo/, .github/, docs/). |
— |
security-txt-present |
/.well-known/security.txt (static or route). Silent for non-web repos. |
— |
Run misar-compliance-guards --list for the live list. Every finding is a
warning (structural heuristic).
Overlap with @misar/guards (no duplication)
Company-identity content (no placeholder identity; canonical CIN/GSTIN/TAN) is
the job of @misar/guards legal-identity-placeholders. This package does not
re-validate identity strings — it checks that evidence/legal files exist and
uses the canonical identity only when scaffolding new files. Run both packages.
Scaffold — misar-compliance-scaffold
Writes the missing compliance/legal artifacts from templates/. Dry-run by
default; --write applies. Never overwrites existing files.
node packages/compliance-guards/bin/scaffold.mjs --dir=/path/to/repo # dry run
node packages/compliance-guards/bin/scaffold.mjs --dir=/path/to/repo --write # apply
Reads product (+ optional legalPagesDir) from config, substitutes
{{CIN}}/{{GSTIN}}/{{TAN}}/{{PRODUCT}}/{{DATE}} … into the templates, and
leaves all legal prose as TODO: legal review placeholders for a human to
complete. Artifacts:
SECURITY.md— vulnerability disclosure + security.txt pointerCODEOWNERSdocs/compliance/{INCIDENT_RESPONSE,DATA_RETENTION,SUBPROCESSORS,DPA,CONTROLS_MATRIX}.md- legal page stubs
refund-policy,imprint,accessibility-statementundersrc/app/app
The advisory → blocking ramp
- Observe — run advisory (default) in CI; findings are
::warning, job green. - Fix / scaffold — clear findings;
misar-compliance-scaffold --writefor the stubs, then complete theTODO: legal reviewcontent. - Enforce — set
"advisory": false(or--blocking). Findings now fail.
Programmatic use
import { GUARDS, GUARDS_BY_ID, DEFAULT_GUARDS } from "@misar/compliance-guards";
import { scaffold, plan, render, buildTokens } from "@misar/compliance-guards/scaffold";
Each guard exports { id, title, run(ctx) -> findings[] }; a finding is
{ guard, file, line, message, severity }.
Tests
node tests/test.mjs # 34 fixture assertions, zero deps