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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue