Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,10 @@ jobs:
- name: Load cached Supabase Docker images
run: |
if [ -f /tmp/supabase-docker-cache/images.tar ]; then
docker load -i /tmp/supabase-docker-cache/images.tar
if ! docker load -i /tmp/supabase-docker-cache/images.tar; then
echo "::warning::Cached Supabase images exceeded runner disk capacity; discarding the restored tar so supabase start can pull only missing images."
fi
rm -f /tmp/supabase-docker-cache/images.tar
fi

- name: Start Supabase Local Emulator and bootstrap default ACLs
Expand All @@ -348,7 +351,7 @@ jobs:
db_container="$(docker ps --format '{{.Names}}' | grep '^supabase_db_' | head -n 1)"
test -n "${db_container}"
docker exec -i "${db_container}" \
psql -U supabase_admin -d postgres -v ON_ERROR_STOP=1 \
psql -U postgres -d postgres -v ON_ERROR_STOP=1 \
< supabase/roles.sql

- name: Verify Migration Replay
Expand Down
2 changes: 1 addition & 1 deletion docs/process-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ hybrid:10}`, all 10 forced-embedding vector cases passed (`force_embedding_failu
## 2026-07-13 audit remediation batch (branch claude/audit-remediation-2026-07-13)

- **Lexical retrieval rewrite (audit finding 1):** `20260713100000_index_friendly_lexical_retrieval.sql` splits `match_document_chunks_text`'s OR-across-relations candidate search into two GIN-index probes unioned by chunk id (same contract, same scores; the `_v2` wrapper inherits the speedup). Parity + plan + timing harness: `scripts/sql/lexical-rpc-parity-check.sql` (scratch databases only; run it against the drift-manifest container kept with `--keep`). Re-run it whenever either lexical body changes.
- **supabase_admin default privileges (audit finding 7):** `supabase/roles.sql` establishes and verifies the intended defaults before migrations on fresh databases. Supabase CLI reads that file as the unprivileged local `postgres` role, so the migration-replay job starts an empty emulator without migrations, applies the file once as `supabase_admin`, and then applies every migration with `supabase migration up --local` in the same fresh database. This preserves the database-scoped defaults without granting reserved role membership. `20260717161000_assert_supabase_admin_default_privileges.sql` evaluates effective defaults from `pg_default_acl` plus `acldefault`, so a missing catalog row cannot hide PostgreSQL's built-in `PUBLIC EXECUTE` on functions. `20260717173000_reassert_supabase_admin_default_privileges.sql` repeats that fail-closed postcondition after the later performance migrations and must remain the final migration. Existing hosted databases do not receive `roles.sql` through ordinary migration deployment; after an authorized hosted apply, run the read-only provider check with `npm run check:default-acl -- --confirm-provider-read`. This command contacts the live Supabase project and therefore requires explicit provider approval. The future-object probe in `20260713102000_revoke_supabase_admin_default_privileges.sql` remains useful in environments that can assume `supabase_admin`.
- **Hosted migration-role default privileges (audit finding 7):** `supabase/roles.sql` establishes and verifies secure `postgres` defaults before fresh local migration replay. `20260719055541_assert_postgres_default_privileges.sql` evaluates effective defaults from `pg_default_acl` plus `acldefault`, so a missing catalog row cannot hide PostgreSQL's built-in `PUBLIC EXECUTE` on functions. `20260719055555_reassert_postgres_default_privileges.sql` repeats the fail-closed postcondition, and `20260719055609_repair_postgres_default_privileges.sql` is the forward-only hosted repair immediately before `20260719055623_enforce_public_title_word_scope.sql`. These filenames match the hosted migration history exactly. All active revokes and assertions target objects created by `postgres` in `public`; service-role grants remain least-privilege. Older applied migration history is immutable. After an authorized hosted apply, run the read-only provider check with `npm run check:default-acl -- --confirm-provider-read`. This command contacts the live Supabase project and therefore requires explicit provider approval.
- **Legacy rag query text scrub (audit finding 5):** `20260713103000_scrub_legacy_rag_query_text.sql` performs four redaction/deletion operations for pre-HMAC plaintext query text: (1) scrubs `rag_queries.query` rows not matching `redacted-query:%`, replacing them with salted `redacted-query:legacy:` placeholders; (2) scrubs both `rag_query_misses.query` and `rag_query_misses.normalized_query` not matching `redacted-query:%`; (3) scrubs both `rag_retrieval_logs.query` and `rag_retrieval_logs.normalized_query` not matching `redacted-query:%` (nullable); (4) deletes `rag_response_cache` rows where `normalized_query` does not match `redacted-cache:%` (cache entries, not re-keyed). **Operator verification after live apply:** for each affected table/operation, count rows not matching the expected redacted pattern (expect 0 unless `RAG_PERSIST_RAW_QUERY_TEXT` is deliberately enabled): `select count(*) from rag_queries where query not like 'redacted-query:%';` (expect 0), `select count(*) from rag_query_misses where query not like 'redacted-query:%' or normalized_query not like 'redacted-query:%';` (expect 0), `select count(*) from rag_retrieval_logs where query not like 'redacted-query:%' or (normalized_query is not null and normalized_query not like 'redacted-query:%');` (expect 0), `select count(*) from rag_response_cache where normalized_query not like 'redacted-cache:%';` (expect 0). The migration includes a post-apply assertion block that enforces these exact checks and fails the migration if any unscrubbed/undeleted rows remain.
- Remaining operator items from the 2026-07-13 audit (confirmation-required, not automated): apply this batch's migrations live, re-run Supabase advisors, trigger the Eval Canary and require two consecutive green scheduled runs, repair the invalid `storage.idx_objects_bucket_id_name_lower` index via dashboard/support, and dedupe response-cache purge cron jobs if `cron.job` shows duplicates.

Expand Down
2 changes: 1 addition & 1 deletion scripts/check-default-acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function argument(name: string, fallback: string) {
return index >= 0 && process.argv[index + 1] ? process.argv[index + 1]! : fallback;
}

const roleName = argument("--role", "supabase_admin");
const roleName = argument("--role", "postgres");
const schemaName = argument("--schema", "public");
if (!process.argv.includes("--confirm-provider-read")) {
throw new Error("Refusing provider-backed ACL verification without --confirm-provider-read.");
Expand Down
4 changes: 2 additions & 2 deletions scripts/generate-drift-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ async function main() {
sql,
);

console.log("Applying role bootstrap (supabase_admin)…");
psql("supabase_admin", rolesSql);
console.log("Applying role bootstrap (postgres)…");
psql("postgres", rolesSql);
console.log("Applying storage scaffold (supabase_admin)…");
psql("supabase_admin", scaffoldSql);
console.log("Replaying supabase/schema.sql from scratch (postgres)…");
Expand Down
Loading