Files
supabase/apps/docs/content/guides/database/extensions/pgaudit.mdx
T
Miranda Limonczenko abbf667084 fix(docs) Resolve local link paths caused that have redirects (#48453)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## Problem

The Docs E2E link checker found broken links throughout docs, starting
with `phone-login.mdx` pointing to `/docs/guides/cli/config` (404).

Old links like `/docs/guides/cli/config` still work on the live site
because `supabase.com` has redirects set up for them, but these links
break on the docs preview site, which is what the E2E check tests
against. These issues look clean on the live site, and I didn't catch
them in my first pass because I was testing production instead of the
preview.

The E2E check only tests the ~20 pages a given PR happens to touch, so
fixing the pages it flagged kept exposing more of the same problem one
page at a time as each fix pulled in a new file. To stop chasing this
incrementally, I cross-referenced every `/docs/guides/*` and
`/docs/reference/*` redirect source in `apps/www/lib/redirects.js`
against actual usage across all of `apps/docs`, and verified each
candidate against the live preview.

## Solution

Rather than updating the Docs E2E link checker, this PR resolves the
links. **Why:** we own these docs, so keeping the links clean without
redirects is keeping the house maintained. See [Broken Window
Theory](https://blog.codinghorror.com/the-broken-window-theory/).

Updated every link still using an old path to point straight at the
current page instead of relying on a redirect. This covers old links
like:

- `/docs/guides/cli/config` →
`/docs/guides/local-development/cli/config`
- `/docs/guides/cli/getting-started` →
`/docs/guides/local-development/cli/getting-started`
- `/docs/guides/cli/local-development` →
`/docs/guides/local-development/database-migrations`
- `/docs/guides/cli/managing-environments` →
`/docs/guides/deployment/managing-environments`
- `/docs/guides/cli/seeding-your-database` →
`/docs/guides/local-development/seeding-your-database`
- bare `/docs/guides/cli` → `/docs/guides/local-development`
- `/docs/guides/platform/compute-add-ons` →
`/docs/guides/platform/compute-and-disk`
- `/docs/guides/platform/shared-responsibility-model` →
`/docs/guides/deployment/shared-responsibility-model`
- `/docs/guides/database` → `/docs/guides/database/overview`
- `/docs/reference/javascript`, `/docs/reference/dart`,
`/docs/reference/kotlin`, `/docs/reference/python`,
`/docs/reference/csharp` → their `/introduction` pages (the redirect's
own destination, `/start`, turned out to be dead even on production — a
separate bug in `redirects.js` I didn't touch here)
- and about 35 more of the same pattern, listed in the commit messages

Also fixed a handful of dead heading anchors found along the way (links
that resolve to the right page but point at a `#section` that got
renamed or moved), including the original `#bigquery` anchor and a few
in `connecting-to-postgres.mdx` where content moved to its own dedicated
page.

Left alone on purpose:
- `content/guides/cli.mdx` — this page has no route in the docs app at
all (no `app/guides/cli/` directory), so it 404s even in production
before the `www` redirect ever fires. Fixing its internal link wouldn't
change that; it needs an actual routing/content decision, not a link
fix.
- A few candidates that already resolve fine as-is (`pg_partman`, bare
`/docs/reference/api`, bare `/docs/reference/cli`) — confirmed via curl,
left untouched.

## Manual testing

1. Confirmed every new link target actually exists by checking the
destination file/page and matching heading anchors.
2. Cross-referenced every `/docs/guides/*` and `/docs/reference/*`
redirect source in `apps/www/lib/redirects.js` against real usage in
`apps/docs`, and curl-verified each old path (404) and new path (200)
against the live PR preview before fixing it.
3. Ran the Docs E2E link checker locally against changed pages.
4. Spot-checked the original broken link from CI
(`/docs/guides/cli/config`) to confirm it now points to a working page.
2026-07-29 14:47:17 -07:00

396 lines
15 KiB
Plaintext

---
id: 'pgaudit'
title: 'PGAudit: Postgres Auditing'
description: 'Session and object auditing via Postgres standard logging'
---
[PGAudit](https://www.pgaudit.org) extends Postgres's built-in logging abilities. It can be used to selectively track activities within your database.
This helps you with:
- **Compliance**: Meeting audit requirements for regulations
- **Security**: Detecting suspicious database activity
- **Troubleshooting**: Identifying and fixing database issues
## Enable the extension
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
queryGroup="database-method"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Extensions** in the sidebar.
3. Search for `pgaudit` and enable the extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Enable the "pgaudit" extension
create extension pgaudit;
-- Disable the "pgaudit" extension
drop extension if exists pgaudit;
```
</TabPanel>
</Tabs>
## Configure the extension
PGAudit can be configured with different levels of precision.
**PGAudit logging precision:**
- **[Session](#session-logging):** Logs activity within a connection, such as a [psql](/docs/guides/database/psql) connection.
- **[User](#user-logging):** Logs activity by a particular database user (for example, `anon` or `postgres`).
- **[Global](#global-logging):** Logs activity across the entire database.
- **[Object](#object-logging):** Logs events related to specific database objects (for example, the auth.users table).
Although Session, User, and Global modes differ in their precision, they're all considered variants of **Session Mode** and are configured with the same input categories.
### Session mode categories
These modes can monitor predefined categories of database operations:
| Category | What it Logs | Description |
| ---------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `read` | Data retrieval (SELECT, COPY) | Tracks what data is being accessed. |
| `write` | Data modification (INSERT, DELETE, UPDATE, TRUNCATE, COPY) | Tracks changes made to your database. |
| `function` | FUNCTION, PROCEDURE, and DO/END block executions | Tracks routine/function executions |
| `role` | User management actions (CREATE, DROP, ALTER on users and privileges) | Tracks changes to user permissions and access. |
| `ddl` | Schema changes (CREATE, DROP, ALTER statements) | Monitors modifications to your database structure (tables, indexes, etc.). |
| `misc` | Less common commands (FETCH, CHECKPOINT) | Captures obscure actions for deeper analysis if needed. |
| `all` | Everything above | Comprehensive logging for complete audit trails. |
Below is a limited example of how to assign PGAudit to monitor specific categories.
```sql
-- log all CREATE, ALTER, and DROP events
... pgaudit.log = 'ddl';
-- log all CREATE, ALTER, DROP, and SELECT events
... pgaudit.log = 'read, ddl';
-- log nothing
... pgaudit.log = 'none';
```
### Session logging
When you are connecting in a session environment, such as a [psql](/docs/guides/database/psql) connection, you can configure PGAudit to record events initiated within the session.
<Admonition type="note">
The [Dashboard](/dashboard/project/_) is a transactional environment and won't sustain a session.
</Admonition>
Inside a session, by default, PGAudit will log nothing:
```sql
-- returns 'none'
show pgaudit.log;
```
In the session, you can `set` the `pgaudit.log` variable to record events:
```sql
-- log CREATE, ALTER, and DROP events
set pgaudit.log = 'ddl';
-- log all CREATE, ALTER, DROP, and SELECT events
set pgaudit.log = 'read, ddl';
-- log nothing
set pgaudit.log = 'none';
```
### User logging
There are some cases where you may want to monitor a database user's actions. For instance, say you connected your database to [Zapier](/partners/integrations/zapier) and created a custom role for it to use:
```sql
create user "zapier" with password '<new password>';
```
You may want to log all actions initiated by `zapier`, which can be done with the following command:
```sql
alter role "zapier" set pgaudit.log to 'all';
```
To remove the settings, execute the following code:
```sql
-- disables role's log
alter role "zapier" set pgaudit.log to 'none';
-- check to make sure the changes are finalized:
select
rolname,
rolconfig
from pg_roles
where rolname = 'zapier';
-- should return a rolconfig path with "pgaudit.log=none" present
```
### Global logging
<Admonition type="caution">
Use global logging cautiously. It can generate many logs and make it difficult to find important events. Consider limiting the scope of what is logged by using session, user, or object logging where possible.
</Admonition>
The below SQL configures PGAudit to record all events associated with the `postgres` role. Since it has extensive privileges, this effectively monitors all database activity.
```sql
alter role "postgres" set pgaudit.log to 'all';
```
To check if the `postgres` role is auditing, execute the following command:
```sql
select
rolname,
rolconfig
from pg_roles
where rolname = 'postgres';
-- should return a rolconfig path with "pgaudit.log=all" present
```
To remove the settings, execute the following code:
```sql
alter role "postgres" set pgaudit.log to 'none';
```
### Object logging
To fine-tune what object events PGAudit will record, you must create a custom database role with limited permissions:
```sql
create role "some_audit_role" noinherit;
```
No other Postgres user can assume or login via this role. It solely exists to securely define what PGAudit will record.
Once the role is created, you can direct PGAudit to log by assigning it to the `pgaudit.role` variable:
```sql
alter role "postgres" set pgaudit.role to 'some_audit_role';
```
You can then assign the role to monitor only approved object events, such as `select` statements that include a specific table:
```sql
grant select on random_table to "some_audit_role";
```
With this privilege granted, PGAudit will record all select statements that reference the `random_table`, regardless of _who_ or _what_ initiated the event. All assignable privileges can be viewed in the [Postgres documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
If you would no longer like to use object logging, you will need to unassign the `pgaudit.role` variable:
```sql
-- change pgaudit.role to no longer reference some_audit_role
alter role "postgres" set pgaudit.role to '';
-- view if pgaudit.role changed with the following command:
select
rolname,
rolconfig
from pg_roles
where rolname = 'postgres';
-- should return a rolconfig path with "pgaudit.role="
```
## Interpreting Audit Logs
PGAudit was designed for storing logs as CSV files with the following headers:
<Admonition type="note">
Referenced from the [PGAudit official docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#format)
</Admonition>
| header | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| AUDIT_TYPE | SESSION or OBJECT |
| STATEMENT_ID | Unique statement ID for this session. Sequential even if some statements are not logged. |
| SUBSTATEMENT_ID | Sequential ID for each sub-statement within the main statement. Continuous even if some are not logged. |
| CLASS | ..., READ, ROLE (see pgaudit.log). |
| COMMAND | ..., ALTER TABLE, SELECT. |
| OBJECT_TYPE | TABLE, INDEX, VIEW, etc. Available for SELECT, DML, and most DDL statements. |
| OBJECT_NAME | The fully qualified object name (for example, public.account). Available for SELECT, DML, and most DDL. |
| STATEMENT | Statement executed on the backend. |
| PARAMETER | If pgaudit.log_parameter is set, this field contains the statement parameters as quoted CSV, or \<none\>. Otherwise, it's \<not logged\>. |
A log made from the following create statement:
```sql
create table account (
id int primary key,
name text,
description text
);
```
Generates the following log in the [Dashboard's Postgres Logs](/dashboard/project/_/logs/postgres-logs):
```
AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,create table account(
id int,
name text,
description text
); <not logged>
```
## Finding and filtering audit logs
Logs generated by PGAudit can be found in [Postgres Logs](/dashboard/project/_/logs/postgres-logs?s=AUDIT). To find a specific log, you can use the log explorer. Below is a basic example to extract logs referencing `CREATE TABLE` events
```sql
select
cast(t.timestamp as datetime) as timestamp,
event_message
from
postgres_logs as t
cross join unnest(metadata) as m
cross join unnest(m.parsed) as p
where event_message like 'AUDIT%CREATE TABLE%'
order by timestamp desc
limit 100;
```
## Practical examples
### Monitoring API events
<Admonition type="note">
API requests are already recorded in the [API Edge Network](/dashboard/project/_/logs/edge-logs) logs.
</Admonition>
To monitor all writes initiated by the PostgREST API roles:
```sql
alter role "authenticator" set pgaudit.log to 'write';
-- the above is the practical equivalent to:
-- alter role "anon" set pgaudit.log TO 'write';
-- alter role "authenticated" set pgaudit.log TO 'write';
-- alter role "service_role" set pgaudit.log TO 'write';
```
### Monitoring the `auth.users` table
In the worst case scenario, where a privileged roles' password is exposed, you can use PGAudit to monitor if the `auth.users` table was targeted. It should be stated that API requests are already monitored in the [API Edge Network](/dashboard/project/_/logs/edge-logs) and this is more about providing greater clarity about what is happening at the database level.
Logging `auth.user` should be done in Object Mode and requires a custom role:
```sql
-- create logging role
create role "auth_auditor" noinherit;
-- give role permission to observe relevant table events
grant select on auth.users to "auth_auditor";
grant delete on auth.users to "auth_auditor";
-- assign auth_auditor to pgaudit.role
alter role "postgres" set pgaudit.role to 'auth_auditor';
```
With the above code, any query involving reading or deleting from the auth.users table will be logged.
## Best practices
### Disabling excess logging
PGAudit, if not configured mindfully, can log all database events, including background tasks. This can generate an undesirably large amount of logs in a few hours.
The first step to solve this problem is to identify which database users PGAudit is observing:
```sql
-- find all users monitored by pgaudit
select
rolname,
rolconfig
from pg_roles
where
exists (
select
1
from UNNEST(rolconfig) as c
where c like '%pgaudit.role%' or c like '%pgaudit.log%'
);
```
To prevent PGAudit from monitoring the problematic roles, you'll want to change their `pgaudit.log` values to `none` and `pgaudit.role` values to `empty quotes ''`
```sql
-- Use to disable object level logging
alter role "<role name>" set pgaudit.role to '';
-- Use to disable global and user level logging
alter role "<role name>" set pgaudit.log to 'none';
```
## FAQ
#### Using PGAudit to debug database functions
Technically yes, but it is not the best approach. It is better to check out our [function debugging guide](/docs/guides/database/functions#general-logging) instead.
#### Downloading database logs
In the [Logs Dashboard](/dashboard/project/_/logs/postgres-logs) you can download logs as CSVs.
#### Logging observed table rows
By default, PGAudit records queries, but not the returned rows. You can modify this behavior with the `pgaudit.log_rows` variable:
```sql
--enable
alter role "postgres" set pgaudit.log_rows to 'on';
-- disable
alter role "postgres" set pgaudit.log_rows to 'off';
```
You should not do this unless you are _absolutely_ certain it is necessary for your use case. It can expose sensitive values to your logs that ideally should not be preserved. Furthermore, if done in excess, it can noticeably reduce database performance.
#### Logging function parameters
We don't currently support configuring `pgaudit.log_parameter` because it may log secrets in encrypted columns if you are using [pgsodium](/docs/guides/database/extensions/pgsodium) or[Vault](/docs/guides/database/vault).
You can upvote this [feature request](https://github.com/orgs/supabase/discussions/20183) with your use-case if you'd like this restriction lifted.
#### Does PGAudit support system wide configurations?
PGAudit allows settings to be applied to 3 different database scopes:
| Scope | Description | Configuration File/Command |
| -------- | ------------------ | -------------------------- |
| System | Entire server | ALTER SYSTEM commands |
| Database | Specific database | ALTER DATABASE commands |
| Role | Specific user/role | ALTER ROLE commands |
Supabase limits full privileges for file system and database variables, meaning PGAudit modifications can only occur at the role level. Assigning PGAudit to the `postgres` role grants it nearly complete visibility into the database, making role-level adjustments a practical alternative to configuring at the database or system level.
PGAudit's [official documentation](https://www.pgaudit.org) focuses on system and database level configs, but its docs officially supports role level configs, too.
## Resources
- [Official `PGAudit` documentation](https://www.pgaudit.org)
- [Database Function Logging](/docs/guides/database/functions#general-logging)
- [Supabase Logging](/docs/guides/telemetry/logs)
- [Self-Hosting Logs](/docs/reference/self-hosting-analytics/introduction)