← Înapoi la „cum e construit”

Documentație în lucru: e verificată rând cu rând înainte de testarea deschisă. Nu lua nimic de-a gata.

Securitate

Fiecare tabel din baza asta de date refuză să întoarcă rândurile altui proprietar, iar un test care oprește build-ul o dovedește la fiecare commit. Nu e o regulă pe care cineva își amintește s-o aplice — e o suită care citește catalogul bazei de date și pică atunci când o migrare adaugă un tabel neprotejat. Pagina asta descrie modelul și numește mecanismul din spatele fiecărei afirmații. Nu numește intenționat niciun server, niciun depozit de fișiere și nicio funcție: a explica cum funcționează autorizarea inspiră încredere, pe când a publica un inventar nu face decât să împartă o listă de ținte.

Modelul

O singură regulă: primești rândurile tale și nimic altceva. Fiecare tabel are securitate pe rând — row-level security, RLS — cu politici legate de proprietar. Nu există un strat de permisiuni în aplicație care să poată fi ocolit ajungând la baza de date pe altă cale, fiindcă regula stă chiar în baza de date.

O țin trei suite de teste, iar ele rulează pe un Postgres adevărat, nu pe o imitație, la fiecare commit.

O migrare care uită e prinsă de meta-clichet. Comentariul lui din cod spune singur cum e gândit; fiind cod, e în engleză:

RLS meta-ratchet — the row-level-security equivalent of the file-size ratchet in scripts/doctrine.mjs.

CLAUDE.md: "Every table has RLS enabled with owner-scoped policies. No exceptions." A future migration that adds an unprotected table fails here, so the promise is mechanical rather than a thing we remember to check.

packages/db/src/rls/meta-ratchet.integration.test.ts. Tabelele cărora li se permite să n-aibă nicio politică sunt trecute în test, fiecare cu motivul pentru care e accesibil doar serverului; o excepție trebuie scrisă și argumentată chiar în fișierul care altfel ar pica.

Un al doilea utilizator adevărat nu ajunge la rândurile primului. Suita de izolare autentifică doi utilizatori reali și îl pune pe al doilea să încerce fiecare operație pe datele primului, pe fiecare tabel legat de proprietar. Merită citită fiindcă verifică ce fac politicile cu adevărat, nu ce ne-ar plăcea nouă să facă:

The assertions match what the policies ACTUALLY promise, which is not uniformly "an error" — each was verified against the local stack before it was written:

  • using filters rows, so B's SELECT/UPDATE/DELETE on A's rows succeed with ZERO rows affected. PostgREST returns 200 and an empty array, not 403. Asserting an error here would be asserting a wish.
  • with check blocks writes, so B inserting a row that claims owner_id = A is a real error: postgres 42501.
  • feedback has RLS and no policies: writes are 42501, but SELECT is simply empty — denial by emptiness, not by error.

packages/db/src/rls/isolation.integration.test.ts. Un test care ar fi susținut că „aici primești 403” ar fi trecut dintr-un motiv greșit sau ar fi picat fără motiv; ăsta scrie comportamentul care a fost măsurat.

Fotografiile sunt private, cont cu cont, ținute la același standard de packages/db/src/rls/storage.integration.test.ts.

Suitele nu pot fi îndreptate spre altceva decât o bază de date locală. Fiecare client pe care îl construiesc trece printr-o verificare care aruncă eroare dacă serverul nu e local (packages/db/src/rls/local-stack.ts). Creează și șterg utilizatori adevărați și rânduri adevărate; rulate în altă parte, trebuie să pice zgomotos, nu să meargă în tăcere.

Funcțiile de pe server

Două propoziții acoperă tot.

Fiecare funcție de pe server la care ajunge un client verifică apelantul înainte să facă orice. Cheia privilegiată a bazei de date e folosită doar de o funcție care a verificat deja cine întreabă, și doar pe date legate de identitatea pe care acel token a dovedit-o.

Ordinea asta e impusă, nu ținută minte. Fiecare funcție de pe server trebuie să declare cum își verifică apelantul, iar una care lasă asta nedeclarat oprește build-ul. Regula a găsit o lipsă adevărată chiar în ziua în care a fost scrisă: cea mai privilegiată funcție din proiect nu declarase niciodată nimic și se bizuia pe o valoare implicită a platformei. Aproape sigur se purta corect — iar „aproape sigur, din oficiu” nu e o decizie, și exact de-asta există verificarea.

Ce pleacă de pe telefon

Șase evenimente de analiză, și nimic altceva care să-ți descrie porumbeii.

Sunt o singură constantă în packages/core/src/analytics-events.ts, din care se generează și tipul evenimentelor din aplicație, și politica de confidențialitate publicată. Faptul că lista e o singură constantă e tot rostul: politica a numit o vreme doar patru dintre ele, două sprinturi la rând, fiindcă politica și codul erau două liste separate și nimic nu putea observa.

Regula pe care o scrie constanta și pe care o publică politica: un eveniment poartă un nume și cel mult o proprietate care e o mărime. Două evenimente au așa ceva — câte sezoane are o crescătorie și câte carduri de analiză aveau ceva de spus. Niciun număr de inel, niciun porumbel, nicio cursă, niciun sezon, niciun identificator de utilizator sau de dispozitiv nu pleacă de pe telefon ca dată de analiză.

Separat, rapoartele de eroare: când aplicația crapă, primim eroarea și modelul telefonului, fără date personale.

Regiunea

Tot — baza de date, fotografiile, autentificarea și analiza — e în Uniunea Europeană. E un angajament din politica de confidențialitate publicată, care e ceea ce ne obligă, nu pagina asta.

Ștergerea

Ștergerea contului golește imediat baza de date activă și distruge pe loc fotografiile, fără nicio copie nicăieri. Copii ale rândurilor rămân în copiile zilnice de rutină ale furnizorului cel mult 8 zile, apoi expiră.

Spunem amândouă jumătățile pentru că prima singură ar fi adevărată și înșelătoare în același timp. Varianta lungă, cu ce ai de făcut și ce vei vedea, e pe Datele tale.

Verifică singur

Nimic de pe pagina asta nu cere să fie crezut. Afirmațiile de aici pe care le poți verifica singur — că exportul e gratuit și complet, și că ștergerea îți golește crescătoria — sunt pașii 5, 6, 7 și 8 din verificarea de pe Datele tale. Durează cinci minute, pe telefonul tău, fără niciun cont de-al nostru.

Afirmațiile despre securitatea pe rând nu sunt pe lista aia, fiindcă un cititor nu poate verifica rândurile altui utilizator fără să fie el însuși alt utilizator. În spatele lor stau codul citat mai sus și faptul că el oprește build-ul.

Cum raportezi o problemă de securitate

Scrie la contact@thatpigeon.app — aceeași adresă publicată, în formă citibilă de programe, la /.well-known/security.txt. Spune-ne ce ai găsit și cum se reproduce; îți răspunde un om. Dacă e ceva sensibil, spune asta din primul rând și stabilim un canal înainte să trimiți detalii.

Nu există program de bug bounty și niciun calendar formal de divulgare — aici e un singur dezvoltator, iar a pretinde altceva ar fi exact genul de afirmație pe care restul paginii există ca s-o evite.