diff --git a/integration/agentcompat/internal/fixture/agent_path.go b/integration/agentcompat/internal/fixture/agent_path.go index 3a4c3bfb..ebf4485e 100644 --- a/integration/agentcompat/internal/fixture/agent_path.go +++ b/integration/agentcompat/internal/fixture/agent_path.go @@ -90,6 +90,9 @@ func validateRelativeAgentPath(candidate string, destructive bool) (string, erro if strings.TrimSpace(candidate) == "" { return "", rejectPath(PathRejectionEmpty) } + if hasWindowsAbsolutePath(candidate) { + return "", rejectPath(PathRejectionAbsolute) + } if hasWindowsVolume(candidate) { return "", rejectPath(PathRejectionVolume) } @@ -118,6 +121,10 @@ func validateRelativeAgentPath(candidate string, destructive bool) (string, erro return nativeRelative, nil } +func hasWindowsAbsolutePath(candidate string) bool { + return len(candidate) >= 3 && ((candidate[0] >= 'A' && candidate[0] <= 'Z') || (candidate[0] >= 'a' && candidate[0] <= 'z')) && candidate[1] == ':' && (candidate[2] == '\\' || candidate[2] == '/') +} + func hasWindowsVolume(candidate string) bool { if strings.HasPrefix(candidate, `\\`) || strings.HasPrefix(candidate, `//`) { return true diff --git a/integration/agentcompat/internal/fixture/agent_path_test.go b/integration/agentcompat/internal/fixture/agent_path_test.go index d7c77051..c35c54dc 100644 --- a/integration/agentcompat/internal/fixture/agent_path_test.go +++ b/integration/agentcompat/internal/fixture/agent_path_test.go @@ -52,16 +52,17 @@ func TestFixture_AgentPathRejectsParentEscape(t *testing.T) { } } -func TestFixture_AgentPathRejectsVolumeOrSeparatorEscape(t *testing.T) { +func TestFixture_AgentPathRejectsWindowsVolumeOrSeparatorEscape(t *testing.T) { root := newTestAgentRoot(t, "agent-volume") tests := []struct { name string candidate string reason PathRejectionReason }{ - {name: "drive absolute", candidate: `C:\outside.txt`, reason: PathRejectionVolume}, + {name: "drive absolute", candidate: `C:\outside.txt`, reason: PathRejectionAbsolute}, {name: "drive relative", candidate: `C:outside.txt`, reason: PathRejectionVolume}, {name: "UNC", candidate: `\\server\share\outside.txt`, reason: PathRejectionVolume}, + {name: "extended UNC", candidate: `\\?\UNC\server\share\outside.txt`, reason: PathRejectionVolume}, {name: "alternate separator", candidate: `inside\outside.txt`, reason: PathRejectionSeparator}, {name: "empty component", candidate: "inside//outside.txt", reason: PathRejectionSeparator}, } diff --git a/integration/agentcompat/internal/scenario/legacy_fm_protocol_test.go b/integration/agentcompat/internal/scenario/legacy_fm_protocol_test.go index 42931e62..82ffcf80 100644 --- a/integration/agentcompat/internal/scenario/legacy_fm_protocol_test.go +++ b/integration/agentcompat/internal/scenario/legacy_fm_protocol_test.go @@ -91,7 +91,7 @@ func TestLegacyFM_RejectedPathDispatchesNoFrame(t *testing.T) { {name: "absolute", candidate: filepath.Join(t.TempDir(), "outside"), wantReason: fixture.PathRejectionAbsolute}, {name: "parent", candidate: "../outside", wantReason: fixture.PathRejectionParent}, {name: "destructive root", candidate: ".", wantReason: fixture.PathRejectionDestructiveRoot}, - {name: "volume", candidate: `C:\outside`, wantReason: fixture.PathRejectionVolume}, + {name: "absolute", candidate: `C:\outside`, wantReason: fixture.PathRejectionAbsolute}, {name: "separator", candidate: `inside\outside`, wantReason: fixture.PathRejectionSeparator}, {name: "symlink parent", candidate: "linked/file", wantReason: fixture.PathRejectionSymlinkParent}, }