fix(memory): tighten jsonl session store permissions

This commit is contained in:
duomi 2026-03-14 21:23:28 +08:00
parent 0f700a6bf0
commit c3e5d6e438
2 changed files with 112 additions and 4 deletions

View file

@ -58,12 +58,20 @@ type JSONLStore struct {
locks [numLockShards]sync.Mutex
}
const (
sessionDirPerm = 0o700
sessionFilePerm = 0o600
)
// NewJSONLStore creates a new JSONL-backed store rooted at dir.
func NewJSONLStore(dir string) (*JSONLStore, error) {
err := os.MkdirAll(dir, 0o755)
err := os.MkdirAll(dir, sessionDirPerm)
if err != nil {
return nil, fmt.Errorf("memory: create directory: %w", err)
}
if err := os.Chmod(dir, sessionDirPerm); err != nil {
return nil, fmt.Errorf("memory: secure directory permissions: %w", err)
}
return &JSONLStore{dir: dir}, nil
}
@ -121,7 +129,7 @@ func (s *JSONLStore) writeMeta(key string, meta sessionMeta) error {
if err != nil {
return fmt.Errorf("memory: encode meta: %w", err)
}
return fileutil.WriteFileAtomic(s.metaPath(key), data, 0o644)
return fileutil.WriteFileAtomic(s.metaPath(key), data, sessionFilePerm)
}
// readMessages reads valid JSON lines from a .jsonl file, skipping
@ -230,11 +238,15 @@ func (s *JSONLStore) addMsg(sessionKey string, msg providers.Message) error {
f, err := os.OpenFile(
s.jsonlPath(sessionKey),
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
0o644,
sessionFilePerm,
)
if err != nil {
return fmt.Errorf("memory: open jsonl for append: %w", err)
}
if err := f.Chmod(sessionFilePerm); err != nil {
f.Close()
return fmt.Errorf("memory: secure jsonl permissions: %w", err)
}
_, writeErr := f.Write(line)
if writeErr != nil {
f.Close()
@ -452,7 +464,7 @@ func (s *JSONLStore) rewriteJSONL(
buf.Write(line)
buf.WriteByte('\n')
}
return fileutil.WriteFileAtomic(s.jsonlPath(sessionKey), buf.Bytes(), 0o644)
return fileutil.WriteFileAtomic(s.jsonlPath(sessionKey), buf.Bytes(), sessionFilePerm)
}
func (s *JSONLStore) Close() error {

View file

@ -4,6 +4,7 @@ import (
"context"
"os"
"path/filepath"
"runtime"
"sync"
"testing"
@ -36,6 +37,101 @@ func TestNewJSONLStore_CreatesDirectory(t *testing.T) {
}
}
func TestJSONLStore_TightensPermissions(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("file permission bits are not enforced on Windows")
}
dir := filepath.Join(t.TempDir(), "nested", "sessions")
store, err := NewJSONLStore(dir)
if err != nil {
t.Fatalf("NewJSONLStore: %v", err)
}
defer store.Close()
if err := store.AddMessage(context.Background(), "s1", "user", "hello"); err != nil {
t.Fatalf("AddMessage: %v", err)
}
dirInfo, err := os.Stat(dir)
if err != nil {
t.Fatalf("Stat dir: %v", err)
}
if got := dirInfo.Mode().Perm(); got != sessionDirPerm {
t.Fatalf("dir perms = %04o, want %04o", got, sessionDirPerm)
}
jsonlInfo, err := os.Stat(store.jsonlPath("s1"))
if err != nil {
t.Fatalf("Stat jsonl: %v", err)
}
if got := jsonlInfo.Mode().Perm(); got != sessionFilePerm {
t.Fatalf("jsonl perms = %04o, want %04o", got, sessionFilePerm)
}
metaInfo, err := os.Stat(store.metaPath("s1"))
if err != nil {
t.Fatalf("Stat meta: %v", err)
}
if got := metaInfo.Mode().Perm(); got != sessionFilePerm {
t.Fatalf("meta perms = %04o, want %04o", got, sessionFilePerm)
}
}
func TestJSONLStore_TightensExistingLoosePermissions(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("file permission bits are not enforced on Windows")
}
dir := filepath.Join(t.TempDir(), "sessions")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
jsonlPath := filepath.Join(dir, "legacy.jsonl")
metaPath := filepath.Join(dir, "legacy.meta.json")
if err := os.WriteFile(jsonlPath, []byte(""), 0o644); err != nil {
t.Fatalf("WriteFile jsonl: %v", err)
}
if err := os.WriteFile(metaPath, []byte(`{"key":"legacy"}`), 0o644); err != nil {
t.Fatalf("WriteFile meta: %v", err)
}
store, err := NewJSONLStore(dir)
if err != nil {
t.Fatalf("NewJSONLStore: %v", err)
}
defer store.Close()
if err := store.AddMessage(context.Background(), "legacy", "user", "hello"); err != nil {
t.Fatalf("AddMessage: %v", err)
}
dirInfo, err := os.Stat(dir)
if err != nil {
t.Fatalf("Stat dir: %v", err)
}
if got := dirInfo.Mode().Perm(); got != sessionDirPerm {
t.Fatalf("dir perms = %04o, want %04o", got, sessionDirPerm)
}
jsonlInfo, err := os.Stat(jsonlPath)
if err != nil {
t.Fatalf("Stat jsonl: %v", err)
}
if got := jsonlInfo.Mode().Perm(); got != sessionFilePerm {
t.Fatalf("jsonl perms = %04o, want %04o", got, sessionFilePerm)
}
metaInfo, err := os.Stat(metaPath)
if err != nil {
t.Fatalf("Stat meta: %v", err)
}
if got := metaInfo.Mode().Perm(); got != sessionFilePerm {
t.Fatalf("meta perms = %04o, want %04o", got, sessionFilePerm)
}
}
func TestAddMessage_BasicRoundtrip(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()