Encrypt pages at rest with a key per person
CI / test (push) Successful in 2m2s
Release / release (push) Successful in 2m15s

Pages are sealed with AES-256-GCM. Each person's key is derived (HKDF) from a master key in the settings file plus their Authentik issuer and subject. Plain files from earlier versions are read and re-saved encrypted. Mako refuses to open a notebook it cannot decrypt rather than overwrite it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-11-01 00:25:37 -06:00
1 parent e3147597e8
commit 9f2fe4a416
11 files changed
+396 -33

No files matched your search

+65
View File
@@ -612,3 +612,68 @@ func TestFailCapitalizes(t *testing.T) {
t.Fatalf("%q", out["error"])
}
}
func TestNothingIsOnDiskInTheClear(t *testing.T) {
e := setup(t, nil)
a := e.signIn(alice)
e.do("POST", "/api/notes", map[string]string{"title": "Harbour plans", "body": "meet at midnight"}, a)
e.do("POST", "/api/notes", map[string]string{"body": "a quick thought"}, a)
files := 0
filepath.WalkDir(filepath.Join(e.dir, "data"), func(p string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
files++
b, _ := os.ReadFile(p)
for _, s := range []string{"Harbour", "midnight", "quick thought", "Alice", "alice@"} {
if bytes.Contains(b, []byte(s)) {
t.Errorf("%s holds %q in the clear", p, s)
}
}
return nil
})
if files < 2 {
t.Fatalf("only %d files written", files)
}
}
func TestKeyIsMadeOnceAndKept(t *testing.T) {
path := filepath.Join(t.TempDir(), "mako.json")
h, err := config.Load(path)
if err != nil {
t.Fatal(err)
}
if !h.NeedsSave() {
t.Fatal("a new file must be written")
}
h.Save()
key := h.Get().Key
if len(key) < 32 {
t.Fatalf("key %q", key)
}
h2, _ := config.Load(path)
if h2.NeedsSave() || h2.Get().Key != key {
t.Fatal("the key changed or was made again")
}
// a file from before encryption has a secret but no key: one is made and must be written
os.WriteFile(path, []byte(`{"secret":"old-secret"}`), 0o600)
h3, _ := config.Load(path)
if !h3.NeedsSave() || len(h3.Get().Key) < 32 {
t.Fatal("an old file gets no key")
}
// a key from the environment wins and is never written to the file
t.Setenv("MAKO_KEY", strings.Repeat("k", 40))
os.WriteFile(path, []byte(`{"secret":"old-secret"}`), 0o600)
h4, _ := config.Load(path)
h4.Save()
if h4.Get().Key != strings.Repeat("k", 40) {
t.Fatal("the environment key was not used")
}
if b, _ := os.ReadFile(path); strings.Contains(string(b), "kkkk") || strings.Contains(string(b), `"key"`) {
t.Fatalf("the environment key was written to the file: %s", b)
}
t.Setenv("MAKO_KEY", "too short")
if err := h4.Get().Check(); err == nil || !strings.Contains(err.Error(), "encryption key") {
t.Fatalf("a short key was accepted: %v", err)
}
}