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
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
-- 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;
|
||||
$$;
|
||||
Reference in New Issue
Block a user