Sikkerhet i Done HRM
Versjon 1.14 — 6. oktober 2026 · gjelder hr.done.ai i produksjon
Done HRM lagrer personalopplysninger for flere selskaper i samme system: kontaktinformasjon, ansettelsesvilkår, lønn, fravær med helseopplysninger, undersøkelsessvar og signerte kontrakter. Dette dokumentet beskriver hvordan disse dataene er beskyttet, på tre nivåer. Hver del begynner med hva som gjelder i praksis, og går deretter inn i hvordan det er løst teknisk.
1. Lagring og sikkerhetskopi
Primær lagring og behandling skjer i EU, kryptert både der data lagres og når de sendes. Databasen, applikasjonen og filene ligger i Frankfurt, og e-post sendes fra Nederland. Tre av leverandørene er amerikansk eide selskaper som kjører i EU-regioner. Får en av dem tilgang fra USA, for eksempel ved support, håndteres det etter personvernforordningens kapittel V. Ingen data lagres på brukernes egne maskiner utover en informasjonskapsel for innlogging. Filer som kontrakter og attester ligger aldri på offentlige adresser: hver nedlasting går gjennom systemet, som først sjekker hvem som spør.
De mest følsomme opplysningene, lønn, er i tillegg kryptert med en egen nøkkel som ikke ligger i databasen. Skulle noen få tak i en kopi av databasen, er lønnsfeltet uleselig uten den nøkkelen.
Tjenesten sender ikke personopplysninger til noen leverandør av kunstig intelligens. Funksjonene som bygger på kunstig intelligens er sperret på servernivå i produksjon. Systemet lagrer heller ingen passord — innlogging skjer via Done ID, som er felles for alle Done-produkter, og som også håndterer tofaktor og passordpolicy.
| Område | Slik er det løst |
|---|---|
| Database | PostgreSQL hos Neon i regionen eu-central-1 (Frankfurt). Kryptert i hvile og under transport; tilkoblinger krever TLS med kanalbinding. Applikasjonen kjører uten superbrukerrettigheter (se del 2). |
| Sikkerhetskopi | To uavhengige lag. Neon har punkt-i-tid-gjenoppretting for de siste 7 dagene. I tillegg tas en daglig kopi av hele databasen, som krypteres (RSA-4096 og AES-256-GCM) og lagres privat hos Vercel, altså hos en annen leverandør enn databasen. Bare den offentlige nøkkelen finnes i driftsmiljøet, så kopiene kan ikke leses av noen med tilgang dit. 28 daglige kopier beholdes, eldre slettes automatisk. Innloggingssesjoner og tokener er ikke med. Jobben varsler driftsansvarlig hvis den feiler eller kopien ser feil ut. Gjenoppretting er testet. Filene i fillageret (dokumenter, CV-er, bilder) speiles hver natt til et eget, kryptert område med samme nøkkel; en fil som slettes beholdes der i 28 dager før den forsvinner. Speilet ligger i samme konto som filene, så det beskytter mot feil i programvaren og utilsiktet sletting, ikke mot at hele lageret går tapt. |
| Lønn | Feltkryptering med AES-256-GCM. Nøkkelen ligger som hemmelig miljøvariabel, ikke i databasen. Verdien dekrypteres kun i serverkode, og bare når brukerens rolle gir rett til å se den. I databasedumper er kolonnen uleselig. |
| Filer | Vercel Blob med privat tilgang. Ingen offentlige adresser. Nedlasting går via serverruter som verifiserer sesjon, selskap og dokumenttilgang før filen strømmes. Stier inneholder selskaps-ID og valideres mot aktivt selskap. Lagrene ligger i Frankfurt (Vercel-region fra1), bekreftet i Vercels administrasjonsgrensesnitt 30. september 2026; regionen kan ikke endres etter at et lager er opprettet. Underliggende lagring er Amazon S3. Vercel mellomlagrer filer i leveringsnettet sitt i inntil én måned; filene hentes bare av applikasjonen, som kjører i Frankfurt. |
| Uttrekk | Nedlasting av dokumenter og eksport til fil er begrenset per bruker: 120 nedlastinger og 20 eksporter per ti minutter. Invitasjoner er begrenset per selskap. Treff på grensen telles som sikkerhetshendelse og kommer med i ukesrapporten. En konto på avveie kan dermed ikke tømme et selskap på noen minutter uten at det merkes. |
| E-post | Lettermint B.V. i Nederland. Varsler inneholder bare emne og lenke, aldri fraværstype, helseopplysninger eller lønn. Innholdet leses i tjenesten etter innlogging. Leverandøren sletter sendingslogger etter 28 dager. |
| Autentisering | Done ID (Keycloak) via OpenID Connect med PKCE. Applikasjonen lagrer ingen passord. Sesjonen ligger i en httpOnly-cookie med SameSite=Lax og Secure, gyldig i 30 dager. |
| Hemmeligheter | Alle nøkler og tilkoblingsstrenger ligger kryptert som miljøvariabler. Databasepassordet for applikasjonen, lønnsnøkkelen og e-postnøkkelen er markert Sensitive, som betyr at de ikke kan leses ut igjen — heller ikke av administratorer. Den private nøkkelen til sikkerhetskopiene finnes ikke i driftsmiljøet i det hele tatt. |
| Innkommende integrasjoner | E-signering (Done Signatory) leverer resultater via webhook som verifiseres med HMAC over hele meldingen, sammenlignet i konstant tid. Planlagte jobber krever en hemmelig bærer-token. |
| Nettlesersikkerhet | Sikkerhetshoder på alle svar: Content-Security-Policy (kun egen origin for skript, bilder og tilkoblinger; innramming forbudt), HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy og Permissions-Policy. Opplasting godtar kun PNG, JPG, WEBP og GIF; SVG avvises fordi den kan inneholde skript. Uautentiserte innganger er begrenset per IP. |
| Drift og måling | Ubehandlede serverfeil og feil i planlagte jobber logges strukturert, varsles på e-post og telles i en egen hendelsestabell sammen med alle avviste forsøk og hver jobbkjøring. En ukentlig driftsrapport oppsummerer tallene. Avhengigheter med kjente sårbarheter kontrolleres i CI på hver endring. En røyktest utenfra kontrollerer etter hver utrulling og fem ganger daglig at tjenesten svarer, at databasen er nåbar og at innloggingskravet holder. Feiler den, varsles driftsansvarlig uavhengig av om tjenesten selv er oppe. |
| Revisjonslogg | Alle skrivende operasjoner mot personopplysninger logges med hvem, når, hvilken rad og hva som endret seg: ansatte, roller og tilgang, fravær, timer, mål, samtaler, utstyr, onboarding, ferdigheter, dokumenter, arkivering, anonymisering og sletting, samt eksport av lønnsliste. Fritekst som notater og begrunnelser logges aldri — bare at de ble endret. En statisk kontroll i byggekjeden stopper bygget hvis en ny funksjon glemmer loggen. Loggen er kun tilføybar for applikasjonen: databaserollen har ikke rett til å endre eller slette rader i den. |
| Sletting og personvern | Tre trinn for ansatte: avslutt ansettelsen, arkiver (skjult, reversibelt) og anonymiser (uopprettelig — alt som er om personen nulles, kontoen frakobles; lønns- og bokføringsgrunnlag beholdes). Dokumenter og undersøkelser som slettes ligger 30 dager i en papirkurv der administrator kan gjenopprette dem, og slettes deretter for godt — fil inkludert. Lagringstidene håndheves av en daglig jobb. Hver ansatt kan eksportere egne data fra Min profil. Når et kundeforhold avsluttes, slettes selskapet med alle rader og filer i én kontrollert operasjon: tørrkjøring først, deretter skarp kjøring som krever et åpent nødtilgangsvindu med oppsigelsen som instruks, og som kontrollerer at ingenting står igjen. Kunden får skriftlig kvittering, og sikkerhetskopiene ruller ut av seg selv innen 28 dager. |
2. Skillet mellom selskaper
Alle selskaper deler ett system og én database. Skillet mellom dem er absolutt: for et selskap finnes de andre selskapene ikke. Det er ikke et filter som skjuler dem i skjermbildet — radene blir aldri hentet.
Dette håndheves på to uavhengige nivåer. Koden filtrerer alltid på selskap. I tillegg har databasen selv regler som gjør det samme, uavhengig av hva koden ber om. Skulle en utvikler glemme filteret i en fremtidig endring, gir databasen null rader, ikke alle selskapers rader. En glemt linje blir dermed en synlig feil, ikke en usynlig lekkasje.
Konsern og allianser er støttet: en kjedeadministrator i morselskapet kan se inn i datterselskapene. Innsynet går bare nedover. Datterselskaper ser aldri hverandre eller morselskapet.
Applikasjonslaget
- Datamodell. Hver forretningstabell har en obligatorisk kolonne for selskap. Ingen tabell deles mellom selskaper uten den.
- Sesjonsoppløsning. Ved hver forespørsel slås brukerens medlemskap opp. Listen over tilgjengelige selskaper er egne medlemskap pluss hele undertreet av datterselskaper hvis brukeren er kjedeadministrator. Aktivt selskap godtas kun hvis det finnes i denne listen.
- Selskaps-ID kommer aldri fra klienten. Serverkoden leser den fra sesjonen og legger den i hver spørring og hver innsetting. Sender nettleseren med en selskaps-ID, ignoreres den.
- Oppslag på ID. Alle oppslag krever både rad-ID og selskaps-ID. En gyldig ID fra et annet selskap gir «ikke funnet», ikke data.
- Invitasjoner. Tilfeldig token på 32 tegn, gyldig i sju dager. Aksept krever at innlogget e-postadresse er identisk med den inviterte. Tokenet forbrukes atomisk, så to samtidige klikk gir ett medlemskap.
Databaselaget
PostgreSQL Row-Level Security er aktivert og tvunget på alle 35 tabeller som inneholder selskapsdata. Policyen er identisk på hver tabell og gjelder både lesing og skriving. Mangler selskapskonteksten, blir sammenligningen usann: null rader lesbare, all skriving avvist. Databasen feiler lukket.
- Applikasjonsrollen. Applikasjonen kobler til som en rolle opprettet uten rett til å omgå Row-Level Security, uten superbrukerrettigheter og uten rett til å endre skjema. Det er avgjørende: databaseeieren omgår slike regler automatisk. De få systemoppslagene som må se på tvers av selskaper bruker en egen systemrolle — heller ikke den er eier. Eier-rollen er forbeholdt mennesker og migrasjoner, og applikasjonen nekter å starte hvis den finner seg selv som eier.
- Kontekst per transaksjon. Selskaps-ID settes transaksjonslokalt, så konteksten kan ikke lekke mellom forespørsler — heller ikke gjennom tilkoblingspoolen.
- Unntak, eksplisitt. Noen få operasjoner er ekte kryss-tenant og kan umulig ha selskapskontekst før oppslaget: kobling av en nyinnlogget bruker til sin ansattrad, oppslag av en invitasjon på token, mottak av signeringsresultat, de daglige jobbene og den daglige sikkerhetskopien. Disse bruker en egen, tydelig merket systemtilkobling, og bruken overvåkes av en statisk test.
3. Tilgang innad i et selskap
Innenfor et selskap ser ikke alle alt. En vanlig ansatt ser kollegakatalogen med navn, stilling, telefon og e-post, og sine egne data. En leder ser i tillegg full informasjon om sine egne ansatte, og bare dem. Superadministrator ser alt i selskapet.
Særlig følsomme opplysninger har egne regler. Kolleger ser at noen er borte, aldri hvorfor: fraværstype er en helseopplysning og vises kun for den det gjelder, nærmeste leder og administrator. Lønn vises aldri for andre enn den det gjelder, administrator, og eventuelt nærmeste leder hvis selskapet har slått det på.
Hva en leder «eier» avgjøres av organisasjonskartet, ikke av avdeling eller tittel. Rekkevidden er hele undertreet av ansatte som rapporterer til lederen, direkte eller indirekte.
| Rolle | Rekkevidde |
|---|---|
| Ansatt | Egen profil, eget fravær, egne timer, egne dokumenter. Kollegakatalog med kontaktfelt. Ser at kolleger er fraværende, uten type. Håndbøker, nyheter, og samtaler og mål de selv er part i. |
| Leder | Som ansatt, pluss full detalj for eget undertre i orgkartet: ansattkort, fraværsdetaljer, godkjenning av fravær og timer, onboarding og offboarding. Dokumenter og lønn for undertreet kun hvis administrator har slått det på per leder. Kan ikke åpne sidestilte ledere eller egne overordnede. |
| Superadministrator | Alt i selskapet: lønn, fraværstyper, roller, invitasjoner, innstillinger, revisjonslogg, anonymisering og arkivering. |
| Kjedeadministrator | Superadministrator i morselskapet og i alle datterselskaper. Bytter selskap fra toppmenyen. Datterselskaper ser ikke oppover eller sidelengs. |
Håndheving
- Rekkevidde beregnes på serveren, per forespørsel. Samme beregning brukes både til å vise (rader utenfor rekkevidde hentes ikke) og til å handle (lagring avvises).
- Rolle og orgkart er uavhengige, med vilje. Å stå som nærmeste leder i orgkartet gir ingen rettigheter uten lederrollen i systemet. Helseopplysninger er eksplisitt rollestyrt på toppen av orgkart-rekkevidden.
- Vern i selve spørringen, ikke i skjermbildet. For en vanlig ansatt henter spørringen kun katalogfeltene. Fødselsdato, lønn, ansettelsesstatus og startdato forlater aldri databasen for den brukeren.
- Synlighet per dokument. Én regel brukes overalt der dokumenter leses: nedlasting, dokumentlisten, ansattkortet, Min profil og den ansattes egen dataeksport. Bare administrator kan endre synlighet, og endringen logges.
- Undersøkelser er enten anonyme eller åpne, og det står tydelig på svarskjemaet før man svarer. I anonyme undersøkelser lagres svarene uten kobling til person, og navn hentes aldri sammen med svar — heller ikke i eksport.
4. Hvordan vi vet at det virker
Beskrivelsene over er ikke bare intensjon. Den statiske vakten kjører automatisk foran hvert bygg; databasetestene kjøres i CI mot en midlertidig databasegren på hver endring, og aldri mot produksjon, siden de skriver rader.
- «Feil selskap»-test. Logger inn som administrator i ett selskap og kaller hver side, handling og API-rute med IDer fra andre selskaper, 81 kall i alt. Kun et eksplisitt avslag teller som bestått; et krasj teller som feil. Testen kjøres med applikasjonsrollen, altså nøyaktig som produksjon.
- Håndheving i databasen. For hver beskyttet tabell: uten selskapskontekst gir databasen null rader, med kontekst kun eget selskap, og forsøk på å flytte en rad til et annet selskap avvises av databasen selv.
- Statisk vakt. Stopper deployen hvis en fil får nye databasespørringer mot selskapstabeller uten selskapsfilter, ut over en gjennomgått og begrunnet liste.
- Synlighetsregelen. Egen test for dokumentsynlighet: administrator, opplaster, navngitt person, ansatt og leder, med og uten synlighet.
- Oppstartskontroll. Serveren nekter å starte i produksjon hvis databaserollen den kobler til med kan omgå Row-Level Security, eller hvis en påkrevd hemmelighet mangler.
- Løpende måling. Hvert avviste forsøk telles: avvist rolle, forsøk på tvers av selskap, nektet dokument-, rapport- og bildetilgang, feilet innlogging, invitasjon åpnet av feil mottaker, avvist signatur og feil jobb-token. Tallene oppsummeres ukentlig.
En ekstern gjennomgang i september 2026 bekreftet funnet fra vår egen kontroll: fra mai til 8. september koblet applikasjonen til som databaseeier, slik at policyene ikke ble evaluert. Perioden er vurdert mot personvernforordningens artikkel 33 i et eget notat. Oppstartskontrollen over gjør at feilen ikke kan gjenta seg stille.