fix(memory): tighten jsonl session store permissions
This commit is contained in:
parent
0f700a6bf0
commit
c3e5d6e438
2 changed files with 112 additions and 4 deletions
|
|
@ -58,12 +58,20 @@ type JSONLStore struct {
|
||||||
locks [numLockShards]sync.Mutex
|
locks [numLockShards]sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionDirPerm = 0o700
|
||||||
|
sessionFilePerm = 0o600
|
||||||
|
)
|
||||||
|
|
||||||
// NewJSONLStore creates a new JSONL-backed store rooted at dir.
|
// NewJSONLStore creates a new JSONL-backed store rooted at dir.
|
||||||
func NewJSONLStore(dir string) (*JSONLStore, error) {
|
func NewJSONLStore(dir string) (*JSONLStore, error) {
|
||||||
err := os.MkdirAll(dir, 0o755)
|
err := os.MkdirAll(dir, sessionDirPerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("memory: create directory: %w", err)
|
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
|
return &JSONLStore{dir: dir}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +129,7 @@ func (s *JSONLStore) writeMeta(key string, meta sessionMeta) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("memory: encode meta: %w", err)
|
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
|
// 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(
|
f, err := os.OpenFile(
|
||||||
s.jsonlPath(sessionKey),
|
s.jsonlPath(sessionKey),
|
||||||
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
|
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
|
||||||
0o644,
|
sessionFilePerm,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("memory: open jsonl for append: %w", err)
|
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)
|
_, writeErr := f.Write(line)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
@ -452,7 +464,7 @@ func (s *JSONLStore) rewriteJSONL(
|
||||||
buf.Write(line)
|
buf.Write(line)
|
||||||
buf.WriteByte('\n')
|
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 {
|
func (s *JSONLStore) Close() error {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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) {
|
func TestAddMessage_BasicRoundtrip(t *testing.T) {
|
||||||
store := newTestStore(t)
|
store := newTestStore(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue