Files
ims/supabase/migrations/20260711000013_match_incidents_auth_guard.sql
T
adminandClaude Sonnet 4.6 c07bdb77ad fix: RLS guard in match_incidents + try/catch around AI/embed calls
- Add new migration 20260711000013_match_incidents_auth_guard.sql that
  replaces match_incidents with an inline auth guard: callers without
  hse/admin role receive PGRST301 Forbidden, closing the SECURITY
  DEFINER RLS bypass.
- Wrap anthropic.messages.create() in try/catch returning 503 in all
  four AI routes: quality-check, triage-suggest, rca-draft, similar.
- Wrap JSON.parse(inc.embedding) and embedText() in similar/route.ts
  in a shared try/catch returning 503 Embedding service unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFDuBhMKvoWjrWT3ZnGmmr
2026-07-11 16:48:20 +08:00

44 lines
1.0 KiB
PL/PgSQL

-- Add authorization guard to match_incidents to enforce DB-level access control.
-- Without this, SECURITY DEFINER bypasses RLS for any direct caller.
create or replace function match_incidents(
query_embedding vector(1024),
exclude_id uuid,
match_count int default 5
)
returns table (
id uuid,
reference_no text,
incident_type text,
description text,
severity int,
similarity float
)
language plpgsql
security definer
as $$
begin
-- Enforce that only hse/admin roles can call this function directly
if not exists (
select 1 from public.users
where id = auth.uid()
and role in ('hse', 'admin')
) then
raise exception 'Forbidden' using errcode = 'PGRST301';
end if;
return query
select
i.id,
i.reference_no,
i.incident_type,
i.description,
i.severity,
1 - (i.embedding <=> query_embedding) as similarity
from incidents i
where i.id != exclude_id
and i.embedding is not null
order by i.embedding <=> query_embedding
limit match_count;
end;
$$;