# Migration Safety Checklist > **Merk:** Denne sjekklisten er skrevet for v1-arkitekturen der RLS var > basert på `workspace_id`-kolonner og `SET app.current_workspace_id`. > Workspace-modellen er erstattet av en node-basert tilgangsmatrise (se > `docs/retninger/bruker_ikke_workspace.md`). Sjekklisten må skrives om for > det nye mønsteret: `node_access`-matrise, edge-basert tilgang, og > RLS-policies som opererer på bruker→node-edges i stedet for workspace-scope. > > Seksjonene under er bevart som referanse for v1-mønsteret. Sjekkliste for alle som kjører PostgreSQL-migrasjoner — lokalt eller i prod. ## Før migrering - [ ] Les migrasjonfilen og forstå hva den gjør - [ ] Har migrasjonen en tilhørende **down-migrering**? (påkrevd for skjema-endringer) - [ ] Tar migrasjonen backup-hensyn? (dropper den kolonner/tabeller med data?) ## Etter migrering ### RLS-verifisering (KRITISK) Etter *enhver* migrering som oppretter eller endrer tabeller med `workspace_id`: ```sql -- 1. Verifiser at RLS er aktivert på alle workspace-tabeller SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND tablename IN ('nodes', 'graph_edges', 'messages', 'channels', 'media_files', 'job_queue', 'message_attachments') ORDER BY tablename; -- Forventet: rowsecurity = true for alle -- 2. Verifiser at policies eksisterer SELECT tablename, policyname, cmd, qual FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename; -- Forventet: workspace_isolation_* policy for hver tabell -- 3. Test isolasjon: sett workspace A, forsøk å lese workspace B SET app.current_workspace_id = ''; SELECT count(*) FROM nodes WHERE workspace_id = ''; -- Forventet: 0 rader (RLS blokkerer) -- 4. Verifiser at superuser IKKE blokkeres (Rust workers trenger dette) RESET app.current_workspace_id; SET ROLE postgres; SELECT count(*) FROM nodes; -- Forventet: alle rader synlige ``` ### Indeksverifisering ```sql -- Sjekk at viktige indekser finnes SELECT indexname, tablename FROM pg_indexes WHERE schemaname = 'public' AND tablename IN ('nodes', 'graph_edges', 'messages') ORDER BY tablename; ``` ### Constraint-verifisering ```sql -- Sjekk at foreign keys er intakte SELECT tc.table_name, tc.constraint_name, tc.constraint_type FROM information_schema.table_constraints tc WHERE tc.table_schema = 'public' AND tc.constraint_type IN ('FOREIGN KEY', 'CHECK', 'UNIQUE') ORDER BY tc.table_name; ``` ## RLS Leak Hunter (CI-test) `SET app.current_workspace_id` er en skjult single point of failure — en glemt SET i en ny feature, en feil i connection-pool, eller en ny tjeneste som kobler til PG uten middleware kan føre til cross-workspace datalekkasje. Denne testen fanger det opp. ### Automatisk CI-test (to-workspace leak detection) Kjøres i migrasjonstester og som egen CI-steg: ```sql -- Opprett to test-workspaces INSERT INTO workspaces (id, name, slug) VALUES ('aaaaaaaa-0000-0000-0000-000000000001', 'Workspace A', 'ws-a'), ('aaaaaaaa-0000-0000-0000-000000000002', 'Workspace B', 'ws-b'); -- Seed testdata i begge INSERT INTO nodes (id, node_type, workspace_id) VALUES ('bbbbbbbb-0000-0000-0000-000000000001', 'tema', 'aaaaaaaa-0000-0000-0000-000000000001'), ('bbbbbbbb-0000-0000-0000-000000000002', 'tema', 'aaaaaaaa-0000-0000-0000-000000000002'); -- TEST 1: Sett workspace A, forsøk å lese workspace B SET app.current_workspace_id = 'aaaaaaaa-0000-0000-0000-000000000001'; DO $$ BEGIN IF (SELECT count(*) FROM nodes WHERE workspace_id = 'aaaaaaaa-0000-0000-0000-000000000002') > 0 THEN RAISE EXCEPTION 'RLS LEAK: Workspace A kan lese Workspace B sine noder!'; END IF; END $$; -- TEST 2: Uten SET (tom current_setting) skal returnere 0 rader RESET app.current_workspace_id; DO $$ BEGIN -- For vanlig bruker (ikke superuser) bør dette returnere 0 IF (SELECT count(*) FROM nodes) > 0 AND current_setting('is_superuser') = 'off' THEN RAISE EXCEPTION 'RLS LEAK: Uautentisert tilkobling kan lese data!'; END IF; END $$; ``` ### Audit-trigger (produksjon) Valgfri trigger som logger mistenkelige queries i prod: ```sql -- Tabell for RLS-audit CREATE TABLE IF NOT EXISTS rls_audit_log ( id BIGSERIAL PRIMARY KEY, table_name TEXT NOT NULL, operation TEXT NOT NULL, current_workspace TEXT, session_user TEXT NOT NULL, query_timestamp TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Funksjon som logger når current_workspace_id ikke er satt CREATE OR REPLACE FUNCTION audit_rls_context() RETURNS TRIGGER AS $$ BEGIN IF current_setting('app.current_workspace_id', true) IS NULL OR current_setting('app.current_workspace_id', true) = '' THEN IF current_setting('is_superuser') = 'off' THEN INSERT INTO rls_audit_log (table_name, operation, current_workspace, session_user) VALUES (TG_TABLE_NAME, TG_OP, current_setting('app.current_workspace_id', true), session_user); END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; ``` **Kjør leak hunter mot ALLE tabeller med workspace_id — ikke bare de som er listet over.** Nye tabeller legges til i listen automatisk via introspeksjon: ```sql -- Finn alle tabeller med workspace_id-kolonne (bør alle ha RLS) SELECT t.tablename FROM pg_tables t JOIN information_schema.columns c ON c.table_name = t.tablename WHERE c.column_name = 'workspace_id' AND t.schemaname = 'public' AND NOT EXISTS ( SELECT 1 FROM pg_policies p WHERE p.tablename = t.tablename ); -- Forventet: 0 rader. Enhver rad her = tabell med workspace_id UTEN RLS-policy. ``` ## Automatisering Disse sjekkene kjøres automatisk i migrasjonstestene (se `docs/arkitektur.md` §10.2). Manuell kjøring er kun nødvendig ved prod-migrasjoner til automatiserte tester er på plass. **RLS Leak Hunter bør prioriteres som første CI-steg — den beskytter mot den mest alvorlige feilkategorien (cross-workspace datalekkasje).**