scythe audit
scythe audit migrations/*.sql
scythe audit --format sarif -o audit.sarifExit codes: 0 (no errors, or --exit-zero), 1 (configuration error), 2 (error-severity findings).
Rule catalog#
Security (SC-SEC*, 12 rules)#
| ID | Name | Severity | Coverage |
|---|---|---|---|
| SC-SEC01 | dangerous-function | error | Filesystem, network, shell access (CWE-78) |
| SC-SEC02 | grant-all | error | GRANT ALL privilege widening (CWE-269) |
| SC-SEC03 | grant-to-public | error | GRANT … TO PUBLIC (CWE-269) |
| SC-SEC04 | superuser-role | error | High-privilege role attributes — Postgres only (CWE-269) |
| SC-SEC05 | literal-password | error | Hard-coded passwords in role creation — Postgres only (CWE-798) |
| SC-SEC06 | weak-hash-in-auth | error | md5()/sha1() over credentials (CWE-327, CWE-916) |
| SC-SEC07 | select-star-pii | warn | SELECT * with PII/credential columns (CWE-200) |
| SC-SEC08 | cartesian-join | error | Unconstrained joins (CWE-400) |
| SC-SEC09 | unbounded-like | warn | LIKE '%…%' patterns (CWE-1333) |
| SC-SEC10 | security-definer-no-search-path | error | Missing pinned search_path — Postgres only (CWE-426) |
| SC-SEC11 | session-mutation | error | SET ROLE/SET SESSION AUTHORIZATION/RESET ROLE — Postgres only (CWE-269) |
| SC-SEC12 | function-search-path-mutable | warn | CREATE FUNCTION without explicit SET search_path — Postgres only (CWE-426) |
Row Level Security (SC-RLS*, 3 rules, Postgres-only)#
| ID | Name | Severity | Coverage |
|---|---|---|---|
| SC-RLS01 | policy-references-user-metadata | error | Editable JWT claims instead of app_metadata (CWE-639) |
| SC-RLS02 | policy-always-permissive | error | Permissive RLS policy with an always-true condition (CWE-285) |
| SC-RLS03 | policy-uses-uncached-auth-function | warn | Direct calls to auth.uid()/auth.jwt()/current_setting() (CWE-405) |
Migration Safety (SC-MIG*, 19 rules, Postgres-only)#
| ID | Name | Severity | Coverage |
|---|---|---|---|
| SC-MIG01 | ban-drop-table | error | DROP TABLE — irreversible |
| SC-MIG02 | ban-drop-column | error | ALTER TABLE … DROP COLUMN — breaks deployed readers |
| SC-MIG03 | require-concurrent-index-creation | error | CREATE INDEX without CONCURRENTLY — ACCESS EXCLUSIVE lock |
| SC-MIG04 | renaming-column | error | ALTER TABLE … RENAME COLUMN — breaks deployed readers |
| SC-MIG05 | constraint-missing-not-valid | error | ADD CONSTRAINT without NOT VALID — validates all rows |
| SC-MIG06 | ban-drop-database-or-schema | error | DROP DATABASE/DROP SCHEMA — irreversible |
| SC-MIG07 | renaming-table | error | ALTER TABLE … RENAME TO — breaks deployed readers |
| SC-MIG08 | ban-truncate-cascade | error | TRUNCATE … CASCADE — clears referencing tables |
| SC-MIG09 | ban-alter-column-type | error | ALTER COLUMN … TYPE — full table rewrite |
| SC-MIG10 | prefer-bigint-over-int | error | 32-bit integer types — overflow risk |
| SC-MIG11 | prefer-text-over-varchar | error | Variable-length strings — length increases block writes |
| SC-MIG12 | prefer-timestamptz | error | timestamp without time zone — session timezone issues |
| SC-MIG13 | prefer-identity-over-serial | error | serial/bigserial/smallserial types |
| SC-MIG14 | disallowed-unique-constraint | error | ADD CONSTRAINT … UNIQUE — inline index building |
| SC-MIG15 | adding-primary-key-constraint | error | ADD CONSTRAINT … PRIMARY KEY — inline index building |
| SC-MIG16 | ban-create-domain-with-constraint | error | CREATE DOMAIN with CHECK — validates all rows |
| SC-MIG17 | ban-drop-not-null | error | ALTER COLUMN … DROP NOT NULL — relaxes deployed contracts |
| SC-MIG18 | adding-not-nullable-field | warn | ADD COLUMN … NOT NULL without DEFAULT |
| SC-MIG19 | unsupported-reg-types | error | reg* OID columns — pg_upgrade incompatibility |
Antipattern (SC-CHK01, 1 rule)#
| ID | Name | Severity | Coverage |
|---|---|---|---|
| SC-CHK01 | check-constraint-always-true | warn | Tautological CHECK constraints — Postgres only |
Suppression#
-- scythe-audit: ignore[SC-SEC02] reason="security-reviewed: vetted role"
GRANT ALL ON internal_audit TO ops_admin;Suppress multiple rules with ignore[SC-SEC01,SC-SEC02]. Run without any suppressions honored using --ignore-suppressions.
Severity filtering and exit codes#
| Desired outcome | Flags |
|---|---|
| Errors only | --severity error |
| Warnings, no build failure | --exit-zero |
| Block on errors (default) | (none) |
| Advisory mode | --severity warn --exit-zero |
User-defined rules#
Custom rule IDs must start with USER- and are declared under [audit] or extra_rules in scythe.toml — see Configuration for the full block syntax and 28 available matchers (function_name_in_set, grant_kind, cartesian_join, drop_statement, and more).
CI integration#
GitHub Actions — SARIF upload:
- name: Run scythe audit
run: scythe audit --format sarif -o audit.sarif --exit-zero
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: audit.sarifGitLab CI — SAST report:
audit:
image: rust:latest
script:
- cargo install scythe-cli
- scythe audit --format json -o gl-sast-report.json --exit-zero
artifacts:
reports:
sast: gl-sast-report.jsonPre-commit — block on errors:
- repo: https://github.com/Goldziher/scythe
rev: v0.15.0
hooks:
- id: scythe-auditBy default scythe audit exits with code 2 on error findings, which pre-commit treats as a hook failure. Add --exit-zero for advisory-only integration.
Related#
- Lint Rule Reference — the 23
scythe lint-only rules, separate from this audit catalog. - CLI Reference — every
scythe auditflag.