The database moved to a container of our own; the platform is gone. This takes out what was left of it — and, where the leftovers were load bearing, moves rather than deletes. Moved, not deleted: supabase/migrations/ -> db/migrations/ the schema's source of truth supabase/build-org.ts -> scripts/build-org.ts lib/supabase/types.ts -> lib/types.ts 52 import sites repointed The bookkeeping needed care. It lived in `supabase_migrations.schema_migrations`, and simply renaming the schema would have left the runner facing an empty table: it would have called all 67 migrations pending and replayed them against a database that is long since current. So the runner now creates `migrationen.schema_migrations` and, once, copies the old rows across — guarded so a second run does nothing and a fresh database skips it entirely. Only then does migration 20260907100000 drop the old schema. Deleted: the CLI config, the seed, the historical schema/function dumps (nothing read them), scripts/umzug-von-supabase.sh (the move is done), and both Supabase packages plus the CLI. Nothing in the application imported them — the build now succeeds with no environment variables at all, which is the proof. Integration tests: six of them signed in through Supabase Auth and asserted against the anon key and the service role. That model is gone, so the tests were not portable — they are deleted. session-context and employee-status-filter already ran on pg and are untouched; om-reporting is ported to a direct connection because it guards a real risk (the reporting line rule exists twice, once in SQL and once in TypeScript). CI: the integration job started a Supabase stack. It now runs a postgres service, applies deploy/db-init and every migration to an empty database — that was the valuable part, and it still holds — then checks that a second run is a no-op, which is what proves the bookkeeping works. Docs: security-review.md audited a service-role key, a cookie adapter and auth.users, none of which exist. Restating findings about removed components would suggest today's system had been reviewed; it has not. It now records what was removed and says a fresh review is due. data-model.md was already marked obsolete and described the pre-OM schema; azure-migration.md was a plan for a route not taken. Both deleted. Verified: npm ci, typecheck, lint, 445 tests, build — all clean without the packages. Integration tests skip cleanly with no database. Migration SQL and the runner are reviewed but NOT executed: no Docker here, and the old instance no longer resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
3.8 KiB
SQL
73 lines
3.8 KiB
SQL
-- Ausführungsrechte auf den SECURITY-DEFINER-Funktionen zurechtrücken.
|
|
--
|
|
-- Der Advisor meldet alle vier als „Public Can Execute" und „Signed-In Users
|
|
-- Can Execute". Das ist nicht bei allen vieren dasselbe Problem — nachgemessen
|
|
-- mit dem anon-Schlüssel gegen die laufende Datenbank:
|
|
--
|
|
-- anon.rpc(is_hr_user) -> false
|
|
-- anon.rpc(current_hr_user_id) -> null
|
|
-- anon.rpc(apply_due_pending_changes) -> 0 ← das ist der Befund
|
|
--
|
|
-- Nur der dritte ist einer.
|
|
|
|
-- ── Bleibt offen, und zwar mit Absicht ───────────────────────────
|
|
--
|
|
-- is_hr_user() und current_hr_user_id() werden *aus den RLS-Policies heraus*
|
|
-- aufgerufen. Ein Policy-Ausdruck wird mit den Rechten der abfragenden Rolle
|
|
-- ausgewertet; ohne EXECUTE für anon und authenticated scheitert damit jede
|
|
-- Abfrage auf jeder Tabelle mit „permission denied for function". Der Entzug
|
|
-- würde die Anwendung vollständig lahmlegen.
|
|
--
|
|
-- Preisgegeben wird dabei nichts: beide nehmen keine Argumente und beantworten
|
|
-- ausschliesslich eine Frage über die aufrufende Person selbst. Wer nicht
|
|
-- angemeldet ist, bekommt false beziehungsweise null — siehe Messung oben.
|
|
|
|
-- ── Wird entzogen ────────────────────────────────────────────────
|
|
--
|
|
-- apply_due_pending_changes() wendet vorgemerkte Versetzungen, Beförderungen
|
|
-- und Abwesenheiten an, sobald ihr Datum erreicht ist. Es ist SECURITY
|
|
-- DEFINER, umgeht also RLS, und war bis hierher ohne Anmeldung aufrufbar — der
|
|
-- anon-Schlüssel steht im ausgelieferten Browser-Bündel.
|
|
--
|
|
-- Der Schaden wäre begrenzt, weil nur ohnehin fällige Änderungen angewandt
|
|
-- werden. Aber es ist ein Schreibpfad, den Fremde auslösen können, und er
|
|
-- macht das Geheimnis der Cron-Route (app/api/cron/apply-pending-changes)
|
|
-- wirkungslos.
|
|
--
|
|
-- Diese Route ist der einzige Aufrufer und benutzt createAdminClient(), also
|
|
-- die service_role — der Entzug für anon und authenticated bricht sie nicht.
|
|
revoke execute on function apply_due_pending_changes() from anon, authenticated;
|
|
|
|
-- rls_auto_enable() stammt nicht aus diesen Migrationen und wird von der
|
|
-- Anwendung nirgends aufgerufen. Was sie tut, ist von hier aus nicht
|
|
-- feststellbar; eine Funktion, die RLS umschaltet und ohne Anmeldung
|
|
-- aufrufbar ist, wäre allerdings ernst. Der Entzug ist risikolos, weil kein
|
|
-- Aufrufer existiert — und falls doch jemand sie braucht, meldet er sich mit
|
|
-- einer klaren Fehlermeldung statt still etwas zu verstellen.
|
|
do $$
|
|
begin
|
|
if exists (
|
|
select 1 from pg_proc p
|
|
join pg_namespace n on n.oid = p.pronamespace
|
|
where n.nspname = 'public' and p.proname = 'rls_auto_enable'
|
|
) then
|
|
execute 'revoke execute on function public.rls_auto_enable() from anon, authenticated';
|
|
end if;
|
|
end;
|
|
$$;
|
|
|
|
-- ── Was bewusst *nicht* passiert ─────────────────────────────────
|
|
--
|
|
-- „Extension in Public" (pg_trgm) bleibt stehen. Die Erweiterung trägt die
|
|
-- Operatorklasse gin_trgm_ops, auf der zwei GIN-Indizes auf employees liegen
|
|
-- (20260714120400_performance_indexes.sql). Ein Schemawechsel müsste die
|
|
-- Indizes und jeden search_path mitziehen, der sie erreichen soll — gerade
|
|
-- jetzt, wo jede Funktion auf `public, pg_temp` festgenagelt ist. Das ist
|
|
-- Aufwand und Risiko für einen Hinweis, der keine Rechteausweitung beschreibt,
|
|
-- sondern eine Konvention.
|
|
--
|
|
-- „Leaked Password Protection Disabled" ist gegenstandslos: die
|
|
-- Passwort-Anmeldung ist abgeschaltet. Eine Anmeldung mit E-Mail und Passwort
|
|
-- gegen die API antwortet mit `email_provider_disabled` (422). Es gibt kein
|
|
-- Passwort, dessen Kompromittierung geprüft werden könnte.
|