/CREWDECK/SECURITY NOTES
[ SECURITY NOTES ]

What is intentionally open — and why.

Companion to @security-memory. Use this when a scanner flags something on this list — it's accepted, not a regression.

[ 01 · INTENTIONAL PUBLIC ENDPOINTS ]

Public HTTP routes

Anything under /api/public/* bypasses session auth on published deployments. Each handler enforces its own auth.

  • POST/api/public/ingest
    AUTH · Bearer <api_key> (per-workspace)

    Webhook ingestion for external tools (GitHub, CI, bots) to create or update tasks.

    Validated with Zod. The key hash is matched server-side with a direct database query; the key's workspace bounds every write. Revoked keys are rejected. last_used_at is touched on every accepted call (audited).

[ 02 · ACCEPTED SECURITY DEFINER FUNCTIONS ]

Database functions running as definer

All listed functions pin search_path = public. The Supabase linter warns on every SECURITY DEFINER; the entries below are deliberate. Only flag a NEW definer if it's RPC-callable from PostgREST, lacks a pinned search_path, or performs writes the caller's RLS wouldn't allow.

  • has_role / has_workspace_role / workspace_role / is_workspace_member / current_user_kind

    Role/membership lookups used inside RLS policies. Must run as definer so the policy itself doesn't recursively re-trigger RLS on memberships/profiles.

    CALLABLE · Yes (RPC) — read-only, returns only the caller's own auth.uid() role context.

  • track_workspace_id / column_workspace_id

    Resolve the workspace owner of a track/column from inside RLS policies on board_columns and tasks. Without definer, RLS on tracks would block the lookup.

    CALLABLE · Yes (RPC) — returns only a UUID the caller already needs to query its children.

  • handle_new_user

    auth.users → public.profiles bootstrap trigger. Runs as definer because the auth schema is privileged.

    CALLABLE · No — trigger only.

  • seed_workspace_defaults / seed_track_defaults

    On workspace/track creation, seed default task_types, board_columns and the creator's assigner membership. The creator may not yet hold the role that RLS would require for these inserts.

    CALLABLE · No — trigger only.

  • set_task_workspace_id

    BEFORE INSERT trigger that fills tasks.workspace_id from the parent track. Needs definer to read tracks for any caller.

    CALLABLE · No — trigger only.

  • prevent_profile_user_kind_escalation

    BEFORE UPDATE trigger on profiles. Blocks an external user from changing their own user_kind to 'internal'. Definer is needed to read the caller's current user_kind without recursion.

    CALLABLE · No — trigger only.

  • prevent_last_assigner_removal

    BEFORE DELETE/UPDATE trigger on memberships. Performs SELECT ... FOR UPDATE over sibling assigner rows; definer ensures sibling rows aren't filtered out by the caller's RLS (which would produce false negatives under concurrency).

    CALLABLE · No — trigger only.

  • audit_write

    Internal helper that inserts into audit_log. The table has no INSERT policy by design — only definer paths may write. EXECUTE is revoked from public/anon/authenticated.

    CALLABLE · No — EXECUTE revoked.

  • audit_memberships / audit_task_approval / audit_api_keys

    Trigger handlers that log sensitive changes. Need definer to write into audit_log (which is locked down). EXECUTE is revoked from public/anon/authenticated.

    CALLABLE · No — trigger only; EXECUTE revoked.