You've already forked agentic-coding-workflow
fix(storage): reject empty key + nil ReadCloser on Get error
This commit is contained in:
@@ -28,8 +28,13 @@ func NewLocalFS(base string) (*LocalFS, error) {
|
|||||||
return &LocalFS{base: filepath.Clean(base)}, nil
|
return &LocalFS{base: filepath.Clean(base)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveSafe 把 key 限制在 base 之下,拒绝 ".." / 绝对路径。
|
// resolveSafe 把 key 限制在 base 之下,拒绝 ".." / 绝对路径 / 空 key。
|
||||||
func (l *LocalFS) resolveSafe(key string) (string, error) {
|
func (l *LocalFS) resolveSafe(key string) (string, error) {
|
||||||
|
// Reject empty / dot keys (would resolve to base itself and corrupt the layout)
|
||||||
|
if key == "" || key == "." {
|
||||||
|
return "", errors.New("storage: key must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
// Reject absolute paths
|
// Reject absolute paths
|
||||||
if filepath.IsAbs(key) {
|
if filepath.IsAbs(key) {
|
||||||
return "", errors.New("storage: key must be relative")
|
return "", errors.New("storage: key must be relative")
|
||||||
@@ -96,7 +101,11 @@ func (l *LocalFS) Get(ctx context.Context, storagePath string) (io.ReadCloser, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return os.Open(full)
|
f, err := os.Open(full)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // 显式 nil interface,避免 typed-nil 包装
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LocalFS) Delete(ctx context.Context, storagePath string) error {
|
func (l *LocalFS) Delete(ctx context.Context, storagePath string) error {
|
||||||
|
|||||||
@@ -52,3 +52,22 @@ func TestLocalFS_DeleteNonexistentIsNoop(t *testing.T) {
|
|||||||
fs, _ := NewLocalFS(t.TempDir())
|
fs, _ := NewLocalFS(t.TempDir())
|
||||||
require.NoError(t, fs.Delete(context.Background(), "nope/nope.txt"))
|
require.NoError(t, fs.Delete(context.Background(), "nope/nope.txt"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalFS_RejectEmptyKey(t *testing.T) {
|
||||||
|
fs, _ := NewLocalFS(t.TempDir())
|
||||||
|
_, err := fs.Put(context.Background(), "", bytes.NewReader(nil), "text/plain")
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = fs.Put(context.Background(), ".", bytes.NewReader(nil), "text/plain")
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = fs.Get(context.Background(), "")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Error(t, fs.Delete(context.Background(), ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalFS_GetMissingFileReturnsNilCloser(t *testing.T) {
|
||||||
|
// 验证错误路径下不会返回 typed-nil io.ReadCloser
|
||||||
|
fs, _ := NewLocalFS(t.TempDir())
|
||||||
|
r, err := fs.Get(context.Background(), "missing/file.txt")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, r)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user