Trusted vs Untrusted Extensions
Extensions marked trusted can be installed by non-superusers with the right grants. Untrusted extensions run C code with superuser privileges during install and demand security review before any CREATE EXTENSION in production.
Recipe
-- Check trust flag (PostgreSQL 13+)
SELECT name,
default_version,
CASE WHEN trusted THEN 'trusted' ELSE 'untrusted' END AS trust_level
FROM pg_available_extensions
WHERE name IN ('vector', 'postgis', 'plpython3u', 'pgcrypto')
ORDER BY name;
-- Grant install to a role (trusted extensions only)
GRANT CREATE ON DATABASE appdb TO deploy_role;
GRANT USAGE ON SCHEMA extensions TO deploy_role;
-- On PostgreSQL 15+: also grant on the extension object
GRANT vector TO deploy_role; -- extension name as objectWhen to reach for this: Onboarding a new extension, auditing who can install what, or hardening a multi-tenant managed cluster.
Working Example
-- Security review checklist query pack
-- 1) All installed extensions
SELECT e.extname,
e.extversion,
a.trusted,
pg_get_userbyid(e.extowner) AS owner,
n.nspname AS schema
FROM pg_extension e
JOIN pg_available_extensions a ON a.name = e.extname
JOIN pg_namespace n ON n.oid = e.extnamespace
ORDER BY e.extname;
-- 2) Roles that can create extensions (superuser or createrole)
SELECT rolname,
rolsuper,
rolcreaterole,
rolcreatedb
FROM pg_roles
WHERE rolsuper OR rolcreatedb
ORDER BY rolname;
-- 3) Untrusted extension install attempt (should fail for app role)
SET ROLE app_role;
CREATE EXTENSION plpython3u; -- expect ERROR: permission denied
RESET ROLE;What this demonstrates:
trustedcolumn inpg_available_extensionsdrives install policy- Inventory joins owner and schema for audit
- Untrusted extensions stay superuser-only in hardened environments
- Role testing validates least privilege before go-live
Deep Dive
Trusted Extensions
Trusted extensions are reviewed by the PostgreSQL community (or vendor) as safe for non-superuser install. They still add capabilities (new types, functions) but are constrained compared to arbitrary C modules.
Common trusted extensions on PostgreSQL 18:
| Extension | Risk surface | Typical approval |
|---|---|---|
vector | New type + indexes | Standard for RAG apps |
pgcrypto | Crypto functions | Approved with schema isolation |
uuid-ossp | UUID generators | Legacy; prefer gen_random_uuid() |
citext | Case-insensitive text | Low risk |
Untrusted Extensions
Untrusted extensions can expose filesystem, network, or arbitrary code execution paths. Treat each as a supply-chain decision.
| Extension | Why untrusted | Review focus |
|---|---|---|
plpython3u | Unsandboxed Python | Code injection, pip packages |
plperlu | Unrestricted Perl | Shell access |
file_fdw | Server-side file read | Path traversal, secrets on disk |
dblink | Remote SQL execution | Credential exposure, lateral movement |
Hardening Patterns
-- Revoke public CREATE on database
REVOKE CREATE ON DATABASE appdb FROM PUBLIC;
-- Allowlist: only specific extensions get GRANT
-- (run as superuser after security review)
GRANT postgis TO dba_deploy_role;
GRANT vector TO dba_deploy_role;
-- Do NOT grant plpython3uOn managed cloud Postgres, the provider's allowlist is the first gate. Your ADR is the second.
Gotchas
- Assuming cloud = safe - Managed hosts offer
plpython3uon some tiers. Fix: Disable via parameter group or never grant install rights. - Trusted flag changes between versions - An extension may become trusted in a newer PG release. Fix: Re-read
pg_available_extensionsafter major upgrades. - Extension in public schema - Even trusted extensions widen the attack surface if functions are callable by
PUBLIC. Fix:REVOKE ALL ON FUNCTION ... FROM PUBLIC;and grant to app roles explicitly. - SUPERUSER deploy pipelines - CI uses superuser creds "just for extensions." Fix: Dedicated
deploy_rolewith minimal grants; superuser only for break-glass. - Shadow IT installs - Developer runs
CREATE EXTENSIONon a staging clone that mirrors prod privileges. Fix: Periodicpg_extensionaudit job with alerting. - CASCADE dependencies - Untrusted extension pulled in as dependency. Fix: Review
pg_dependchain before approving parent extension.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Application code replaces extension | Untrusted PL/language risk too high | You need index-native performance in SQL |
| Separate schema + restricted EXECUTE | Extension approved but functions must be gated | Every function needs custom wrappers |
| No extensions policy | Maximum lockdown compliance | You cannot ship pgvector or PostGIS features |
| Containerized ETL outside Postgres | file_fdw or Python in DB tempting | Strong transactional requirements with data |
FAQs
Who decides trusted vs untrusted?
Extension authors set the flag in the control file; PostgreSQL core reviewers validate for bundled extensions. Third-party extensions may be untrusted by default.Can I mark my extension trusted?
Only if it meets community criteria and ships with PostgreSQL or passes review. Internal extensions stay untrusted.Does trusted mean no vulnerabilities?
No. It means install privilege can be delegated. You still patch versions and monitor CVEs.What about PostGIS?
Trust level varies by packaging. Treat geospatial as production-critical and pin versions regardless of trust flag.How do I detect new extensions?
Comparepg_extension snapshots daily or use event triggers on ddl_command_end for CREATE EXTENSION.Are extensions in replication slots risky?
Logical replication can replay DDL includingCREATE EXTENSION. Restrict DDL on publisher roles.pg_tle (Trusted Language Extensions)?
AWS-led mechanism for controlled in-database extensions. Separate from coretrusted flag; know your platform.Should app users see extension schemas?
GrantUSAGE only on needed functions. Hide admin schemas from search_path.What logs to enable?
log_statement = 'ddl' or pgaudit for extension DDL in regulated environments.Dev vs prod policy?
Same allowlist. Dev-only extensions drift into prod backups and migration scripts.Related
- Extensions Basics - install and versioning
- Extension Allowlist Policy - governance ADR template
- Security Basics - threat model
- Roles Basics - least privilege patterns
- Extensions Best Practices - operational checklist
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, and Patroni 3.x.