Version 1.3 · opdateret 24. august 2026
Sikkerhedsdokument
Skrevet til den, der skal sige god for, at virksomhedens medarbejdere kan bruge ordningen. Det beskriver, hvad der faktisk er bygget — ikke hvad der er planlagt — og hvad vi ikke kan garantere. De steder er de vigtigste på siden.
De fem påstande, resten af dokumentet skal bære
- Vi modtager aldrig indberetterens IP-adresse — det er afgjort i webserverens konfiguration, ikke i applikationens kode.
- Indberetninger krypteres med kundens eget nøglepar, før de rører databasen. Et databasedump alene er værdiløst.
- Hver kunde har sin egen database. Adskillelsen er ved
databasegrænsen, ikke ved et
where. - Kun den upartiske enhed kan læse sager. Ejeren kan lukke ordningen, men ikke åbne en indberetning.
- Produktion og udvikling er to adskilte maskiner. De deler hverken styresystem, databaseinstans eller kørselsmiljø.
- En indberetning kan ikke slås op på en medarbejder. HR-modulerne ligger i samme database, men sagsskemaet har ingen kolonne, der peger på en person — og det håndhæves af en test, der læser det kørende skema.
Vejen en indberetning tager
medarbejderens browser
│ HTTPS (TLS 1.3)
▼
Cloudflare ── terminerer TLS, ser IP som transportled
│ krypteret tunnel (udgående forbindelse fra vores server)
▼
webserver ── FJERNER CF-Connecting-IP, X-Forwarded-For, X-Real-IP,
│ True-Client-IP og sætter REMOTE_ADDR = 127.0.0.1
▼
applikationen ── ser ingen IP-adresse. Ingen adgangslog på kundens vært.
│
├─ forsegler teksten med KUNDENS OFFENTLIGE nøgle
│ (processen kan skrive, men ikke læse)
▼
kundens egen database ── indberetningen ligger som ciffertekst
Serveren har ingen port åben mod internettet.
Forbindelsen etableres udefra og ind gennem en tunnel, vores egen
server selv åbner, og webserveren lytter kun på maskinens interne
loopback-adresse. Databasen lytter slet ikke — hverken på nettet
eller på loopback: den er startet med skip-networking,
og applikationen taler med den over en unix-socket.
Præcist sagt: den eneste lyttende port ud over loopback er SSH, og
den er kun nåbar fra vores eget interne netværk — ikke fra
internettet. Det er den vej, vi selv administrerer maskinen. Påstanden
kan efterprøves med ss -tlnp, og det er meningen, at den
skal kunne det.
IP-adressen fjernes uden for applikationen
En politik om ikke at logge IP-adresser holder, indtil nogen tilføjer en linje kode. Derfor er værnet flyttet ud af applikationen og ind i webserveren: headerne nulstilles, før PHP-processen startes. Applikationen kan ikke gemme det, den aldrig modtager.
Værnet dækker hele kundens vært — ikke kun indberetningssiden. Det er en skærpelse fra 15. august 2026: et besøg på whistleblowerpolitikken kl. 23.14 er næsten lige så koblende som selve indberetningen. Af samme grund er adgangsloggen slået fra for hele værten.
Grænsen: Cloudflare ser adressen som transportled,
og indberetterens arbejdsgiver kan se på sit eget netværk, at der er
oprettet forbindelse til <firma>.krydsaf.dk. Vi
kan fjerne vores egen viden, ikke omverdenens. Skal en indberetter
være usynlig for sin arbejdsgivers netværk, skal hun indberette fra
en privat forbindelse — det står på indberetningssiden.
Hvad krypteringen beskytter mod — og hvad den ikke gør
Hver kunde har ét nøglepar (libsodium, crypto_box_seal).
Den offentlige nøgle ligger læsbart i databasen; den private ligger
samme sted, men indpakket under en hovednøgle, der kun findes i
serverens miljø — aldrig i databasen og aldrig i en sikkerhedskopi af
databasen.
Den proces, der tager imod en indberetning, har kun den offentlige nøgle. Den kan skrive en indberetning uden at kunne læse den. En angriber, der overtager præcis den kodesti, får intet med.
Bilag forsegles hver for sig med deres egen nøgle, og filnavnet forsegles også: opsigelse-lars-hansen.pdf er i sig selv en oplysning. Billeder gen-kodes ved modtagelsen, så EXIF — herunder GPS-position og enhedsnavn — forsvinder.
Vi påstår ikke zero-knowledge. Vores servere kan åbne en indberetning, fordi den upartiske enhed skal kunne læse den, og fordi kunden ellers ville miste alt ved et tabt kodeord — og dermed bryde sin egen lovpligt om feedback inden tre måneder. Vil man have et system, hvor leverandøren teknisk ikke kan læse med, findes det — men prisen er, at kunden kan miste sagerne uigenkaldeligt. Vi har valgt det andet, og vi siger det højt frem for at bruge ordet "end-to-end" om noget, det ikke er.
Kunder deler ikke database — og drift deler ikke maskine
Hver kunde har sin egen database. Adskillelsen er altså ved
databasegrænsen, ikke ved en where-betingelse, en
udvikler kan glemme. Databasens navn udledes af kundens id og
valideres mod et fast mønster, før det bruges — der er ingen vej fra
et formularfelt til et databasenavn.
Applikationens databasebruger kan kun røre sine egne databaser. Det er afprøvet, ikke antaget: brugeren kan oprette og slette en kundedatabase, kan ikke oprette andet, og kan i det hele taget kun se sin egen.
Produktion og udviklingsmiljø er to adskilte maskiner med hver sit styresystem, hver sin databaseinstans og hver sin tunnel. Udviklingsmiljøet indeholder ingen kundedata, ligger bag adgangskode og er mærket, så søgemaskiner ikke indekserer det.
Kun den upartiske enhed kan læse — også hos os
Rollerne er faste: ejer, administrator, den upartiske enhed (sagsbehandler) og medarbejder. Kun sagsbehandleren kan læse indberetninger. Ejeren administrerer ordningen og kan lukke den, men kan ikke åbne en sag — netop fordi ejeren selv kan være genstand for en indberetning. Det er whistleblowerlovens § 11 sat i kode og ikke i en vejledning.
Sagsbehandlere skal bruge tofaktor. Kravet hænger på rollen og kan ikke slås fra — heller ikke af en administrator. Mister en sagsbehandler sin telefon, nulstilles tofaktoren af kundens egen administrator. Der findes ingen vej i produktet, hvor vi kan gøre det for jer — og det er med vilje: en leverandør, der kan slå tofaktor fra på en sagsbehandler-konto, kan reelt læse kundens indberetninger. (Vores driftspersonale har serveradgang og kan derfor teknisk ændre det i databasen; se afsnittet nedenfor. Forskellen er, at det ikke er en funktion, men et brud på vores egen instruks.)
Udpeges en ny sagsbehandler, får ejeren og de eksisterende sagsbehandlere besked. Det er kundens egen § 11-beslutning, hvem der udpeges — men den skal ikke kunne ske ubemærket.
Grænsen: vores driftspersonale har adgang til serverne og dermed teknisk mulighed for at åbne en indberetning. Det er ikke anderledes end hos enhver anden leverandør, der ikke er zero-knowledge — men det skal stå her frem for at blive opdaget. Adgangen er reguleret i databehandleraftalen, sker kun efter kundens instruks, og de nøgler, der skal bruges, ligger ikke i databasen.
Navngivne medarbejdere ligger i samme database som de anonyme sager
Indtil august 2026 indeholdt kundens database kun anonyme indberetninger. Det gør den ikke længere. Aktiverer kunden HR-modulerne, ligger medarbejderregister, arbejdstid, ferie og fravær, sygefraværets frister, barsel, arbejdspladsvurdering med kemisk instruktion, ansættelsesbeviser, firmapolitikker med kvitteringer, on- og offboarding, adgangsregister, certifikater og udstyr, MUS og personalesager samt artikel 30-fortegnelsen i samme database som sagerne. Den til enhver tid gældende liste står i databehandleraftalens § 2 — den her er skrevet ud, fordi en henvisning ikke er en oplysning. Det er en ægte ændring af risikobilledet, og den skal stå her frem for at blive opdaget.
Det, der står mellem de to, er skemaet. Sagernes tabeller har en hvidliste over præcis de kolonner, de må have — ikke en liste over forbudte navne, men en liste over tilladte. Der findes ingen kolonne, der peger på en person, og ingen fremmednøgle mellem de to verdener. En ny tabel eller en ny kolonne i sagsskemaet gør vores testsuite rød, indtil nogen har taget stilling, og kontrollen læser det kørende skema — ikke en beskrivelse af det. Det er samtidig grunden til, at den eneste identitet i hele sagsskemaet er den BEHANDLER, der har svaret: § 11 kræver, at det kan dokumenteres, hvem den upartiske enhed var.
Rollehegnet går begge veje. Sagsbehandleren — den upartiske enhed — har ingen adgang til medarbejderregistret. Kunne hun slå navne op, kunne hun krydse et register med en indberetning. Omvendt kan ejeren og administratoren se HR-modulerne, men ikke en eneste sag.
Grænsen, sagt lige ud: HR-oplysningerne er ikke krypteret felt for felt, sådan som indberetningerne er. De beskyttes af databaseadskillelsen, af rolleadgangen, af TLS i transit og af krypterede sikkerhedskopier — men et databasedump fra HR-siden er læsbart, hvor et dump af sagerne er værdiløst uden nøglen. Det er et bevidst valg: en krypteret lønoplysning kan ikke sorteres, søges eller lægges sammen, og et modul, der ikke kan det, er ikke et HR-modul. Vi siger det hellere end at lade ordet "kryptering" dække hele produktet.
Og en oplysning mere, end de fleste nævner: sygefraværsmodulet registrerer, om der er indgået en § 56-aftale, og tæller sygedage efter funktionærlovens 120-dages-regel. Ingen af delene indeholder en diagnose, men begge siger noget om helbredet og er derfor følsomme oplysninger efter artikel 9. De står som sådan i databehandleraftalens § 2.
Lovens frister er en maskine, ikke en huskeseddel
Kvittering inden 7 dage og feedback inden 3 måneder er indbygget: systemet varsler, eskalerer ved overskridelse, og en sag kan ikke afsluttes uden den feedback, loven kræver. Ledelsen får besked om antallet af overskredne frister — aldrig om hvilke sager, for et sagsnummer i ledelsens indbakke er i sig selv en oplysning.
Når opbevaringsfristen udløber, slettes indholdet automatisk, mens det lovpligtige register (§ 23) bevares uden en linje af indholdet. Fristen kan forlænges med en begrundelse, der gemmes på sagen — og kun forlænges. Den kan ikke forkortes gennem brugerfladen, og kun afsluttede sager kan overhovedet få en frist.
Lukkes ordningen, destrueres data og krypteringsnøgler. Derefter kan indholdet ikke genskabes — heller ikke af os.
Grænsen, præcist: sikkerhedskopier roterer på 14 dage, både på serveren og uden for den. Indtil de 14 dage er gået, findes en kopi, hvor nøglen stadig ligger ved siden af den krypterede tekst. Vi har taget produktionsserveren ud af den almindelige billed-backup, netop for at de 14 dage er sande og ikke i virkeligheden fem uger. Sikkerhedskopierne er krypteret til en nøgle, serveren selv ikke har: den kan lave dem, men ikke læse dem.
Hvad vi gør for at holde os selv i ørerne
- Hele den automatiske testsuite kører før hver udrulning. Er den rød, når koden ikke serveren.
- Kendte sårbarheder i afhængigheder tjekkes i samme skridt og afbryder udrulningen.
- Sikkerhedsheaderne efterprøves mod det kørende site ved hver udrulning — ikke mod konfigurationen. Fejler tjekket, får vi besked.
- En gennemspilning i en rigtig browser køres ved hver ændring af flowet — ikke ved hver udrulning. Den går hele vejen: opret ordning → udpeg enhed → tofaktor → indberet med bilag → følg sagen → behandl → hent bilaget ud igen → afslut → eksportér, plus de negative prøver: forkert kode fem gange, ejeren afvist på sagsfladen, og et billede, der er lille som fil men urimeligt stort at pakke ud.
- Sikkerhedsgennemgang af hele kodebasen — ikke kun af det, der er ændret. Seneste kørsel: 24. august 2026.
Gennemgangen den 24. august 2026 fandt ingen udnyttelige sårbarheder i selve programkoden. Den fandt til gengæld fem forhold i driften — i webserverens og PHP's opsætning, i filrettigheder og i, hvilken bruger de natlige kørsler kørte som. Tre af dem betød, at et bilag ikke kunne afleveres i den størrelse, vi lover. Alle fem er rettet, og hver enkelt har fået et hegn, der gør testsuiten rød, hvis opsætningen skrider igen. Lektien er værd at gengive: fejlene lå ikke i koden, men i de tal og rettigheder, koden forudsætter — og dem kunne ingen af de dengang 758 tests se. Derfor uploader browser-gennemspilningen nu en rigtig fil.
Gennemgangen i august 2026 fandt seksten forhold, som alle er rettet. De har hver deres egen test, så de ikke kan komme tilbage — på nær to, der kun kan fanges ved at køre systemet: webserverreglen og browserpolitikken nedenfor. Dem dækker henholdsvis header-tjekket ved hver udrulning og gennemspilningen i browseren. To af dem er værd at kende, fordi de siger noget om, hvorfor vi gør det her: indberetningssiden svarede reelt ikke i produktion på grund af en webserverregel, og vores egen sikkerhedspolitik i browseren afbrød oprettelsen af nye ordninger. Ingen af delene kunne ses i koden — kun ved at prøve.
Det vi endnu ikke har
Dette dokument er vores egen beskrivelse. Det er ikke en revisorerklæring, og vi kalder det ikke en. Vi har ikke ISAE 3000, vi har ikke en ekstern penetrationstest, og vi har ikke en ISO-certificering. Det er på køreplanen, ikke i skabet.
Får I brug for en uafhængig vurdering nu, er I velkomne til selv at få foretaget en test — skriv til os først, så aftaler vi rammen.
Hvem er ansvarlig for hvad
I er dataansvarlige for indberetningerne. BORN DIGITAL ApS er databehandler og behandler kun på jeres instruks. Rammen er databehandleraftalen, som indgås ved oprettelsen.
I udpeger den upartiske enhed. KrydsAf leverer struktur og værktøj — aldrig en juridisk vurdering af en indberetning. Systemet kan forberede: sammenfatte, kategorisere og holde styr på frister. Beslutningen er jeres enheds.
Underdatabehandlere og deres roller står i databehandleraftalen. Ændres de, varsles I efter aftalens regler.
Fandt I noget?
Har I opdaget et sikkerhedsproblem, så skriv til [email protected]. Vi svarer, vi retter, og vi skriver tilbage hvad vi gjorde. Vi bestrider ikke fund, vi ikke kan gendrive.