You've already forked agentic-coding-workflow
106 lines
3.0 KiB
Go
106 lines
3.0 KiB
Go
package acp_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/yan1h/agent-coding-workflow/internal/acp"
|
|
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
|
)
|
|
|
|
func TestResolveSafePath_InsideWorktree(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
|
|
target := filepath.Join(tmp, "sub", "a.txt")
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(target), 0o755))
|
|
require.NoError(t, os.WriteFile(target, []byte("x"), 0o644))
|
|
|
|
got, err := acp.ResolveSafePath(tmp, target)
|
|
require.NoError(t, err)
|
|
// EvalSymlinks may resolve macOS / temp paths via /private prefix; on
|
|
// Windows the path may be returned as-is. Compare via filepath.Clean.
|
|
assert.Equal(t, filepath.Clean(target), got)
|
|
|
|
got, err = acp.ResolveSafePath(tmp, "sub/a.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, filepath.Clean(target), got)
|
|
}
|
|
|
|
func TestResolveSafePath_AbsoluteOutside(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
|
|
// On Windows /etc/passwd is interpreted relative to the current drive;
|
|
// use a guaranteed-outside path.
|
|
outsideAbs := filepath.Join(filepath.VolumeName(tmp)+string(filepath.Separator), "etc", "passwd")
|
|
|
|
_, err := acp.ResolveSafePath(tmp, outsideAbs)
|
|
ae, ok := errs.As(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errs.CodeAcpFsPathOutsideWorktree, ae.Code)
|
|
}
|
|
|
|
func TestResolveSafePath_RelativeWithDotDot(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
|
|
_, err := acp.ResolveSafePath(tmp, "../etc/passwd")
|
|
ae, ok := errs.As(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errs.CodeAcpFsPathOutsideWorktree, ae.Code)
|
|
}
|
|
|
|
func TestResolveSafePath_PrefixSiblingTrap(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
sibling := tmp + "_sibling"
|
|
require.NoError(t, os.MkdirAll(sibling, 0o755))
|
|
t.Cleanup(func() { _ = os.RemoveAll(sibling) })
|
|
|
|
_, err := acp.ResolveSafePath(tmp, sibling+string(filepath.Separator)+"file")
|
|
ae, ok := errs.As(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errs.CodeAcpFsPathOutsideWorktree, ae.Code)
|
|
}
|
|
|
|
func TestResolveSafePath_SymlinkEscape(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("symlink test requires admin on Windows")
|
|
}
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
outside := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(outside, "secret"), []byte("s"), 0o644))
|
|
|
|
link := filepath.Join(tmp, "link-to-secret")
|
|
require.NoError(t, os.Symlink(filepath.Join(outside, "secret"), link))
|
|
|
|
_, err := acp.ResolveSafePath(tmp, link)
|
|
ae, ok := errs.As(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errs.CodeAcpFsPathOutsideWorktree, ae.Code)
|
|
}
|
|
|
|
func TestResolveSafePath_NonexistentInsideOK(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := t.TempDir()
|
|
want := filepath.Join(tmp, "newfile.txt")
|
|
got, err := acp.ResolveSafePath(tmp, want)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, want, got)
|
|
}
|
|
|
|
func TestResolveSafePath_RelativeCwdRejected(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := acp.ResolveSafePath("relative-cwd", "x")
|
|
ae, ok := errs.As(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errs.CodeAcpFsPathOutsideWorktree, ae.Code)
|
|
}
|