mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
fix(security): schema-lint hardening migration (search_path + view security_invoker)
Migration v120 brings existing brains to the same posture as fresh installs: - ALTER VIEW page_links SET (security_invoker = on) on Postgres so the view honors the caller's RLS instead of the owner's (the view-through-RLS bypass). - ALTER FUNCTION ... SET search_path on the gbrain-owned trigger/event functions (both engines, IF EXISTS so engine-only functions are skipped; body untouched, so the load-bearing auto_enable_rls event trigger is unchanged). Closes #171. - Broaden the BYPASSRLS preflight in the historical RLS migration gates to honor superuser and inherited-role BYPASSRLS, so a superuser-connected fresh install no longer aborts (#1385). Fresh-install function definitions in schema.sql / pglite-schema.ts are born-correct (regenerated schema-embedded.ts). scripts/check-search-path.sh is a new CI guard (wired into verify) that fails if a trigger function in the schema base files is added without SET search_path. Postgres-only assertions live in the bootstrap E2E; the PGLite path is covered by test/migration-v120.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
07dd05b31c
commit
b19eb35548
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# CI guard (#1647 / #171): every trigger function in the canonical schema base
|
||||
# files MUST pin `SET search_path`. Without it, an unqualified reference inside
|
||||
# the function body resolves through the caller's search_path, so a same-named
|
||||
# object in a user-controlled schema could shadow it. Migration v120 ALTERs
|
||||
# existing brains; this guard keeps fresh-install function definitions correct
|
||||
# so a NEW trigger function can't reintroduce the gap. Mirrors the
|
||||
# check-jsonb-pattern.sh guard philosophy (a written rule caused the disease;
|
||||
# a guard cures it).
|
||||
#
|
||||
# Scope: schema base files only (src/schema.sql, src/core/pglite-schema.ts).
|
||||
# Historical migration bodies in migrate.ts are append-only and not rescanned;
|
||||
# the runtime doctor probe (pg_proc.proconfig) covers the live post-migration
|
||||
# state on real brains.
|
||||
#
|
||||
# Usage: scripts/check-search-path.sh
|
||||
# Exit: 0 when all trigger functions pin search_path, 1 otherwise.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
FILES="src/schema.sql src/core/pglite-schema.ts src/core/schema-embedded.ts"
|
||||
|
||||
# A hardened header reads `... RETURNS trigger SET search_path = ... AS $tag$`.
|
||||
# An UNHARDENED one reads `... RETURNS trigger AS $tag$` — match that form and
|
||||
# (belt-and-suspenders) drop any line that already mentions search_path.
|
||||
BAD="$(grep -nEi 'CREATE OR REPLACE FUNCTION [a-z_]+\(\) RETURNS trigger AS ' $FILES 2>/dev/null | grep -vi 'search_path' || true)"
|
||||
|
||||
if [ -n "$BAD" ]; then
|
||||
echo "ERROR: trigger function(s) missing SET search_path in schema base files:"
|
||||
echo "$BAD"
|
||||
echo
|
||||
echo "Add 'SET search_path = pg_catalog, public' to the function header, e.g.:"
|
||||
echo " CREATE OR REPLACE FUNCTION foo() RETURNS trigger SET search_path = pg_catalog, public AS \$\$"
|
||||
echo "See #1647 / #171."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK: all trigger functions in schema base files pin search_path"
|
||||
@@ -38,6 +38,7 @@ CHECKS=(
|
||||
"check:proposal-pii"
|
||||
"check:test-names"
|
||||
"check:jsonb"
|
||||
"check:search-path"
|
||||
"check:source-id-projection"
|
||||
"check:source-config-leak"
|
||||
"check:progress"
|
||||
|
||||
Reference in New Issue
Block a user