From 8e744c982ed2a16c0659099f748a684fdef273a7 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Fri, 18 Sep 2026 11:43:05 +0200 Subject: [PATCH] =?UTF-8?q?fix(security):=20F08-Folge=20=E2=80=94=20RLS-He?= =?UTF-8?q?nne-Ei=20auf=20api=5Ftokens=20aufloesen=20(Migration=200147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim F08-Live-Beweis aufgedeckt: JEDER Bearer-Token wurde mit 401 token_invalid abgelehnt — auch frisch erstellte. Ursache: erzwungenes RLS mit Tenant-Policy auf api_tokens (Migration 0084 reaktivierte es blind; 0080 hatte es bewusst deaktiviert: "written during login before tenant context"). verify_api_token muss den Hash NACHSCHLAGEN, um den Tenant zu BESTIMMEN — Henne-Ei: die Tenant-Policy blockiert genau diese Abfrage, da die Request-Session noch keinen Tenant-Kontext hat. Astra prophezeite das in F10: "Eine alleinige Reparatur der Bearer-Unterstützung kann ihn erst erreichbar machen" — exakt eingetroffen. Fix (Migration 0147): RLS auf api_tokens deaktiviert + Policy entfernt. Sicherheit unveraendert: Der SHA-256-Hash IST das Zugangsgesetznis; ein Hash-Lookup kann keine fremden Mandanten-Tokens aufzaehlen. sessions und password_reset_tokens sind bereits RLS-off (gleiche Bootstrap-Begruendung, live verifiziert). Verifikation folgt nach Deploy mit dem F08-Live-Bearer-Beweis. --- alembic/versions/0147_api_tokens_rls_off.py | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 alembic/versions/0147_api_tokens_rls_off.py diff --git a/alembic/versions/0147_api_tokens_rls_off.py b/alembic/versions/0147_api_tokens_rls_off.py new file mode 100644 index 0000000..1c0786c --- /dev/null +++ b/alembic/versions/0147_api_tokens_rls_off.py @@ -0,0 +1,50 @@ +"""Disable RLS on api_tokens (F08 bootstrap fix, Astra S2). + +verify_api_token() must look up the token hash via the request session +(crm_api) BEFORE any tenant context exists — the TOKEN is what determines +the tenant. Forced RLS with a tenant-isolation policy on api_tokens made +that lookup return zero rows, so EVERY Bearer token was rejected with 401 +"token_invalid", including freshly created ones (verified live on +production 2026-09-18). + +This restores the documented decision from migration 0080 ("written +during login before tenant context") which 0084 inadvertently overrode +by blindly re-enabling fail-closed RLS everywhere. sessions and +password_reset_tokens remain RLS-off for the same bootstrap reason. + +Security unchanged: the SHA-256 token hash IS the access secret — a +lookup by hash cannot enumerate other tenants' tokens, and every use of +the row still goes through the authenticated verify path. + +Revision ID: 0147 +Revises: 0146 +""" + +from alembic import op + +revision = "0147" +down_revision = "0146" +branch_labels = None +depends_on = None + + +POLICY_NAME = "api_tokens_tenant_isolation" + + +def upgrade() -> None: + # Remove the tenant-isolation policy first (it only covered the + # runtime roles anyway), then disable + unforce RLS. + op.execute(f"DROP POLICY IF EXISTS {POLICY_NAME} ON api_tokens;") + op.execute("ALTER TABLE api_tokens DISABLE ROW LEVEL SECURITY;") + op.execute("ALTER TABLE api_tokens NO FORCE ROW LEVEL SECURITY;") + + +def downgrade() -> None: + # Best-effort inverse: restore forced RLS + the previous policy. + op.execute("ALTER TABLE api_tokens ENABLE ROW LEVEL SECURITY;") + op.execute("ALTER TABLE api_tokens FORCE ROW LEVEL SECURITY;") + op.execute( + "CREATE POLICY api_tokens_tenant_isolation ON api_tokens " + "FOR ALL TO crm_api, crm_worker " + "USING (tenant_id = (NULLIF(current_setting('app.current_tenant_id', true), ''))::uuid)" + )