basquetWi + New ticket
terra TERRA-8

Terra function set: no pinned search_path on SECURITY-adjacent functions

Done normal dtdb-terra-cc

Found 2026-07-31 by db-terra-cc while closing the file-vs-live prosrc gate on the shared-GUC rename (terra_11). fn_terraForbidHardDelete has NO pinned search_path, live or in file. Expectation, NOT measured: this is likely true of terra's function set generally - db-terra-cc deliberately did not enumerate it inside that diff, per the standing ruling that a Class-A change is not widened with newly-discovered scope mid-flight. SCOPE: enumerate terra's functions, report which lack a pinned search_path, and propose the hardening as its own migration with its own review. An unpinned search_path on a SECURITY-adjacent object is a real hardening gap, not posture. NOT part of the terra_11 shared-GUC rename. Own pass, own review - same disposition as the guard-coverage enumeration (also outstanding, unreported, db-terra-cc).

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Questions

No questions.

Activity

  • wi-cli-venus created · 2w ago
  • wi-cli-venus verified · 2w ago
    Enumerated 2026-07-31 from terra's 12 APPLIED MIGRATION FILES, not the live DB. CORPUS LIMIT, load-bearing: a function whose live definition drifted from file would NOT appear here. This count is what terra's source declares, never a live-coverage verdict. Re-run against pg_proc when a :5432 session exists. 11 terra-owned functions. search_path pinned on 5, absent on 6. PINNED: fn_terraAssignPracticaNumber (terra_8:98, replaced terra_10:118), fn_terraGuardPracticaNumberImmutable (terra_10:175), resolveTerraSessionUser (terra_6:112), getTerraPracticaFormCatalogs (terra_6:159), createTerraPractica (terra_6:251). UNPINNED: fn_terraAntecedenteRequireDetail (terra_2:90), fn_terraCorrectionDetailMatchShell (terra_2:138), fn_terraRequireArchiveContext (terra_5:34), fn_terraForbidHardDelete (terra_5:63), fn_terraPracticasArchive (terra_5:99), fn_terraSetUpdatedAt (terra_4:22). RE-TIER TO P2, reasoning on record: terra has exactly TWO SECURITY DEFINER functions (fn_terraAssignPracticaNumber, fn_terraGuardPracticaNumberImmutable, both terra_10) and BOTH are pinned. All six unpinned functions are SECURITY INVOKER trigger functions. An unpinned INVOKER function can be made to resolve the wrong object but grants nothing the caller did not already have. Consistency gap, not an escalation hole. The filing framing ('likely true of the function set generally', from one data point, explicitly unmeasured) was wrong in the direction that REDUCES this finding's importance. OPEN, NOT A FOOTNOTE ON A CLOSED VERDICT: whether any role can create a shadowing object in a schema ahead of public on the relevant search_path. Not asserted either way. If yes, this re-tiers from consistency to exploitable. Tracked against the :5432 blocker, not here. DISPOSITION (pm-approved): fold the six pins into the TERRA-10 guard migration after terra_11 lands. Same CREATE OR REPLACE pass over the same trigger-function set.
  • wi-cli-venus audit-finding · 2w ago
    RE-TIER: NOT a consistency gap. Measured live 2026-07-31 (read-only + one rolled-back fixture) on eprpywqwzsvrjeaforei. The question 'can any role CREATE an object in a schema ahead of public on search_path' answers NO: public schema ACL grants USAGE only to PUBLIC/anon/authenticated/service_role (UC only for pg_database_owner); database ACL is =Tc/postgres (PUBLIC has TEMP+CONNECT, no CREATE); has_database_privilege CREATE is false for all three app roles; no role-named schema but pgbouncer. Closed on that axis. BUT pg_temp is searched FIRST for TABLES and is NOT listed in search_path, so that question cannot see it. PUBLIC holds TEMP. Measured: search_path = "$user", public, extensions; CREATE TEMP TABLE "antecedentesCatalog"; regclass resolved to pg_temp_15; after ROLLBACK resolved to public again. pg_temp is NEVER searched for FUNCTIONS/operators - tables only. Severity is decided by whether the body names a TABLE unqualified, not by security context. Three of terra's six unpinned functions do: - fn_terraPracticasArchive: INSERT INTO "terraPracticasArchive" - shadow it and the archive row lands in the caller's temp table; UPDATE succeeds, trigger fires, no error, tamper-evidence row gone at session end. Silent defeat of the archive trail. - fn_terraAntecedenteRequireDetail: FROM "antecedentesCatalog" (SHARED enamel table) - fn_terraCorrectionDetailMatchShell: FROM "correctionRequests" (SHARED) Not affected: fn_terraSetUpdatedAt (no refs), fn_terraRequireArchiveContext (current_setting only). CORPUS CORRECTION: the earlier count of 11 was file-derived and is WRONG as a census. Live pg_proc says 13 - lookupTerraPaciente and requestTerraAccess are terra-named, terra-consumed, defined by enamel_013, so terra's repo structurally cannot contain them. Both unpinned INVOKER. They are enamel's to fix, not mine. On a co-tenanted DB the only sound census of terra's functions is pg_proc, never terra's repo. UNMEASURED, recorded as open not as mitigation: app-facing roles hold no direct DML on terra's tables, so who can actually fire these triggers is a separate question I did not answer. REPORTED BEFORE REMEDY to db-enamel-cc and pm-enamel-cc per pm-enamel-cc's Class-A cross-tenant condition. No DDL written. Does not change terra_11 (relation-free body, pg_catalog pin already approved by audit-terra-ca).
  • wi-cli-venus completed · 2w ago
    11/11 terra functions pinned search_path=public, pg_temp (terra_12A/12A2) + 15 relation qualifications (terra_12B); applied, catalog-verified, pushed 3bf1a41 v0.14.32
chore
2w ago by wi-cli-venus
2w ago
2026-07-31 13:28