Turn the chapter into an evidence-driven hardening baseline covering network exposure, HBA, SCRAM/TLS, public/search_path safety, extension and SECURITY DEFINER governance, logging, secret handling, OS isolation, and supported-minor patch discipline.

Hardening Network Access, Extensions, SECURITY DEFINER, Search Path, Logging, and Patching

Turn the chapter into an evidence-driven hardening baseline covering network exposure, HBA, SCRAM/TLS, public/search_path safety, extension and SECURITY DEFINER governance, logging, secret handling, OS isolation, and supported-minor patch discipline.

Intermediate → Advanced180–240 minutesPostgreSQL security engineeringCurrent patched PostgreSQL 18.x; verify current minor at lab timeCore PostgreSQL; OpenSSL used only for the free local TLS labServiceHub disposable objects: app.ch20_* and ch20_* rolesAdmin/superuser required for HBA/TLS/server-role exercisesLocal/free tooling; no paid identity provider requiredLast reviewed: August 2026

Learning outcomes

Security controls fail when they are treated as an installation checklist instead of a continuously verified system. This lesson converts Chapters 18–20 into a hardening baseline: minimize network reachability, require strong authentication and verified transport, eliminate ambient object-creation/search-path hazards, govern server-side code/extensions, capture useful security logs without leaking secrets, and stay on supported patched releases.

01

Produce an evidence-driven hardening snapshot from PostgreSQL settings/catalogs rather than a generic checklist.

02

Reduce public-schema/search_path and SECURITY DEFINER attack surface.

03

Review extension/server-side-code privilege boundaries and reject unnecessary native/untrusted code.

04

Choose connection/security logging that supports incident response without indiscriminate secret leakage.

05

Build patch, restore, secret-rotation, and OS-account responsibilities into the production security runbook.

1. Start with exposure: listen_addresses + firewall + HBA

listen_addresses controls which local interfaces PostgreSQL binds. It is not a firewall and does not authenticate clients. Host firewalls/security groups decide which remote packets reach the port; HBA decides how matching PostgreSQL connection attempts authenticate. All three layers should agree.

sql · network/authentication configuration snapshot
SELECT name, setting, context, source, pending_restartFROM pg_settingsWHERE name IN (  'listen_addresses',  'port',  'ssl',  'password_encryption',  'hba_file')ORDER BY name;SELECT rule_number, type, database, user_name,       address, auth_method, options, errorFROM pg_hba_file_rulesORDER BY rule_number;

A secure baseline is not “set listen_addresses='*' and rely on HBA.” Bind only required interfaces when practical, filter source networks outside PostgreSQL, then use specific HBA records with strong authentication.

2. Validate password/TLS policy—not only server capability

sql · authentication/TLS readiness
SHOW password_encryption;SHOW ssl;SELECT rolname,       CASE         WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM'         WHEN rolpassword LIKE 'md5%' THEN 'MD5-legacy'         WHEN rolpassword IS NULL THEN 'no-password'         ELSE 'other'       END AS verifier_kindFROM pg_authidWHERE rolcanloginORDER BY rolname;

This admin-only verifier classification identifies legacy password storage without printing verifiers. Combine it with HBA review and real client tests using TLS verify-full. A server with ssl=on can still accept unencrypted host connections unless HBA/network policy prevents them.

3. Remove writable schemas from untrusted search paths

Function/operator/type resolution follows search_path. If an attacker can create an object in a schema searched before a trusted object, another role can accidentally invoke the attacker's object. PostgreSQL's security guidance recommends removing untrusted-writable schemas from sensitive paths.

sql · public schema and path audit
SELECT n.nspname,       EXISTS (         SELECT 1         FROM aclexplode(           COALESCE(n.nspacl, acldefault('n', n.nspowner))         ) AS x         WHERE x.grantee = 0           AND x.privilege_type = 'CREATE'       ) AS public_has_create,       EXISTS (         SELECT 1         FROM aclexplode(           COALESCE(n.nspacl, acldefault('n', n.nspowner))         ) AS x         WHERE x.grantee = 0           AND x.privilege_type = 'USAGE'       ) AS public_has_usageFROM pg_namespace AS nWHERE n.nspname = 'public';SHOW search_path;SELECT rolname, rolconfigFROM pg_rolesWHERE rolconfig IS NOT NULLORDER BY rolname;
sql · harden public CREATE when application compatibility allows
BEGIN;REVOKE CREATE ON SCHEMA public FROM PUBLIC;SELECT n.nspname,       EXISTS (         SELECT 1         FROM aclexplode(           COALESCE(n.nspacl, acldefault('n', n.nspowner))         ) AS x         WHERE x.grantee = 0           AND x.privilege_type = 'CREATE'       ) AS public_has_create_afterFROM pg_namespace AS nWHERE n.nspname = 'public';ROLLBACK;

Do not run the revoke blindly on a legacy application that intentionally creates shared objects there. Inventory dependencies first. The security goal is that untrusted roles cannot create objects in schemas implicitly searched by privileged code.

4. Harden SECURITY DEFINER like privileged application code

A SECURITY DEFINER function runs with its owner privileges. It must use trusted, schema-qualified objects; constrain search_path; avoid dynamic-SQL injection; and revoke PostgreSQL's default PUBLIC EXECUTE before exposing it to the intended callers.

sql · secure definer routine created in one transaction
BEGIN;SET ROLE servicehub_owner;CREATE OR REPLACE FUNCTION app.ch20_my_open_ticket_count()RETURNS bigintLANGUAGE sqlSTABLESECURITY DEFINERSET search_path = pg_catalog, app, pg_tempAS $$  SELECT count(*)  FROM app.ch20_ticket  WHERE tenant_role = session_user    AND status = 'open'$$;REVOKE ALLON FUNCTION app.ch20_my_open_ticket_count()FROM PUBLIC;GRANT EXECUTEON FUNCTION app.ch20_my_open_ticket_count()TO ch20_tenant_a, ch20_tenant_b;RESET ROLE;COMMIT;

This example intentionally uses session_user: the authenticated connection identity is not replaced by the function owner's current_user. Its tenant check is therefore valid for direct tenant-login sessions. The chapter's SET ROLE simulations keep the administrator as session_user, so do not use those simulations to test this function's tenant result. A shared-login pooler or impersonation design must use a different non-forgeable identity contract and test it explicitly.

sql · privilege and configuration evidence
SELECT p.oid::regprocedure AS routine,       pg_get_userbyid(p.proowner) AS owner,       p.prosecdef,       p.proconfig,       has_function_privilege(         'ch20_tenant_a',         p.oid,         'EXECUTE'       ) AS tenant_a_executeFROM pg_proc AS pWHERE p.oid = 'app.ch20_my_open_ticket_count()'::regprocedure;

5. Extension and language inventory belongs in security review

sql · installed extension and procedural-language inventory
SELECT e.extname, e.extversion,       n.nspname AS schema_name,       pg_get_userbyid(e.extowner) AS ownerFROM pg_extension AS eJOIN pg_namespace AS n ON n.oid = e.extnamespaceORDER BY e.extname;SELECT lanname, lanpltrusted,       pg_get_userbyid(lanowner) AS ownerFROM pg_languageORDER BY lanname;

Chapter 19's rule carries forward: an extension or untrusted language is server-code/package/runtime surface. Remove unused components, review provenance/version compatibility, and install the same dependencies on failover/restore targets. Do not grant superuser simply to satisfy an extension setup wizard.

6. Logging: collect security evidence without logging every secret

PostgreSQL 18 makes connection logging more granular. Failed authentication is logged regardless; log_connections can log receipt, authentication identity, authorization, and setup durations. Statement logging is more sensitive because SQL text can contain credentials, tokens, personal data, or application secrets.

sql · logging configuration snapshot
SELECT name, setting, context, sourceFROM pg_settingsWHERE name IN (  'log_destination',  'logging_collector',  'log_connections',  'log_disconnections',  'log_line_prefix',  'log_error_verbosity',  'log_statement',  'log_min_duration_statement',  'log_min_duration_sample')ORDER BY name;
conf · example connection-focused baseline to evaluate
log_connections = 'authentication,authorization'log_disconnections = onlog_line_prefix = '%m [%p] %q%u@%d/%a '# Do not blindly set log_statement = 'all' in a secret-rich workload.

Choose log retention/access controls as carefully as database permissions. Logs are operational data that can themselves become sensitive.

7. Secrets belong outside SQL and source control

Use interactive password changes, secret managers, OS-protected service files, or equivalent deployment mechanisms. For libpq password files on Unix, permissions must prevent group/world access. Rotate credentials after exposure and make revocation part of incident response.

shell · do not embed production passwords in commands/scripts
# Avoid:# psql "postgresql://user:RealSecret@example/db"# Prefer a protected service/password mechanism or secret injection:psql "service=servicehub-prod application_name=servicehub-api" 

A shell process list, CI log, crash report, SQL history, or Git diff is not an acceptable password vault.

8. OS account isolation and file permissions are database security

PostgreSQL server files are protected by the operating-system account running the server. A user that can replace database binaries, shared libraries, configuration, TLS private keys, or read unrestricted data files can bypass many SQL controls. Limit login/shell access to the PostgreSQL service account and secure backups, WAL archives, certificate keys, and extension libraries.

sql · server paths and preload surface
SHOW data_directory;SHOW config_file;SHOW hba_file;SHOW ident_file;SHOW shared_preload_libraries;SHOW local_preload_libraries;SHOW session_preload_libraries;

9. Patch discipline: minor updates are part of the security boundary

PostgreSQL supports each major for a limited lifecycle and recommends running the current minor release for that major. Minor releases contain bug and security fixes and normally do not require dump/reload, though their release notes can contain required follow-up actions. Before a production patch, verify the current official versioning/security pages, test the package on staging/replicas, read release notes, and preserve rollback/recovery capability.

sql · record the actual server/client baseline in incident evidence
SELECT version();SELECT current_setting('server_version') AS server_version,       current_setting('server_version_num') AS server_version_num;
shell · record client version independently
psql --versionpg_config --version

Server, psql/libpq, extensions, OS packages, and drivers can have different versions. “We run PostgreSQL 18” is not enough for a vulnerability or compatibility assessment.

10. One evidence-driven hardening query

sql · security review snapshot
SELECT now() AS observed_at,       current_user,       current_setting('server_version') AS server_version,       current_setting('listen_addresses') AS listen_addresses,       current_setting('ssl') AS ssl_enabled,       current_setting('password_encryption') AS password_encryption,       current_setting('log_connections') AS log_connections,       current_setting('log_disconnections') AS log_disconnections;SELECT count(*) FILTER (WHERE error IS NOT NULL) AS hba_parse_errorsFROM pg_hba_file_rules;SELECT count(*) AS superuser_login_rolesFROM pg_rolesWHERE rolsuper AND rolcanlogin;SELECT count(*) AS bypassrls_login_rolesFROM pg_rolesWHERE rolbypassrls AND rolcanlogin;

This is not a compliance score. It is a timestamped starting snapshot for human review. Firewall rules, certificates, secrets, package CVEs, file permissions, backups, and application authorization live outside these catalog rows and must be reviewed separately.

11. Cleanup the disposable Chapter 20 database objects

sql · database-object cleanup
DROP VIEW IF EXISTS app.ch20_open_ticket;DROP FUNCTION IF EXISTS app.ch20_my_open_ticket_count();DROP TABLE IF EXISTS app.ch20_ticket CASCADE;DROP SCHEMA IF EXISTS ch20_app CASCADE;DROP ROLE IF EXISTS ch20_tenant_a;DROP ROLE IF EXISTS ch20_tenant_b;DROP ROLE IF EXISTS ch20_api_login;DROP ROLE IF EXISTS ch20_report_login;DROP ROLE IF EXISTS ch20_deploy_login;DROP ROLE IF EXISTS ch20_reader;DROP ROLE IF EXISTS ch20_writer;DROP ROLE IF EXISTS ch20_schema_owner;

HBA/TLS files and any passwords created in Lessons 1–2 must be removed or restored separately according to the lab topology. Do not delete production authentication records as part of a generic SQL cleanup script.

Production judgment

Security is layered and continuously verified: network reachability → HBA/authentication → TLS server identity → role/object privileges → RLS → server-side code/extensions → logs/secrets/OS → current supported patches and tested recovery. A strong control at one layer does not compensate for an intentionally open adjacent layer.

12. Checkpoint

Check your understanding

  1. Why is listen_addresses not a firewall?
  2. Why is a writable schema in a privileged search_path dangerous?
  3. What three controls are essential around a SECURITY DEFINER function?
  4. Why can log_statement='all' be a security risk?
  5. Why must the current PostgreSQL minor release be re-verified during every security review?
Review the answers

listen_addresses only controls local bind interfaces. Writable searched schemas enable object-shadowing/Trojan-horse attacks. SECURITY DEFINER needs trusted qualified objects/search_path, tight ownership, and PUBLIC EXECUTE revocation/explicit grants. Full statement logs can capture secrets or sensitive data. Minor/security releases and supported-version status change over time, so stale hardening documentation can itself become a vulnerability.

Authoritative references

Authentication, TLS, authorization, and policy behavior is security- and version-sensitive. The lesson uses these PostgreSQL 18 primary sources.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.