Postgres PII discovery
Find where personal data lives. Never take a copy home.
redact-scan is a read-only Postgres scanner powered by the same
Redact engine that sits in the request path. Prefer a replica or staging
database. The report lists table/column locations and entity types — never
sample values.
$ cargo install redact-scan
$ redact-scan --url postgres://reader@localhost/app --schema public --out report.json
→ findings: public.customers.email EMAIL_ADDRESS
→ findings: public.orders.payload $.ssn US_SSN
→ locations and counts only — never values Safety first
Refuse write grants. Scrub credentials. Bound every sample.
Session
Read-only by construction
Opens with default_transaction_read_only = on, statement and
lock timeouts. Refuses superuser roles and
INSERT / UPDATE / DELETE / TRUNCATE
grants on tables and columns.
Queries
No SELECT *
Quotes enumerated identifiers only. Passwords are scrubbed from errors, logs, and the panic hook. A single connection in the pool.
Samples
Local sidecar only
--include-samples writes a local sidecar and cannot be combined
with --report-url. Samples are never merged into the scan report.
Reports
Locations, not values
Findings carry table, column, entity type, layer, counts, and confidence.
Optional --report-url POSTs the same JSON — still without values.
Four layers
Catalog first. Sample only when needed.
Select layers with --layers. Default: 0,0.5,1,2.
| Layer | Source | User-table reads |
|---|---|---|
| 0 | pg_catalog / information_schema names, types, comments, indexes, FKs | No |
| 0.5 | pg_stats most-common values and histogram bounds | No |
| 1 | Bounded TABLESAMPLE SYSTEM + LIMIT on ordinary tables | Yes, bounded |
| 2 | JSON/JSONB path-level detections on sampled documents | Yes, bounded |
Exit codes
Fail the job when findings match.
0— clean1—--fail-onmatched findings2— error
