What Bones

Semgrep and ZAP

Two open-source scanners — one reads the code, one attacks the running app — both configured in the repo.

Why two scanners

They catch different mistakes. Semgrep reads the source and finds patterns, like a raw SQL string or a secret read in the wrong file. OWASP ZAP never sees the source. It sends real requests to a running app and reports what comes back.

Both are free, run in Docker, and need no account. Their config lives in scripts/security, so a rule changes in the same pull request as the code it covers.

Semgrep

Bones doesn't use Semgrep's public rule packs. It has 15 rules of its own in scripts/security/semgrep/bones.yml, each one a line of SECURITY_REVIEW.md turned into a pattern:

  • Secrets — dumping the environment, reading a secret outside its module, or inlining one into a client build.
  • Database — sql.raw, SQL built from strings, and like without escaping.
  • Input — unbounded strings and arrays, and loose schemas.
  • Auth — reading the session outside auth.ts, or trusting X-Forwarded-For.
  • Rendering — HTML injected into the page.

bones.ts and bones.tsx beside the rules hold good and bad examples. CI tests the rules against them before scanning, so a rule that stops matching fails loudly instead of passing everything.

ZAP

ZAP runs two scans against a throwaway copy of the stack:

  • A passive baseline of the web app. It crawls the pages and flags missing headers, weak cookies, and leaked information.
  • An active scan of the backend. It sends SQL injection, open-redirect, and path probes to every signed-out route in scripts/security/zap/backend-openapi.yaml. A local run sent 823 requests and got no server errors.

Every ZAP rule fails the scan unless scripts/security/zap/*.conf lowers it to a warning, with the reason on the same line.

Alongside them

Two checks are small scripts, not tools:

  • csp-check.mjs loads every page in Chromium through Playwright and fails on any Content Security Policy violation.
  • client-secrets.mjs fails when a client reads an unlisted env var, or when a built bundle contains a secret.

GitHub's Dependabot alerts cover dependencies.

Used in