From 5fffad571603f0e0be3f69ba8e6122ac898edea8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 14:14:14 +0900 Subject: [PATCH 1/3] test(job-analysis): prove every child table's append-only guard and tenant RLS Migration 0013 declares an append-only guard and a tenant-scoped RLS policy on all five job-analysis tables, but the PostgreSQL contracts only proved them on the root job_analysis_snapshot table. A dropped guard or policy on job_analysis_task_item, job_analysis_ksao_item, job_analysis_task_ksao_link, or job_analysis_write_command would have passed both CI contracts silently. The unowned schema-hardening contract now proves, per child table: - UPDATE and DELETE are rejected by the append-only guard on a seeded row (the row-count precondition keeps the proof non-vacuous); - pg_class shows ENABLE+FORCE row-level security and exactly one permissive ALL policy whose USING and WITH CHECK both call current_tenant_record_id(); - a probe role under another tenant sees zero rows. The catalog assertions are required because a missing policy also returns zero rows, so the behavioral read alone cannot distinguish "isolated" from "denied". Verified locally on a clean cluster at develop eb9757f8: - parent + hardening contracts PASS (test_job_analysis_snapshot_postgres.sh, then test_job_analysis_snapshot_schema_hardening.sh against one database, as CI runs them inside the same container); - non-vacuity: removing the task_item trigger fails as "job_analysis_task_item UPDATE was not rejected by the append-only guard: UPDATE 1"; removing the write_command RLS policy fails as "job_analysis_write_command must ENABLE and FORCE row-level security: false:false"; - npm run validate EXIT=0 (55 node tests). --- ..._job_analysis_snapshot_schema_hardening.sh | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/test_job_analysis_snapshot_schema_hardening.sh b/tests/test_job_analysis_snapshot_schema_hardening.sh index 88f85288f..11d149963 100644 --- a/tests/test_job_analysis_snapshot_schema_hardening.sh +++ b/tests/test_job_analysis_snapshot_schema_hardening.sh @@ -62,4 +62,85 @@ if [[ "${redundant_unique_count}" != "0" ]]; then exit 1 fi +# Migration 0013 declares an append-only guard and a tenant RLS policy on every +# child table, not only the root snapshot. Prove the four child tables carry the +# same immutable, tenant-isolated guarantee that the root table is checked for. +child_tables=( + job_analysis_task_item + job_analysis_ksao_item + job_analysis_task_ksao_link + job_analysis_write_command +) + +# Catalog proof first: a missing FORCE-RLS policy would also return zero rows to +# another tenant, so the behavioral check below is only meaningful once each +# child table is confirmed to have ENABLE+FORCE RLS and its scoped policy. +for child_table in "${child_tables[@]}"; do + child_rls_state="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT c.relrowsecurity::text || ':' || c.relforcerowsecurity::text +FROM pg_class c +WHERE c.oid = '${child_table}'::regclass; +")" + if [[ "${child_rls_state}" != "true:true" ]]; then + echo "${child_table} must ENABLE and FORCE row-level security: ${child_rls_state}" >&2 + exit 1 + fi + + child_policy_count="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT count(*) +FROM pg_policy +WHERE polrelid = '${child_table}'::regclass + AND polcmd = '*' + AND polpermissive + AND pg_get_expr(polqual, polrelid) LIKE '%current_tenant_record_id()%' + AND pg_get_expr(polwithcheck, polrelid) LIKE '%current_tenant_record_id()%'; +")" + if [[ "${child_policy_count}" != "1" ]]; then + echo "${child_table} must have exactly one scoped ALL policy: ${child_policy_count}" >&2 + exit 1 + fi +done + +for child_table in "${child_tables[@]}"; do + child_row_count="$(tenant_psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc \ + "SELECT count(*) FROM ${child_table};")" + if [[ "${child_row_count}" == "0" ]]; then + echo "${child_table} has no seeded row; the append-only proof would be vacuous" >&2 + exit 1 + fi + + set +e + child_update_output="$({ tenant_psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c \ + "UPDATE ${child_table} SET tenant_record_id = tenant_record_id;" ; } 2>&1)" + child_update_status=$? + set -e + if [[ ${child_update_status} -eq 0 || "${child_update_output}" != *"append-only"* ]]; then + echo "${child_table} UPDATE was not rejected by the append-only guard: ${child_update_output}" >&2 + exit 1 + fi + + set +e + child_delete_output="$({ tenant_psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c \ + "DELETE FROM ${child_table};" ; } 2>&1)" + child_delete_status=$? + set -e + if [[ ${child_delete_status} -eq 0 || "${child_delete_output}" != *"append-only"* ]]; then + echo "${child_table} DELETE was not rejected by the append-only guard: ${child_delete_output}" >&2 + exit 1 + fi +done + +for child_table in "${child_tables[@]}"; do + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c \ + "GRANT SELECT ON ${child_table} TO orgmetra_rls_probe;" >/dev/null + + other_tenant_child_rows="$(PGOPTIONS="-c orgmetra.tenant_record_id=${OTHER_TENANT_ID}" \ + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc \ + "SET ROLE orgmetra_rls_probe; SELECT count(*) FROM ${child_table};")" + if [[ "${other_tenant_child_rows}" != "0" ]]; then + echo "row-level security leaked ${other_tenant_child_rows} ${child_table} row(s) across tenants" >&2 + exit 1 + fi +done + echo "job-analysis PostgreSQL schema hardening contract passed" From 68306194f88b5a6beaf4fa06f8c340135572a180 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 15:11:37 +0900 Subject: [PATCH 2/3] test(job-analysis): prove exact RLS write isolation --- ..._job_analysis_snapshot_schema_hardening.sh | 143 ++++++++++++++++-- 1 file changed, 131 insertions(+), 12 deletions(-) diff --git a/tests/test_job_analysis_snapshot_schema_hardening.sh b/tests/test_job_analysis_snapshot_schema_hardening.sh index 11d149963..933a23ace 100644 --- a/tests/test_job_analysis_snapshot_schema_hardening.sh +++ b/tests/test_job_analysis_snapshot_schema_hardening.sh @@ -6,6 +6,11 @@ set -euo pipefail TENANT_ID="10000000-0000-7000-8000-000000000001" OTHER_TENANT_ID="20000000-0000-7000-8000-000000000002" ANALYSIS_ID="00000000-0000-7000-8000-000000000081" +LINK_PROBE_TASK_ID="00000000-0000-7000-8000-000000000182" +LINK_PROBE_KSAO_ID="00000000-0000-7000-8000-000000000183" +CROSS_TENANT_TASK_ID="00000000-0000-7000-8000-000000000282" +CROSS_TENANT_KSAO_ID="00000000-0000-7000-8000-000000000283" +CROSS_TENANT_COMMAND_ID="00000000-0000-7000-8000-000000000287" tenant_psql() { PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID}" command psql "$@" @@ -73,8 +78,9 @@ child_tables=( ) # Catalog proof first: a missing FORCE-RLS policy would also return zero rows to -# another tenant, so the behavioral check below is only meaningful once each -# child table is confirmed to have ENABLE+FORCE RLS and its scoped policy. +# another tenant. Require one and only one permissive policy, command ALL, and +# exact normalized USING/WITH CHECK expressions; substring matching would let +# an expression such as "... OR true" masquerade as tenant isolation. for child_table in "${child_tables[@]}"; do child_rls_state="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " SELECT c.relrowsecurity::text || ':' || c.relforcerowsecurity::text @@ -86,21 +92,61 @@ WHERE c.oid = '${child_table}'::regclass; exit 1 fi - child_policy_count="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " -SELECT count(*) + child_policy_state="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT + count(*) FILTER (WHERE polpermissive)::text || ':' || + count(*) FILTER ( + WHERE polpermissive + AND polcmd = '*' + AND regexp_replace( + coalesce(pg_get_expr(polqual, polrelid), ''), + '[[:space:]()]', '', 'g' + ) = 'tenant_record_id=current_tenant_record_id' + AND regexp_replace( + coalesce(pg_get_expr(polwithcheck, polrelid), ''), + '[[:space:]()]', '', 'g' + ) = 'tenant_record_id=current_tenant_record_id' + )::text FROM pg_policy -WHERE polrelid = '${child_table}'::regclass - AND polcmd = '*' - AND polpermissive - AND pg_get_expr(polqual, polrelid) LIKE '%current_tenant_record_id()%' - AND pg_get_expr(polwithcheck, polrelid) LIKE '%current_tenant_record_id()%'; +WHERE polrelid = '${child_table}'::regclass; ")" - if [[ "${child_policy_count}" != "1" ]]; then - echo "${child_table} must have exactly one scoped ALL policy: ${child_policy_count}" >&2 + if [[ "${child_policy_state}" != "1:1" ]]; then + echo "${child_table} must have exactly one permissive ALL tenant policy with exact USING/WITH CHECK expressions: ${child_policy_state}" >&2 exit 1 fi done +# Give the link write probe a fresh, constraint-valid task/KSAO pair. These are +# legitimate tenant rows seeded by the authorized tenant; the cross-tenant role +# later attempts only the missing link between them. +tenant_psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 </dev/null + "GRANT SELECT, INSERT ON ${child_table} TO orgmetra_rls_probe;" >/dev/null other_tenant_child_rows="$(PGOPTIONS="-c orgmetra.tenant_record_id=${OTHER_TENANT_ID}" \ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc \ @@ -141,6 +187,79 @@ for child_table in "${child_tables[@]}"; do echo "row-level security leaked ${other_tenant_child_rows} ${child_table} row(s) across tenants" >&2 exit 1 fi + + case "${child_table}" in + job_analysis_task_item) + cross_tenant_insert_sql=" +INSERT INTO job_analysis_task_item ( + tenant_record_id, analysis_record_id, task_record_id, task_statement, + importance_level, difficulty_level, source_uri, source_title, + source_version_code, retrieved_at, content_digest_sha256, origin_code +) VALUES ( + '${TENANT_ID}', '${ANALYSIS_ID}', '${CROSS_TENANT_TASK_ID}', + 'Cross-tenant RLS write probe', 1, 1, 'https://example.invalid/rls-probe', + 'RLS write probe', 'probe:v1', TIMESTAMPTZ '2026-09-11 00:00:00+00', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'contract_probe' +);" + ;; + job_analysis_ksao_item) + cross_tenant_insert_sql=" +INSERT INTO job_analysis_ksao_item ( + tenant_record_id, analysis_record_id, ksao_record_id, category_code, + requirement_statement, importance_level, proficiency_level, source_uri, + source_title, source_version_code, retrieved_at, content_digest_sha256, + origin_code +) VALUES ( + '${TENANT_ID}', '${ANALYSIS_ID}', '${CROSS_TENANT_KSAO_ID}', + 'knowledge_requirement', 'Cross-tenant RLS write probe', 1, 1, + 'https://example.invalid/rls-probe', 'RLS write probe', 'probe:v1', + TIMESTAMPTZ '2026-09-11 00:00:00+00', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'contract_probe' +);" + ;; + job_analysis_task_ksao_link) + cross_tenant_insert_sql=" +INSERT INTO job_analysis_task_ksao_link ( + tenant_record_id, analysis_record_id, task_record_id, ksao_record_id, + relationship_strength, essential_for_task +) VALUES ( + '${TENANT_ID}', '${ANALYSIS_ID}', '${LINK_PROBE_TASK_ID}', + '${LINK_PROBE_KSAO_ID}', 1, FALSE +);" + ;; + job_analysis_write_command) + cross_tenant_insert_sql=" +INSERT INTO job_analysis_write_command ( + tenant_record_id, write_command_id, analysis_record_id, idempotency_key, + request_digest_sha256, actor_reference, purpose_code +) VALUES ( + '${TENANT_ID}', '${CROSS_TENANT_COMMAND_ID}', '${ANALYSIS_ID}', + 'cross-tenant-rls-probe-01', + 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + 'keyverse_subject:rls_probe', 'job_analysis_write' +);" + ;; + *) + echo "unhandled child table in cross-tenant INSERT probe: ${child_table}" >&2 + exit 1 + ;; + esac + + set +e + cross_tenant_insert_output="$({ \ + PGOPTIONS="-c orgmetra.tenant_record_id=${OTHER_TENANT_ID}" \ + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c \ + "SET ROLE orgmetra_rls_probe; ${cross_tenant_insert_sql}" ; \ + } 2>&1)" + cross_tenant_insert_status=$? + set -e + if [[ ${cross_tenant_insert_status} -eq 0 \ + || "${cross_tenant_insert_output}" != *"row-level security"* ]]; then + echo "${child_table} cross-tenant INSERT was not rejected by RLS WITH CHECK: ${cross_tenant_insert_output}" >&2 + exit 1 + fi done echo "job-analysis PostgreSQL schema hardening contract passed" From f2f3585395239868f34e2cc28197442873974286 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 16:04:07 +0900 Subject: [PATCH 3/3] test(job-analysis): bind RLS policy to PUBLIC role scope --- .../test_job_analysis_snapshot_schema_hardening.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_job_analysis_snapshot_schema_hardening.sh b/tests/test_job_analysis_snapshot_schema_hardening.sh index 933a23ace..725fe5395 100644 --- a/tests/test_job_analysis_snapshot_schema_hardening.sh +++ b/tests/test_job_analysis_snapshot_schema_hardening.sh @@ -27,7 +27,8 @@ if [[ ${delete_status} -eq 0 || "${delete_output}" != *"append-only"* ]]; then fi psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' -CREATE ROLE orgmetra_rls_probe NOLOGIN; +CREATE ROLE orgmetra_rls_probe + NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS; GRANT SELECT ON job_analysis_snapshot TO orgmetra_rls_probe; SQL @@ -78,9 +79,9 @@ child_tables=( ) # Catalog proof first: a missing FORCE-RLS policy would also return zero rows to -# another tenant. Require one and only one permissive policy, command ALL, and -# exact normalized USING/WITH CHECK expressions; substring matching would let -# an expression such as "... OR true" masquerade as tenant isolation. +# another tenant. Require one and only one PUBLIC permissive policy, command ALL, +# and exact normalized USING/WITH CHECK expressions; a role-scoped policy could +# otherwise default-deny the probe role and produce a false-green isolation test. for child_table in "${child_tables[@]}"; do child_rls_state="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " SELECT c.relrowsecurity::text || ':' || c.relforcerowsecurity::text @@ -98,6 +99,7 @@ SELECT count(*) FILTER ( WHERE polpermissive AND polcmd = '*' + AND polroles = ARRAY[0::oid] AND regexp_replace( coalesce(pg_get_expr(polqual, polrelid), ''), '[[:space:]()]', '', 'g' @@ -111,7 +113,7 @@ FROM pg_policy WHERE polrelid = '${child_table}'::regclass; ")" if [[ "${child_policy_state}" != "1:1" ]]; then - echo "${child_table} must have exactly one permissive ALL tenant policy with exact USING/WITH CHECK expressions: ${child_policy_state}" >&2 + echo "${child_table} must have exactly one PUBLIC permissive ALL tenant policy with exact USING/WITH CHECK expressions: ${child_policy_state}" >&2 exit 1 fi done @@ -262,4 +264,4 @@ INSERT INTO job_analysis_write_command ( fi done -echo "job-analysis PostgreSQL schema hardening contract passed" +echo "job-analysis PostgreSQL schema hardening contract passed" \ No newline at end of file