Rekaro

Säkerhet

Så är stacken byggd, och här är den svag

Rekaro körs i containrar som bara innehåller Pythons standardbibliotek, bakom en Caddy som sätter samma Content-Security-Policy på varje svar. Sidan beskriver vad den konstruktionen skyddar mot, vad den inte skyddar mot, och vilka luckor som står öppna just nu.

Vilken sorts uppgift det här är

Varje rad på sidan är Rekaros egen uppgift om Rekaro, alltså nivå 4 i femnivåstegen. Ingen utomstående har granskat den, och ingen certifiering ligger bakom. Det vi kan göra i stället är att namnge filen bakom varje påstående, så att den som har åtkomst till datarummet slår upp den själv.

En säkerhetssida som bara listar styrkor är marknadsföring. Luckorna står längst ned med samma utrymme som allt annat, och de tre viktigaste är öppna i dag.

Nivå 4 · egen uppgiftKälla: rekaro-provenance, femnivåstegen. Läst 2026-08-16.

Noll tredjepartsursprung

Sidorna laddar ingenting från någon annans domän. Inga typsnitt från ett CDN, inget analysskript, ingen widget, ingen inbäddad betalningsruta. Policyn nedan gör det till ett fel i stället för till en vana, och den är identisk på alla ställen där den sätts.

Content-Security-Policy, direktiv för direktiv
Direktiv Värde Vad det stänger
default-src'none' Allt som inte uttryckligen tillåts nedan.
script-src'self' Inline-skript och varje skript från en annan värd.
style-src'self' Inline-stil, style-attributet och varje stilmall utifrån.
img-src'self' Spårpixlar och bilder från annat ursprung.
connect-src'self' Utgående anrop till någon annans API från webbläsaren.
frame-ancestors'none' Att sidan bäddas in i någon annans ram, alltså clickjacking.
base-uri'none' Att en injicerad base-tagg flyttar varje relativ länk.
form-action'self' Att ett formulär postas till en annan värd.

Priset är verkligt och vi betalar det med flit. Stripe Elements och inbäddad Checkout kräver att policyn öppnas för ett skript, en ram och ett anrop till tredje part, så betalningen sker i stället server till server med en omdirigering till Stripes egen sida. Det kostar något i konvertering. Det köper att ett intrång hos en leverantör vi inte har valt inte kan köra kod i en inköpares webbläsare.

Samma direktiv stänger också style-attributet, vilket är märkbart när något ska ritas. Mätaren på statussidan sätter därför sin bredd med ett dataattribut som stilmallen läser, inte med en beräknad bredd i JavaScript.

Nivå 4 · egen uppgiftKälla: launch/Caddyfile och build.py shell(). Läst 2026-08-16.

Standardbiblioteket som försvarslinje

Tjänsterna importerar bara Pythons standardbibliotek. Ingen pip, inget hjul att bygga, ingen beroendekedja att granska, och därmed inget paket som kan byta ägare en fredagskväll. Basimagen är python:3.13-alpine och den är vald för just det.

Det håller även när vi tar betalt. Stripes API är formkodad POST med en bearer-token, nåbar med urllib, och signaturkontrollen på webhooken är hmac och hashlib. Ingen av de sakerna behöver ett bibliotek.

Regeln är testad och inte påstådd: ett test listar varje toppnivåimport i tjänsten och failar på allt utanför en allowlist. Utan det testet glider ett tredjepartspaket in i en brådskande rättning och basimagen får en verktygskedja som ingen har granskat.

Nivå 4 · egen uppgiftKälla: rekaro-launch-service, punkt 1, och tjänsternas Dockerfile. Läst 2026-08-16.

Sessionen och kakan

Sessionskakans attribut och livslängd
Egenskap Värde Varför
Namn__Host-rekaro_konto Prefixet tvingar Secure och Path, och förbjuder Domain. Det stänger vägen där en underdomän sätter sessionskakan åt huvuddomänen.
HttpOnly Ett skript kan inte läsa kakan, alltså inte heller ett injicerat.
SameSiteLax Räcker inte som skydd på egen hand, se raden under tabellen.
Absolut livslängd14 dagar Taket, oavsett aktivitet.
Overksamhet24 timmar Glidande fönster ovanpå taket. Kakan skrivs om när mindre än halva tiden är kvar.
ÅterkallelseGeneration i kontot Token är tillståndslös men går att döda: generationen höjs vid lösenordsbyte, utloggning överallt, ändrad andra faktor och spärr.

SameSite Lax stoppar inte en formulärpost från en annan sajt som skickas som ren text med en JSON-formad kropp. Varje muterande rutt kräver därför två saker till: exakt rätt innehållstyp, annars avvisas anropet, och ett ursprung som matchar en tillåten värd. En rutt utan sidoeffekt kräver ingendera.

Nivå 4 · egen uppgiftKälla: launch/ARKITEKTUR.md, avsnitten Sessionen och CSRF. Läst 2026-08-16.

Grindarna som håller nere flödet

Hastighetsgrinden räknar på en monoton klocka och inte på väggklockan, eftersom en tidsjustering annars nollställer fönstret åt den som råkar ligga i det. Nyckeln är en HMAC över klientens adress med ett salt ur miljön, så att räknarna inte är en förteckning över vem som besökt sajten.

Adressen tas höger-mest ur vidarebefordringskedjan och aldrig vänster-mest. Vänster-mest är den post klienten själv får sätta, alltså en nyckel som angriparen väljer. Över 4 096 nycklar rensas tabellen, eftersom en dict som bara växer är en minnesläcka på en maskin med den här minnesbudgeten.

Botfiltret är ett dolt fält plus en ifyllnadstid på 1,5 sekunder, och båda utfallen svarar likadant. En avvisning med en felkod är ett orakel: boten justerar och kommer tillbaka. Ett vanligt svar utan sidoeffekt lär den ingenting.

Kroppen läses alltid färdigt, även före ett avslag. Det är det enda i listan som redan har orsakat ett produktionsfel: ett tidigt avbrott lämnade en oläst kropp som tolkades som början på nästa anrop, vilket gav växlande felkoder som såg ut som en ostadig grind och var ett protokollfel.

Nivå 4 · egen uppgiftKälla: launch/signup/server.py och intel/server.py, via rekaro-launch-service. Läst 2026-08-16.

Vad som loggas, och vad som inte gör det

Uppgift för uppgift
Uppgift Sparas Var, och hur länge
Besökarens IP-adressNej Bara en HMAC över den, som nyckel i hastighetsgrinden. Den ligger i minnet och försvinner när containern startar om.
Peer-adressen i containerloggenNej Standardloggraden är överskriven. Annars hamnar adressen i containerloggen med rotation, vilket är en personuppgift på drift.
Sessionskakans innehållNej Token verifieras och kastas. Ingen sessionstabell finns.
Signerat sekretessåtagandeJa Tidpunkt, namn, bolag och hashad e-postadress, i datarummets volym.
Hämtning av ett dokumentJa Tidpunkt, dokument och vilken åtkomstkod, i datarummets volym. Läses bara av admin-rollen.
Kontots egen säkerhetsloggJa Inloggningar och ändringar, synliga för kontot själv.

Nivå 4 · egen uppgiftKälla: launch/ARKITEKTUR.md, avsnitten accounts och dataroom. Läst 2026-08-16.

En användare per tjänst

Varje container kör som ett eget användar-id, ett per tjänst och utan luckor. Registret står i arkitekturfilen och löper i dag från 10001 till 10010. Numren ligger högt för att en container aldrig ska kunna förväxlas med ett värdkonto när en datavolym inspekteras utifrån.

Registret finns av ett konkret skäl. Två Dockerfiler delade en gång ut samma id, och båda kommentarerna påstod att numret var unikt. Två containrar på samma id delar identitet mot en volym, och då är isoleringen borta utan att något felar.

Ovanpå det kör varje tjänst med skrivskyddat filsystem, tömda förmågor, förbud mot nya privilegier och ett litet tmpfs för temporära filer. En tjänst som inte kan skriva utanför sin volym kan inte heller lämna något efter sig där.

Nivå 4 · egen uppgiftKälla: launch/ARKITEKTUR.md, uid-registret, och docker-compose.yml. Läst 2026-08-16.

Luckorna

De här står öppna i dag. Kolumnen längst till höger säger om vi har belägg för raden eller bara ett påstående, vilket är skillnaden mellan något du kan kontrollera i datarummet och något du får ta vårt ord på.

Kända luckor, med vad som krävs för att stänga dem
Lucka Vad den kostar Vad som krävs Belagt
Bilderna är taggade lokalt En ombyggnad skriver över den föregående. Det finns ingen återställningspunkt att rulla tillbaka till. Tagga varje bygge med sin commit och pinna taggen i en deploy-override. Ja, i docker-compose.yml
Volymsäkerhetskopian är inte löst Filerna i volymerna är enda kopian av all data, på ett kort i en enda maskin. Skrivningarna överlever ett strömavbrott. De överlever inte att kortet går sönder. En extern kopia, körd på schema, verifierad genom en återläsning. Klar före den första driftsättningen som rör pengar. Ja, i docker-compose.yml
SPF, DKIM och DMARC saknas på utskicksdomänen Vem som helst kan skicka post som utger sig för att komma från oss, och våra egna återställningsmejl hamnar oftare i skräpposten. Tre poster i DNS, och en publicerad uppslagning som visar att de ligger där. Nej, ingen uppslagning gjord
Tankstrecksgrinden läser bara genererad HTML Kod och stilmallar passerar okontrollerade, alltså precis de filer där tecknet oftast slinker in. Utöka grinden i bygget till att läsa hela trädet. Ja, i build.py

Listan är den vi kan namnge i dag och inte en fullständig förteckning. En lucka som upptäcks förs in här, på den öppna sidan, och inte i en ändringslogg som ingen läser.

Nivå 4 · egen uppgiftKälla: rekaro-launch-service, punkt 14, och överlämningen till den här sidan. Läst 2026-08-16.