Do not leave the key store world-readable

SQLite creates its file with the process umask, which on a stock system means
every account on the machine can read it. What is in there is not a working
credential — keys are stored hashed — but it is the whole tenancy tree and
every request anyone made, and that should not depend on an operator
remembering to chmod it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5 committed 2026-09-16 09:01:41 +02:00
1 parent 1839832b8b
commit cd3233ffe8
2 files changed
+43

No files matched your search

+12
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"slices"
"strings"
"time"
@@ -44,6 +45,17 @@ func OpenSQLite(ctx context.Context, path string) (*SQLite, error) {
_ = db.Close()
return nil, err
}
// SQLite creates the file with the process umask, which on a stock system
// means world-readable. What is in here is not a working credential — keys
// are stored hashed — but it is the whole tenancy tree and every request
// anyone made, and that should not be readable by every account on the
// machine because nobody remembered to chmod it.
for _, f := range []string{path, path + "-wal", path + "-shm"} {
if err := os.Chmod(f, 0o600); err != nil && !os.IsNotExist(err) {
_ = db.Close()
return nil, fmt.Errorf("securing %s: %w", f, err)
}
}
return &SQLite{db: db}, nil
}
+31
View File
@@ -3,6 +3,7 @@ package store
import (
"context"
"errors"
"os"
"path/filepath"
"slices"
"strings"
@@ -403,3 +404,33 @@ func must(t *testing.T, err error) {
t.Fatal(err)
}
}
func TestTheStoreIsNotWorldReadable(t *testing.T) {
// SQLite creates the file with the process umask, which on a stock system
// leaves it readable by every account on the machine. What is in here is
// not a working credential, but it is the whole tenancy tree and every
// request anyone made.
dir := t.TempDir()
path := filepath.Join(dir, "llmgw.db")
s, err := OpenSQLite(context.Background(), path)
if err != nil {
t.Fatal(err)
}
defer s.Close()
// Force the WAL files into existence.
tree(t, s)
for _, name := range []string{"llmgw.db", "llmgw.db-wal", "llmgw.db-shm"} {
info, err := os.Stat(filepath.Join(dir, name))
if os.IsNotExist(err) {
continue
}
if err != nil {
t.Fatal(err)
}
if mode := info.Mode().Perm(); mode&0o077 != 0 {
t.Errorf("%s is mode %o", name, mode)
}
}
}