From 19d6c4a2febf5dfe68362ee3e7c99e4bcc4a15ed Mon Sep 17 00:00:00 2001 From: Christopher Rotnes Date: Fri, 15 May 2026 22:42:22 +0200 Subject: [PATCH] security: apply quick-win hardening fixes (#229 #231 #232 #233) - Add app/api/local.settings.json to .gitignore to prevent accidental secret exposure via git add - Tighten CSP: scope connect-src to specific Supabase project subdomain and add frame-ancestors 'none' directive - DB migration: revoke EXECUTE on handle_new_user() from anon and authenticated roles (was callable via REST RPC with SECURITY DEFINER) - DB migration: set search_path = public on save_session, update_session, and replace_template_exercises to prevent schema injection Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + app/public/staticwebapp.config.json | 2 +- .../migrations/20260515_security_quick_wins.sql | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 supabase/migrations/20260515_security_quick_wins.sql diff --git a/.gitignore b/.gitignore index b8f85b0..1ada1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ dist/ files.zip .env .env.local +app/api/local.settings.json .claude/ design_handoff_workout_lens/ design_handoff_tilpass_appen/ diff --git a/app/public/staticwebapp.config.json b/app/public/staticwebapp.config.json index fe4daeb..eea3322 100644 --- a/app/public/staticwebapp.config.json +++ b/app/public/staticwebapp.config.json @@ -26,7 +26,7 @@ "X-Frame-Options": "DENY", "Referrer-Policy": "strict-origin-when-cross-origin", "Permissions-Policy": "camera=(), microphone=(), geolocation=()", - "Content-Security-Policy": "default-src 'self'; script-src 'self' 'sha256-S1NwxpfinBiP8uiGmiz+HYOp4lnKrfbe0hf7PcCP3Nk='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' https://*.supabase.co; object-src 'none'; base-uri 'self'; form-action 'self'" + "Content-Security-Policy": "default-src 'self'; script-src 'self' 'sha256-S1NwxpfinBiP8uiGmiz+HYOp4lnKrfbe0hf7PcCP3Nk='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' https://kyolnraqudwrjjbtxhwx.supabase.co; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" }, "platform": { "apiRuntime": "node:22" diff --git a/supabase/migrations/20260515_security_quick_wins.sql b/supabase/migrations/20260515_security_quick_wins.sql new file mode 100644 index 0000000..6296086 --- /dev/null +++ b/supabase/migrations/20260515_security_quick_wins.sql @@ -0,0 +1,17 @@ +-- Revoke direct RPC access to handle_new_user(). +-- This is an auth trigger that should only fire internally on user creation, +-- never be callable via /rest/v1/rpc/ by anon or authenticated roles. +REVOKE EXECUTE ON FUNCTION public.handle_new_user() FROM anon; +REVOKE EXECUTE ON FUNCTION public.handle_new_user() FROM authenticated; + +-- Fix mutable search_path on all three stored functions. +-- Without SET search_path, a privileged attacker could shadow public schema +-- tables and redirect writes to malicious objects. +ALTER FUNCTION public.replace_template_exercises(p_template_id uuid, p_exercises jsonb) + SET search_path = public; + +ALTER FUNCTION public.save_session(p_gym_calendar_id uuid, p_session_date date, p_image_url text, p_notes text, p_training_group_id uuid, p_exercises jsonb, p_replace boolean) + SET search_path = public; + +ALTER FUNCTION public.update_session(p_session_id uuid, p_gym_calendar_id uuid, p_exercises jsonb, p_replace boolean) + SET search_path = public;