fix(agent/agentcontext): canonicalize scan root in symlink boundary check (#26175)

This commit is contained in:
Kyle Carberry
2026-06-09 19:21:55 -07:00
committed by GitHub
parent deb6eec68b
commit cc9f10fbf2
4 changed files with 18 additions and 8 deletions
+2 -2
View File
@@ -61,7 +61,7 @@ func TestAPI_ListSourcesEmpty(t *testing.T) {
func TestAPI_AddAndListSource(t *testing.T) {
t.Parallel()
wd := t.TempDir()
src := t.TempDir()
src := testutil.TempDirResolved(t)
srv, _ := newAPITestServer(t, agentcontext.ManagerOptions{
WorkingDir: func() string { return wd },
@@ -103,7 +103,7 @@ func TestAPI_AddSourceRejected(t *testing.T) {
func TestAPI_GetAndDeleteSource(t *testing.T) {
t.Parallel()
wd := t.TempDir()
src := t.TempDir()
src := testutil.TempDirResolved(t)
srv, m := newAPITestServer(t, agentcontext.ManagerOptions{
WorkingDir: func() string { return wd },
+5 -5
View File
@@ -67,8 +67,8 @@ func TestManager_InitialSnapshotIsPopulated(t *testing.T) {
func TestManager_AddSourceTriggersResolve(t *testing.T) {
t.Parallel()
wd := t.TempDir()
src := t.TempDir()
wd := testutil.TempDirResolved(t)
src := testutil.TempDirResolved(t)
mustWriteFile(t, filepath.Join(src, "AGENTS.md"), "from source")
m := newTestManager(t, agentcontext.ManagerOptions{
@@ -194,7 +194,7 @@ func TestManager_RemoveSource(t *testing.T) {
func TestManager_HasSource(t *testing.T) {
t.Parallel()
wd := t.TempDir()
src := t.TempDir()
src := testutil.TempDirResolved(t)
m := newTestManager(t, agentcontext.ManagerOptions{
WorkingDir: func() string { return wd },
@@ -285,7 +285,7 @@ func TestManager_ResyncCanceledKeepsLiveSnapshot(t *testing.T) {
func TestManager_InitialSourcesSeeded(t *testing.T) {
t.Parallel()
wd := t.TempDir()
src := t.TempDir()
src := testutil.TempDirResolved(t)
mustWriteFile(t, filepath.Join(src, "AGENTS.md"), "from initial")
m := newTestManager(t, agentcontext.ManagerOptions{
@@ -312,7 +312,7 @@ func TestManager_InitialSourcesSeeded(t *testing.T) {
func TestManager_SeedSourcesLateBindsAfterManifest(t *testing.T) {
t.Parallel()
wd := t.TempDir()
late := t.TempDir()
late := testutil.TempDirResolved(t)
mustWriteFile(t, filepath.Join(late, "AGENTS.md"), "late binding")
// AllowedRoots intentionally omits `late` so AddSource
+2 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/agent/agentcontext"
"github.com/coder/coder/v2/testutil"
)
// switchHomeEnv overrides the platform-specific environment
@@ -99,7 +100,7 @@ func TestValidateSourcePath_RejectsParentSegments(t *testing.T) {
func TestValidateSourcePath_AllowsInsideRoot(t *testing.T) {
t.Parallel()
dir := t.TempDir()
dir := testutil.TempDirResolved(t)
child := filepath.Join(dir, "child")
require.NoError(t, os.MkdirAll(child, 0o755))
+9
View File
@@ -368,7 +368,16 @@ func resolveReadTarget(path string, info fs.FileInfo, scanRoot string) (readPath
if err != nil {
return "", nil, false, StatusUnreadable, fmt.Sprintf("symlink resolve: %v", err)
}
// Canonicalize scanRoot symmetrically with the target so the
// boundary check survives platform-level symlinks in the scan
// root prefix. macOS, for example, exposes /var as a symlink
// to /private/var; EvalSymlinks on the target produces a
// /private/var path while the caller's scanRoot may still be
// /var, which would incorrectly trip the prefix check.
rootClean := filepath.Clean(scanRoot)
if resolved, err := filepath.EvalSymlinks(rootClean); err == nil {
rootClean = resolved
}
if !pathHasPrefix(target, rootClean) {
return "", nil, false, StatusInvalid, fmt.Sprintf("symlink target %q escapes scan root %q", target, scanRoot)
}