730521ee792a0ca10472c6ae872b2b75a47098ce
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| ee1a38492c |
Pin search_path, and close a write path that needed no login
Der Security Advisor meldete 34 Warnungen; nach dem Festnageln des search_path sind es zehn. Von diesen zehn ist eine einzige ein echter Befund — aber die hätte man in den 34 nicht gesehen. search_path (34 Warnungen) Alle betroffenen Funktionen sind SECURITY INVOKER, laufen also mit den Rechten der aufrufenden Person; ein manipulierter Pfad bringt dort nichts zu holen. Die vier DEFINER-Funktionen setzen ihn längst. Festgenagelt wird es trotzdem, für den Tag, an dem jemand eine davon auf SECURITY DEFINER umstellt, weil eine Mutation an RLS vorbei schreiben muss — dann wäre es eine Rechteausweitung, und an den search_path denkt in dem Moment niemand. Als Schleife statt als Liste von 34 Signaturen: die würde beim nächsten Umbau veralten. Sie lässt Erweiterungen in Ruhe (pg_trgm legt show_trgm und show_limit ebenfalls in public ab) und prüft am Ende selbst nach. pg_temp steht ausdrücklich am Pfadende — ohne die Angabe durchsucht Postgres das temporäre Schema zuerst, und dort darf jede Sitzung anlegen, was sie will. Ausführungsrechte (8 Warnungen) Hier trennt sich der Befund vom Rauschen, und zwar durch Messen 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 Die ersten beiden bleiben offen, und das ist keine Nachlässigkeit: sie werden aus den RLS-Policies heraus aufgerufen, und ein Policy-Ausdruck wird mit den Rechten der abfragenden Rolle ausgewertet. Ohne EXECUTE scheitert jede Abfrage auf jeder Tabelle. Preisgegeben wird nichts — beide nehmen keine Argumente und beantworten nur eine Frage über die aufrufende Person selbst. Der dritte ist der Befund. apply_due_pending_changes() wendet vorgemerkte Versetzungen, Beförderungen und Abwesenheiten an, ist SECURITY DEFINER, umgeht damit RLS — und war 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 für Fremde und macht das Geheimnis der Cron-Route wirkungslos. Entzogen für anon und authenticated; die Route benutzt die service_role und läuft weiter. rls_auto_enable() stammt nicht aus diesen Migrationen und wird nirgends aufgerufen. Der Entzug ist risikolos und beantwortet die Frage, was sie tut, notfalls mit einer klaren Fehlermeldung. Zwei Warnungen bleiben bewusst stehen pg_trgm in public trägt die Operatorklasse gin_trgm_ops, auf der zwei GIN-Indizes auf employees liegen. Ein Schemawechsel müsste Indizes und jeden search_path mitziehen — Risiko für eine Konvention, keine Rechteausweitung. „Leaked Password Protection" ist gegenstandslos: die Passwort-Anmeldung ist abgeschaltet, eine Anmeldung gegen die API antwortet mit email_provider_disabled. Es gibt kein Passwort, das kompromittiert sein könnte. |
|||
| 27669e0359 |
Put the whole application on the OM model, and delete what it replaced
Die Datenbank stand seit dem Cut-over auf org_units/om_positions/
position_assignments, die Anwendung fragte weiter nach employees.division_id,
team_id und manager_id — Spalten, die es nicht mehr gab. Die Oberfläche war
deshalb leer, obwohl die Daten vollständig da waren. Das ist jetzt behoben,
und zwar nicht durch Nachbau der alten Begriffe, sondern indem sie verschwinden.
Neu ist eine dünne Schicht, die die Verkettung Person → Besetzung →
Planstelle → Einheit einmal auflöst (lib/placement.ts) und der Baum als reine
Funktionen darauf (lib/org.ts): Vorfahrenkette, Teilbaum, Brotkrume. Alles
Weitere hängt daran.
Was sich dadurch von selbst erledigt hat:
- Das Organigramm musste drei Quellen versöhnen, weil keine den ganzen
Zeitstrahl abdeckte. position_assignments ist zeitabhängig, also
beantwortet eine Abfrage "wer besetzte am Stichtag welche Planstelle" —
für Vergangenheit und Zukunft gleichermassen. Wer keine Planstelle hatte,
war nicht da; eine zweite Zugehörigkeitsregel braucht es nicht mehr.
- Die Struktursicht war auf genau vier Ebenen verdrahtet und rendert jetzt
rekursiv über parent_id. Liste und Grafik entstehen aus *einem* Baum;
vorher lag dieselbe Hierarchie zweimal vor und konnte auseinanderlaufen.
- Eine offene Stelle ist keine eigene Tabelle mehr, sondern eine Planstelle
ohne laufende Besetzung — das Komplement kann nicht aus dem Tritt geraten.
- Eine Versetzung ist der Wechsel auf eine Zielplanstelle statt Zielteam
plus frei getipptem Titel. Sie kann damit nicht mehr dort landen, wo es
keine Stelle gibt, und die Tätigkeit kommt aus dem Job-Katalog.
- Beim Anlegen einer Planstelle entfällt die Suche nach der vorgesetzten
Person: sie ergibt sich aus der Einheit, die Frage kann nicht mehr falsch
beantwortet werden.
Zwei Auswertungen werden dabei richtiger, nicht nur anders. Ein
Stichtagsbericht gruppierte bisher nach der *heutigen* Zuordnung, weil es
keine Historie gab; er löst sie jetzt zum Stichtag auf. Und ein Ereignis
trägt die Einheit, in der die Person am Tag des Ereignisses sass — vorher
stand ein Austritt von vor zwei Jahren unter einem Team, in das sie nie
versetzt worden war. Der Bereichsfilter greift überall auf den ganzen
Teilbaum; auf den Bereich allein angewandt lieferte er nur die
Bereichsleitung.
Gelöscht: die Reorganisations-Werkbank samt Szenarien und Zügen (sie
verschob Teams und Abteilungen zwischen Bereichen — Objekte, die es nicht
mehr gibt; im OM-Modell ist das ein Umhängen von parent_id), die
Mitarbeiter- und Vorgesetztensuche, die nur sie und die Ausschreibung
brauchten, und aus lib/supabase/types.ts die Tabellen divisions,
departments, teams, positions und employee_assignments.
Die beiliegende Migration räumt die Datenbank entsprechend auf. Sie entfernt
auch Funktionen, die der Cut-over verfehlt hat: create_position,
delete_position und undo_reorg existierten zusätzlich in einer
jsonb-Variante und tauchen deshalb weiter in der PostgREST-Schnittstelle auf,
obwohl ihre Tabellen weg sind — ein Aufruf wäre erst zur Laufzeit
gescheitert. An ihre Stelle treten create_position und delete_position im
OM-Sinn; letzteres schliesst eine früher besetzte Planstelle, statt sie zu
löschen, sonst verschwände mit ihr die Besetzungshistorie.
Typecheck, Lint, Build und 182 Tests sind grün. Die Integrationstests sind
mitgezogen, aber weiterhin ungelaufen — dafür braucht es eine laufende
lokale Datenbank.
|