feat(notes): notekroppe forsegles, og søgningen åbner dem selv
CI / OpenAPI is valid (push) Successful in 38s
CI / Go (push) Failing after 18m36s
CI / End-to-end (push) Failing after 18m54s
CI / Docker image builds (push) Successful in 7m27s
Documentation / build (push) Failing after 7m7s
Documentation / deploy (push) Skipped
Release / Build and publish image (push) Failing after 9m59s

Grunden er den samme som for postkassers kodeord og OAuth-tokens: en
sikkerhedskopi kan hentes gennem fladen, og indtil nu bar hver eneste af dem hver
eneste note i klartekst. Noter er dér, folk skriver kodeord og API-nøgler ned — det
ved vi, fordi der ligger sådan nogle i den installation, det her blev bygget til.

**Både titel og krop.** Titlen *er* første linje, så en forseglet krop ved siden af
en læselig titel ville efterlade begyndelsen af hver eneste note i klartekst.

**Og de to kopier gik med.** Det var derfor det ikke var gjort for længe siden:
`notes_fts` indekserede kroppen, og den genererede `fold`-søjle bar en
translittereret kopi af den. At forsegle kroppen og lade dem stå ville have været
teater — en angriber med filen kunne læse hver note ud af indekset. Begge er væk,
og tabellen er bygget om, fordi SQLite ikke kan fjerne en genereret søjle på plads.

**Søgningen åbner teksten i Go.** Målt frem for gættet: tolv hundrede noter à 3,3 kB
koster 33–43 ms pr. søgning, og fladen venter 250 ms efter sidste tastetryk, før den
spørger — så det ligger inde i pausen. Tiden går ikke med at åbne konvolutterne;
fire megabyte AES-GCM er få millisekunder. Den går med at folde og småskrive hver
krop, og det er lineært: regn med omtrent 350 ms ved ti tusind noter. Dér er det den
forkerte form, og svaret er da et nøglet indeks — hvis pris er, at præfiks- og
delordssøgningen falder bort.

bm25 forsvandt med indekset. Det, der er beholdt, er den halvdel, der betød mest:
titlen vejer ti gange kroppen. Med et loft pr. ord, så en note, der nævner ordet
fyrre gange, ikke slår en, der nævner alle ordene én gang — det er den fejl, en ren
optælling laver, og den er let at lave og svær at få øje på.

**En bagudfyldning ved opstart, ikke efterhånden.** Instansens indstillinger
forsegles, når de skrives, og det er rigtigt for dem. For et arkiv på tolv hundrede
importerede noter, som ingen redigerer igen, betyder "når de skrives" aldrig — og
det er præcis dét arkiv, der er værd at forsegle. Idempotent, så den koster et
gennemløb af id'er hver anden gang, og `updated_at` røres ikke: at lukke en
konvolut om en note er ikke at skrive den, og et stempel med i aften ville kaste
den rækkefølge væk, importen brugte en dag på at hente hjem.

En fejl under bagudfyldningen stopper opstarten. Halvdelen af noterne lukket er
ikke i stykker — `v1:`-præfikset gør, at begge slags kan læses — men en instans,
der starter og lader som om den beskytter noget, den ikke gør, er værre end en, der
siger fra.

Hvad der stadig ligger åbent, står i SECURITY.md og på dokumentationssiden:
opgavetitler, beskrivelser, kommentarer, og `note_links`, som røber hvilke
projekter og opgaver en note peger på, men ikke hvad der står.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-20 22:40:55 +02:00
co-authored by Claude Opus 5
parent 23bd784b07
commit c8d67dd07d
7 changed files with 434 additions and 99 deletions
+12 -7
View File
@@ -39,13 +39,18 @@ reply within a week.
- **The CSP** is computed at startup by hashing the inline scripts in the built
page, rather than allowing `unsafe-inline`.
- **Note bodies, task titles, descriptions and comments are not encrypted.** The
sealing above covers credentials — mailbox passwords, OAuth tokens, TOTP secrets,
AI keys — and nothing else. A note holding a password is holding it in plain text
in every backup that can be downloaded. Encrypting note bodies is on the list and
is larger than it sounds: the full-text index and the folded search column are
both derived from the body, so both would have to go and search would have to be
rewritten to decrypt in application code.
- **Note titles and bodies are sealed** under the same key, since 0027. Both, not
only the body: a note's title *is* its first line, so a readable title beside a
sealed body would leave the beginning of every note in the clear.
The two plaintext copies went with it. `notes_fts` indexed the body and the
generated `fold` column held a transliterated copy of it — sealing the body while
leaving those would have been theatre, since a stolen file could be read straight
out of the index. Search opens the text in Go instead, which costs 33–43 ms
across twelve hundred notes and is the wrong shape somewhere around ten thousand.
- **Task titles, descriptions and comments are still in the clear**, and so is
`note_links` — ids derived from a note's body, so a stolen file still shows which
projects and tasks a note points at, though not what it says.
## What it does not do