Härdning av hela TÜV-revisionen
Tio av tolv fynd stängda, två reducerade med skälen utskrivna. Varje
stängning prövas av integrationssviten mot riktig Postgres — 144
kontroller, upp från 100.
T-1 Händelsenyckeln är nu (arende_id, id). Det finns inget delat
namnrum kvar att ockupera, så attacken saknar yta i stället för att
vara mildrad. Inom ett ärende bedöms en kollision på klientens
avtryck — en hash av det kanoniserade innehåll klienten skickade,
taget före serverns egna fält och före krypteringen, eftersom
varken raden eller nyttolasten går att jämföra. Identiskt innehåll
är fortfarande idempotent; samma id med annat innehåll ger 409 och
skriver ingenting alls, inte heller resten av satsen. Samma attack
en nivå upp — ärende-id är lika förutsägbart — stängs separat: ett
id som ägs av en annan organisation ger 409 i stället för att tyst
låta bli, vilket tidigare lämnade offrets ärende oskapat och varje
senare synk svarande 404.
T-2 Mätdonet slås upp i registret. Beteckning och kalibrering HÄRLEDS
därifrån och skriver över det klienten skickade. Okänt mätdon ger
400. Utgånget avvisas inte — mätningen gjordes — men registrets
datum följer med, så graderingen faller på registrets uppgift. Ett
mätvärde utan mätdon får sina påstådda uppgifter borttagna.
T-3 Reducerad, inte stängd. Personnycklarna kuverteras under en
huvudnyckel utanför databasen. En återställd dump ger nycklar som
inte öppnas — verifierat genom att starta om utan huvudnyckeln. Vad
som återstår står utskrivet: en backup tagen FÖRE en radering, plus
huvudnyckeln, återställer fortfarande uppgifterna.
Därtill: gallringen verkställs nu av ett eget jobb och grupperar på det
blindade fordonsindexet så att en delad nyckel inte gallras för tidigt
(T-4) · AI-avlästa mätvärden bär härkomst (T-5) · bcrypt kostnad 12
(T-6) · åtkomstloggen och raderingsregistret är append-only, med en
smalare regel för personnycklarna som måste kunna förstöras (T-7) ·
åtkomstloggen dokumenterad (T-8) · ett externt regelpaket utan signatur
spärrar avslut (T-9) · CORS faller inte längre öppet (T-10) · exp krävs
i token (T-11) · react-router 7 (T-12, med den kvarvarande avvikelsen
motiverad).
Den motspelande hyresgästen som revisionen efterlyste finns nu som
testform och körs i CI.
---- Vad härdningen själv avslöjade -----------------------------------
Två av rättelserna var kortvarigt fel på samma sätt som fynden, och
bägge fångades bara av att jag försökte bevisa dem:
T-7-testet var grönt mot en TOM tabell. En radnivåtrigger har inga
rader att fyra på, så delete lyckades och kontrollen mätte ingenting.
T-6-testet påstod anropet, inte kostnaden. toContain("gen_salt('bf')")
hade accepterat kostnad 6 för evigt — och gjorde det, så länge det
fanns.
Bägge är mönstret revisionen namngav: en kontroll som är riktig i sina
egna termer och oprövad vid sin gräns. Det gäller tester lika mycket som
kod.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -44,11 +44,20 @@ describe("AI-orkestern", () => {
|
||||
"/api/auth/registrera",
|
||||
"/api/arenden",
|
||||
String.raw`\/api\/delad\/`,
|
||||
"gen_salt('bf')",
|
||||
"JWT_SECRET",
|
||||
]) {
|
||||
expect(plattform).toContain(bit);
|
||||
}
|
||||
|
||||
// Lösenordshashens KOSTNAD, inte bara att bcrypt används (TÜV T-6).
|
||||
// pgcryptos gen_salt('bf') utan argument ger kostnad 6 — 2^6 = 64
|
||||
// varv, mot dagens golv på 2^10. Testet var tidigare skrivet på
|
||||
// anropet och hade därför godkänt den svaga varianten för alltid.
|
||||
const kostnader = [...plattform.matchAll(/gen_salt\('bf'(?:,\s*(\d+))?\)/g)].map((m) =>
|
||||
Number(m[1] ?? 6),
|
||||
);
|
||||
expect(kostnader.length).toBeGreaterThan(0);
|
||||
for (const k of kostnader) expect(k).toBeGreaterThanOrEqual(12);
|
||||
// API:t exponerar medvetet inga update/delete-operationer.
|
||||
expect(plattform).not.toMatch(/\b(update|delete)\s+felsokning_handelser/i);
|
||||
// Multi-tenant: all ärendedata är organisationsknuten.
|
||||
|
||||
@@ -111,6 +111,14 @@ export type Handelse =
|
||||
matdonId?: string;
|
||||
matdonBeteckning?: string;
|
||||
matdonKalibreradTill?: string;
|
||||
/**
|
||||
* Härkomst när värdet inte skrevs in av teknikern själv — en
|
||||
* modelltolkad instrumentavläsning, eller en importerad protokollrad
|
||||
* (TÜV T-5). Ett bekräftat maskinläst värde är riktigt men inte
|
||||
* spårbart utan det här: rapporten ska kunna svara på hur siffran
|
||||
* kom dit, inte bara vilken den blev.
|
||||
*/
|
||||
kalla?: string;
|
||||
}
|
||||
| { typ: "hypotes"; text: string; niva: Exclude<Tillforlitlighet, "hog"> }
|
||||
| ({ typ: "foto"; beskrivning: string } & Bilaga)
|
||||
|
||||
@@ -102,9 +102,16 @@ export function evidensposter(arende: Arende): Evidenspost[] {
|
||||
// att en okalibrerad mätning väger som en kalibrerad är precis den
|
||||
// sortens obefogade säkerhet produkten finns för att undvika.
|
||||
const spårbar = Boolean(h.matdonId) && kalibreradVid(h.matdonKalibreradTill, post.tidpunkt);
|
||||
const märkning = h.matdonBeteckning
|
||||
? `${h.matdonBeteckning}${spårbar ? "" : " — kalibrering saknas eller utgången"}`
|
||||
: "instrument ej angivet";
|
||||
const märkning = [
|
||||
h.matdonBeteckning
|
||||
? `${h.matdonBeteckning}${spårbar ? "" : " — kalibrering saknas eller utgången"}`
|
||||
: "instrument ej angivet",
|
||||
// Härkomsten står i rapporten, inte bara i loggen: den som läser
|
||||
// ska se att siffran är maskinläst utan att gräva (TÜV T-5).
|
||||
h.kalla,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
lagg(
|
||||
post,
|
||||
"mätvärde",
|
||||
|
||||
@@ -1734,7 +1734,19 @@ function SnabbDokumentation({
|
||||
// strukturerad data ersätter aldrig originalevidensen.
|
||||
skicka({ typ: "foto", beskrivning: `Instrumentavläsning (${avlasning.tolkning.instrumenttyp})`, dataUrl: avlasning.foto });
|
||||
for (const v of avlasning.tolkning.varden) {
|
||||
skicka({ typ: "matvarde", beskrivning: v.beskrivning, varde: v.varde, enhet: v.enhet });
|
||||
// `kalla` sätts därför att värdet AVLÄSTES av en modell ur fotot
|
||||
// (TÜV T-5). Teknikern bekräftar innan något skrivs, vilket täcker
|
||||
// riktigheten — men bekräftelsen ger inte spårbarheten: utan
|
||||
// härkomst går ett maskinläst värde inte att skilja från ett
|
||||
// teknikern läste av instrumentet, och "hur kom siffran hit" är
|
||||
// just vad en granskare frågar om en avskriven avläsning.
|
||||
skicka({
|
||||
typ: "matvarde",
|
||||
beskrivning: v.beskrivning,
|
||||
varde: v.varde,
|
||||
enhet: v.enhet,
|
||||
kalla: "Instrumentavläsning (modelltolkad, bekräftad av tekniker)",
|
||||
});
|
||||
}
|
||||
paSparad?.(
|
||||
`Instrumentavläsning (${avlasning.tolkning.instrumenttyp}): ${avlasning.tolkning.varden
|
||||
|
||||
Reference in New Issue
Block a user