hipaa runs seccover and adds the audit-log check. For each table it shows whether every INSERT, UPDATE and DELETE is recorded, where, with which values, and whether that record can be altered. The check only reads. It writes one HIPAA report covering access control, audit controls, integrity, the Security Rule mapping and an evidence stamp. Enterprise edition.
unitautogen hipaa --schema public --out unitautogen_tests
--schema, --out, --sec-gate and the rest--config FILE declares audit or PHI tables the tool missed. ./unitautogen.toml is read automatically.--storage also checks Supabase Storage policies and public buckets, see below<out>/hipaa/<schema>/hipaa_report.html, with .json beside it. It links the seccover and unit reports for detail.unitautogen --supabase hipaa --schema public writes to supabase/tests/seccover/hipaa/<schema>/ and turns on --storageTG_KEEP_TEST_SCHEMAS=1 keeps them.hipaa writes no tests of its own. The tests behind its report are these.
| Evidence | Folder | Written by |
|---|---|---|
| Access control, functions, views and triggers | <out>/security/<schema>/functions/, views/, triggers/ | seccover |
| Access control, RLS policies | <out>/security/<schema>/rls/ | seccover |
| Access control, column-level security | <out>/security/<schema>/cls/ | seccover |
| Audit controls and integrity | <out>/unit/<schema>/triggers/ | trigger unit tests |
| PII in error messages | <out>/pii_tests/ | pii |
security/ and unit/, see CI and CD--no-emit-tests the suites run and are then removed, and the report says soThe report reads like a short book. A contents table shows each chapter, what it answers and which chapters found a gap. Each chapter opens with an In brief box before its detail tables. Above chapter 1, a Bounds table lists the roles the matrices cannot bind, such as superusers, the schema owner, BYPASSRLS roles and TRUNCATE holders, and the pgaudit state. The scope is the database layer.
| Chapter | What it shows |
|---|---|
| 1. Summary | One row per safeguard, 164.312(a)(1) access control, (b) audit controls, (c)(1) integrity of the audit record and (d) authentication, marked gap found, partly tested, no gaps found, not applicable or not tested, with the reason. |
| 2. What to fix | Every gap with its priority, HIPAA rule, object, finding, the database change that closes it and the chapter with the evidence. Fixes that exist only because PostgreSQL grants EXECUTE to PUBLIC by default are marked and explained once, with the REVOKE that closes them all. |
| 3. Reports | One row per report this one draws on, with what it covers and a link. |
| 4. Access control | 164.312(a)(1) and (d). The RLS test identities, the RLS policy verdict per table, and every SECURITY DEFINER function, view and trigger a client reaches, with its verdict. Plus RLS bypass surfaces, column-grant leaks and PII-in-error findings. |
| 5. Audit controls | 164.312(b). One row per data table with a cell per INSERT, UPDATE and DELETE. A cell reads recorded when a passing trigger test made the change and asserted the audit row. It reads partly recorded when the audit table has no actor column, the row is not asserted, or a condition decides whether it is written. Other cells read cancelled, not tested, not recorded or audit trigger off. The row also lists what the audit table has no column for, such as actor, timestamp, operation or old and new values. When pgaudit is installed, the report reads its settings and adds what it logs per role. |
| 6. Trigger side effects | Triggers that change the ePHI rows themselves. Rewriting a column, deleting or updating other rows, writing into another schema, cancelling or rejecting the change, or failing open. |
| 7. Integrity | 164.312(c)(1). For each audit table and client role, whether it can forge, alter, erase or truncate audit rows, or add a trigger that does. Verdicts are tamper-proof, forgeable, editable, misattributable, unresolved, unreachable or unknown. |
| 8. HIPAA Security Rule | One row per safeguard the database layer evidences, 164.312(a)(1), (b), (c)(1), (d) and 164.308(a)(8), with links to the chapters that hold the evidence. Safeguards outside the database layer are not listed. |
| 9. Evidence stamp | Database, schema, server version, the database-clock timestamp, the connection role, tool version and licence. The SHA-256 of the report, of every source file it read and of every emitted test suite. An edited copy no longer matches. |
An audit table is one that a trigger writes a record of each change into. The tool finds these itself. A table a trigger writes into counts as an audit table when its name contains audit, log, history, journal, trail, change or archive, or ends in event, version or revision. It also counts when it has two or more audit-style columns, such as changed_by, changed_at, operation, old_data, new_data, actor or record_id.
If your audit table has a different name and columns, say public.activity, the trigger's write is treated as an ordinary write. The changes it records do not count as recorded under Audit controls. List the missed tables in the config file.
[audit]
tables = ["public.activity", "billing.ledger"]
not, and [audit.map] states which audit table a table must log toTG_HIPAA_AUDIT_TABLES=public.activity,billing.ledger still works and only addsWhich tables and columns hold PHI is decided automatically. Where the tool gets it wrong, correct it in the config file under [phi].
Supabase keeps a row in storage.objects for every stored file, such as photos, PDFs and scans. The RLS policies on that table decide who can open, upload, change or delete each file. A public bucket lets anyone with a file's link download it, and its policies are not checked for downloads. Add --storage to check them. --supabase turns it on by itself.
unitautogen hipaa --schema public --storage
unitautogen seccover --schema public --storage
storage.objects and storage.buckets, and the public-bucket check. Nothing else, since those functions and tables are Supabase's own.<out>/security/storage/rls_report.html. With hipaa, <out>/hipaa/storage/hipaa_report.html covers access control only.UPDATE storage.buckets SET public = false WHERE id = '<bucket>';, and for fix now also narrowing the SELECT policy on storage.objects so only a file's owner can list it.--storage needs seccover or hipaa. A database without the storage tables is skipped with a message.For what the report proves and what stays with you, see the HIPAA evidence page.