Scheiding afgedwongen door de database, niet door applicatiecode.
Elk verzoek dat iets wijzigt, wordt drie keer geautoriseerd, op drie plekken die onafhankelijk van elkaar falen. Daarachter liggen 868 row-level security-policies, zodat een fout in een handler een bug is en geen datalek. Deze pagina is geschreven voor degene die zijn handtekening onder ons moet zetten.
Hoe Lurno de data van de ene organisatie beschermt tegen de andere
Lurno autoriseert elk wijzigend verzoek op drie onafhankelijke plekken. De interface controleert een recht voordat ze een knop toont. De server controleert hetzelfde recht voordat hij data aanraakt, en weigert als het antwoord nee is. De database past een row-level security-policy toe die bepaalt welke rijen de query überhaupt mag teruggeven. Alle drie moeten slagen. Er zijn 868 van zulke policies verspreid over 676 migraties, en applicatietabellen leven in 14 domeinschema's in plaats van in het standaardschema public, zodat de rijen van de ene organisatie onbereikbaar blijven vanuit de sessie van een andere, zelfs als de code erboven fout is. Beheerwijzigingen worden weggeschreven naar een auditlog dat alleen toevoegt en waarvan de rijen met SHA-256 aan elkaar zijn geketend en met een query te verifiëren zijn. Augmental Learning Inc. werkt volgens de praktijken van ISO 27001; het heeft geen ISO 27001-certificaat en geen SOC 2-rapport, en dat zeggen we in de vragenlijst in plaats van een koper iets anders te laten aannemen.
- Autorisatie via drie poorten
- Een model waarin één verzoek drie keer wordt geautoriseerd, door drie mechanismen die onafhankelijk van elkaar falen. Poort één is de interface: die verbergt een knop waarvoor de ingelogde persoon geen recht heeft. Poort twee is de server: de handler controleert het recht voordat hij data aanraakt en breekt af als het antwoord nee is. Poort drie is de database: een row-level security-policy op de tabel beperkt welke rijen de query kan teruggeven. De eerste poort is gemak, de tweede is de eigenlijke grens, en de derde is wat nog steeds standhoudt als de tweede wordt overgeslagen.
Drie poorten, en wat er achter elk daarvan zit
Eén verzoek, drie keer geautoriseerd
Een knop in de interface vraagt of de ingelogde persoon een recht heeft voordat hij verschijnt. De handler op de server controleert hetzelfde recht voordat hij iets doet. Daarna past de database een policy toe die bepaalt welke rijen de query mag zien. Drie mechanismen, drie afzonderlijke plekken om het goed te doen, en één keer nee eindigt het verzoek.
- De controle in de interface is gemak, geen grens. Ze verbergt een knop; ze beschermt geen tabel.
- Elke controle loopt via één rechtencatalogus en één functie. Geen enkele handler leest een rolnaam en bepaalt zelf wat die rol betekent.
- Een rol die op een vestiging in de boom van een organisatie wordt toegekend, werkt door naar alles onder die vestiging — en de databasepolicies lezen dezelfde boom als de interface, zodat de twee niet uit elkaar kunnen lopen.
868 policies verspreid over 676 migraties
De scheiding is schema, geen afspraak. Tabellen worden aangemaakt met row-level security aan en met policies die verwijzen naar dezelfde rechtenfunctie die de server aanroept, en een migratiecontrole zoekt naar tabellen die toegang kregen zonder policy erachter. Het getal is geen marketingcijfer — het is wat de database teruggeeft als u vraagt hoeveel policies ze afdwingt.
- Policies bevatten hun eigen logica niet. Ze roepen de rechtenfunctie aan, zodat het antwoord niet kan afwijken van wat de server een moment eerder besloot.
- Schrijfpolicies dragen naast een leesvoorwaarde ook een controlevoorwaarde, zodat een insert geen rij kan plaatsen in een organisatie waarvoor de schrijver geen recht heeft.
- Waar een view persoonsgegevens toont, wordt toegang kolom voor kolom verleend. Een tabelbrede toekenning zou de bescherming op kolomniveau stilzwijgend ongedaan maken, dus die wordt niet gebruikt.
Veertien domeinschema's, nooit het publieke
Applicatietabellen leven in veertien benoemde schema's — één voor identiteit en toegang, één voor organisaties, één voor media, enzovoort. Niets van belang staat in het standaardschema public, de plek waar een per ongeluk verleend recht of een te ruime standaardinstelling het snelst iets blootlegt. Privémedia hebben helemaal geen storagepolicies voor ingelogde browsers: elke leesactie wordt ondertekend door een server die eerst de bijbehorende rij heeft gecontroleerd, en elke upload wordt op dezelfde manier uitgegeven.
- Eenmalige tokens — uitnodigingen, wachtwoordherstel, accountherstel — worden opgeslagen als gezouten hashes. Het ruwe token wordt één keer getoond, in de e-mail, en nooit meer.
- Alleen branding is openbaar. Het logo en favicon van een organisatie moeten al vóór het inloggen kunnen renderen; avatars en lesmateriaal niet, dus die blijven ondertekend.
Achteraf bewijzen wat er is gebeurd
Een auditlog dat niemand kan controleren is een logbestand met betere marketing. Dit log voegt alleen toe, laat manipulatie zien, en controleren is een query.
Een log dat alleen toevoegt
Beheerwijzigingen worden weggeschreven als gebeurtenissen met actor, actie, resource, organisatie en een payload van vóór en na. Niets werkt een auditrij bij. Het schrijfpad voegt alleen toe.
Een SHA-256-hashketen
Elke gebeurtenis bewaart de hash van de vorige en een hash van zichzelf, beide toegekend door een databasetrigger en niet door de code die de gebeurtenis logde. Wijzig één rij en elke latere hash klopt niet meer.
Verificatie die u kunt zien draaien
Een verificatiefunctie loopt de keten door en noemt de eerste rij waar die breekt. We draaien hem tijdens een review in uw bijzijn.
Toegangsreviews
Wie welke rol heeft, in welke organisatie, periodiek beoordeeld in plaats van pas tijdens een audit. Een intrekking is een auditgebeurtenis als elke andere.
Twee mensen voor de onomkeerbare dingen
Iemand anonimiseren of verwijderen is eerst een verzoek en dan een aparte goedkeuring. De aanvrager kan zijn eigen verzoek niet goedkeuren, en niemand kan zichzelf als doelwit kiezen.
Sessies die u kunt beëindigen
Een ingelogde persoon kan zijn andere sessies intrekken, en een ingetrokken sessie wordt buitengesloten bij de volgende rechtencontrole in plaats van pas wanneer het token toevallig verloopt.
Van toestemming tot wissing, met een klok op elke stap
Elke tenant is verwerkingsverantwoordelijke voor de data van zijn cursisten en Lurno is verwerker. De machinerie hieronder maakt die scheiding echt in plaats van contractueel.
- 01
Toestemming, met een versie erop
Elk beleid waarmee iemand instemt, draagt een versienummer. Wijzig het beleid en de oude toestemming telt niet meer: de persoon wordt opnieuw gevraagd, en waarmee hij instemde en wanneer wordt vastgelegd. AI-verwerking is een aparte toestemming — trek die in en er wordt niets naar een provider gestuurd.
- 02
Een inzageverzoek, als wizard
Een beheerder werkt het verzoek stap voor stap af in plaats van het te improviseren: identificeer de betrokkene, verzamel wat er platformbreed van hem is, produceer de export. Overdraagbaarheid betekent machineleesbaar — JSON en CSV, geen pdf van een schermafbeelding.
- 03
Bewaartermijnen met een einddatum
Persoonsgegevens dragen per categorie een bewaartermijn in plaats van standaard eeuwig te blijven bestaan. Inactieve accounts, verlopen uitnodigingen en oude sessies hebben allemaal een datum waarna ze ophouden te bestaan.
- 04
Wissing onder een vierogenprincipe
De ene beheerder vraagt het aan, een tweede keurt het goed, en pas dan wordt het uitgevoerd. De aanvrager kan nooit goedkeuren, en geen van beiden kan zichzelf als doelwit kiezen. Wissing raakt de tabellen die naar de persoon verwijzen, niet alleen de profielrij.
- 05
Een datalekregister met een klok van 72 uur
Een incident is een rij met een deadline die wordt berekend als 72 uur na ontdekking, en herinneringen die blijven afgaan tot het gesloten is. De klok begint bij de ontdekking, niet op het moment dat iemand aan artikel 33 denkt.
Waar de data daadwerkelijk staat
Mediaopslag die u ergens anders kunt neerzetten
Bestanden worden weggeschreven via een opslaginterface met vier implementaties: Supabase Storage, Amazon S3, Azure Blob Storage en Google Cloud Storage. Dat is meestal het eerlijke antwoord op een residentie-eis — een deployment kan bestanden naar een bucket in de regio schrijven die u moet gebruiken, in plaats van te wachten tot een leverancier er een opent. Laat de eis vroeg weten, dan zeggen we ronduit of eraan wordt voldaan.
- De database, de authenticatie en de functies waarop de API draait, worden gehost door Supabase. Dat is de enige afhankelijkheid die u niet weg kunt configureren, en ze staat met naam in de DPA.
- Eigen domeinen per organisatie worden geserveerd via Cloudflare met automatisch uitgegeven certificaten, zodat een cursist in Beiroet of Milaan uw domein ziet en niet het onze.
Productanalytics in de EU, met cursisten erbuiten
Platformanalytics lopen via PostHog in de EU-regio, achter een abstractielaag zodat de leverancier vervangen kan worden zonder het product aan te raken. Session replay is gemaskeerd en neemt cursisten nooit op — de cursistomgeving is helemaal niet voor replay geïnstrumenteerd.
- Tekstvelden en alles wat als persoonsgegeven is gemarkeerd, worden in de browser gemaskeerd voordat een replay ergens heen gaat.
- Analytics zijn afhankelijk van toestemming en staan uit tenzij een organisatie ze aanzet. Er wordt op geen enkele omgeving geprofileerd over een minderjarige.
Wat er met tekst gebeurt voordat die een AI-provider bereikt
Een bericht van een cursist, een geüploade pdf, een vraag die in de copilot wordt getypt: het is allemaal inhoud, geen instructie. Dat onderscheid wordt afgedwongen, niet in een prompt gevraagd.
Classificatie van injectie
Onvertrouwde invoer wordt geclassificeerd voordat de echte aanroep draait. Tekst die het model probeert om te leiden, wordt gemarkeerd in plaats van uitgevoerd.
Een willekeurig gekozen omheining
Onvertrouwde tekst wordt omsloten door een tag per verzoek die ze niet kan raden, en die tag wordt uit de inhoud zelf verwijderd. Bij afkappen wordt de omheining opnieuw gesloten in plaats van open te blijven staan.
Anonimisering vóór de aanroep
Namen, e-mailadressen en identificatoren worden vervangen voordat er iets naar een provider vertrekt. Beoordelen gebeurt over een geanonimiseerde inzending; de koppeling terug naar de cursist blijft op onze server.
Validatie tegen een uitvoerschema
Elk antwoord wordt tegen een schema gevalideerd. Een misvormd antwoord krijgt één herstelpoging en faalt daarna, in plaats van in een cursus terecht te komen.
Een spoor van voorstel tot wijziging
Verzoeken, geaccepteerde concepten en afgewezen concepten zijn aparte regels in hetzelfde log met hashketen. Wat het model voorstelde staat naast wat een mens toepaste.
Hoe mensen inloggen
Geschreven zoals een security reviewer het nodig heeft: wat er vandaag geleverd wordt, en wat niet.
Accounts op uitnodiging
Accounts worden aangemaakt door een beheerder of via een uitnodiging — er is geen self-service aanmelden. Links voor herstel en wachtwoordreset zijn eenmalig en worden gehasht opgeslagen.
Partneraanmelding (silent SSO)
Een partnersysteem dat iemand al heeft geauthenticeerd, kan die persoon met een ondertekende assertie overdragen, zodat de cursist nooit een tweede login ziet. Het wordt per organisatie ingesteld, werkt met sleutels en is intrekbaar.
Single sign-on via SAML en OIDC
Op de roadmapNiet gebouwd. Hangt uw uitrol af van Entra ID of Okta, zeg het dan bij het eerste gesprek, dan vertellen we hoe het ervoor staat in plaats van te suggereren dat het er is.
MFA en passkeys
Op de roadmapStaan eveneens op de roadmap. Vandaag beheert de identity provider het wachtwoord, kunnen sessies door hun eigenaar worden ingetrokken, en gaat die intrekking in bij de volgende rechtencontrole.
Iedereen die uw data aanraakt
Vijf, en elk daarvan staat met naam in de verwerkersovereenkomst in plaats van dat u het later ontdekt.
| Capability | Waarvoor het dient | Waar het geldt |
|---|---|---|
| Supabase | Database, authenticatie, bestandsopslag en de functies waarop de API draait | De primaire verwerker. Mediaopslag kan in plaats daarvan naar S3, Azure of GCS worden gewezen. |
| Cloudflare | DNS, TLS en het eigen domein waarop elke organisatie wordt geserveerd | Beëindigt TLS voor domeinen per organisatie; certificaten worden automatisch uitgegeven. |
| OpenAI | AI-generatie, tutoring en beoordelingsconcepten | Wordt alleen aangeroepen wanneer een verzoek het nodig heeft, na anonimisering. Anthropic en Google kunnen in plaats daarvan worden ingesteld, en een organisatie kan een eigen sleutel koppelen. |
| Resend | Transactionele e-mail — uitnodigingen, notificaties, resets | Een organisatie kan versturen vanaf een eigen subdomein, met een eigen sleutel. |
| PostHog | Productanalytics en gemaskeerde session replay | EU-regio. Cursistomgevingen worden niet opgenomen. |
De lijst die geldt, is die in de verwerkersovereenkomst, samen met het FERPA-addendum over privacy van leerlinggegevens voor scholen. We sturen beide liever vóór een gesprek dan erna.
WCAG 2.1 AA, en wat we niet hebben
Lurno is gebouwd volgens WCAG 2.1 AA. Geautomatiseerde axe-controles draaien in continuous integration, zodat een contrastfout of een ontbrekend label een build breekt in plaats van een release te halen. Toetsenbordpaden, zichtbare focus en voorkeuren voor beperkte beweging zitten in de componentenbibliotheek en worden niet per scherm achteraf toegevoegd. Het product verschijnt in het Engels, Frans, Arabisch en Italiaans, en het Arabisch is overal rechts-naar-links, geen vertaalde links-naar-rechts-weergave.
Wat we niet hebben is een VPAT of een externe toegankelijkheidsaudit, en we schrijven dat liever hier op dan het te verstoppen in het antwoord op vraag 47. Is een formeel conformiteitsrapport een voorwaarde voor aanschaf, breng het dan vroeg op — dat is een scopinggesprek, geen vinkje.
Beantwoord voordat u de vragenlijst hoeft te sturen
De pagina's waar deze steeds naar verwijst
Stuur ons de vragenlijst.
Het meeste is hierboven beantwoord. Neem de rest mee — residentie, bewaartermijnen, subverwerkers, de DPA — naar een gesprek, dan antwoorden we in dezelfde gewone woorden als hier.