@@ -196,6 +196,19 @@ func TestWorkspaceSandboxCheckShortCircuits(t *testing.T) {
196196 }
197197}
198198
199+ func TestWorkspaceSandboxCheckRejectsInvalidCapabilityToken (t * testing.T ) {
200+ t .Parallel ()
201+
202+ root := t .TempDir ()
203+ action := fileAction (ActionTypeRead , "filesystem_read_file" , "read_file" , root , "notes.txt" )
204+ action .Payload .CapabilityToken = & CapabilityToken {}
205+
206+ _ , err := NewWorkspaceSandbox ().Check (context .Background (), action )
207+ if err == nil || ! strings .Contains (err .Error (), "capability token path not allowed" ) {
208+ t .Fatalf ("expected capability token path rejection, got %v" , err )
209+ }
210+ }
211+
199212func TestBuildWorkspacePlan (t * testing.T ) {
200213 t .Parallel ()
201214
@@ -263,6 +276,21 @@ func TestBuildWorkspacePlan(t *testing.T) {
263276 wantOK : true ,
264277 wantTarget : "." ,
265278 },
279+ {
280+ name : "sandbox target type falls back to target type" ,
281+ action : Action {
282+ Type : ActionTypeRead ,
283+ Payload : ActionPayload {
284+ ToolName : "filesystem_grep" ,
285+ Resource : "filesystem_grep" ,
286+ Workdir : root ,
287+ TargetType : TargetTypeDirectory ,
288+ Target : "docs" ,
289+ },
290+ },
291+ wantOK : true ,
292+ wantTarget : "docs" ,
293+ },
266294 }
267295
268296 for _ , tt := range tests {
@@ -291,6 +319,21 @@ func TestBuildWorkspacePlan(t *testing.T) {
291319 }
292320}
293321
322+ func TestWorkspaceSandboxValidateWorkspacePlanErrors (t * testing.T ) {
323+ t .Parallel ()
324+
325+ sandbox := NewWorkspaceSandbox ()
326+ _ , err := sandbox .validateWorkspacePlan (workspacePlan {
327+ root : filepath .Join (t .TempDir (), "missing" ),
328+ target : "notes.txt" ,
329+ targetType : TargetTypePath ,
330+ actionType : ActionTypeRead ,
331+ })
332+ if err == nil || ! strings .Contains (err .Error (), "resolve workspace root" ) {
333+ t .Fatalf ("expected resolve workspace root error, got %v" , err )
334+ }
335+ }
336+
294337func TestNeedsWorkspaceSandbox (t * testing.T ) {
295338 t .Parallel ()
296339
@@ -441,6 +484,13 @@ func TestCanonicalWorkspaceRoot(t *testing.T) {
441484 if _ , ok := sandbox .canonicalRoots .Load (cleanedPathKey (existing )); ! ok {
442485 t .Fatalf ("expected canonical root cache entry for %q" , existing )
443486 }
487+ gotCached , err := sandbox .canonicalWorkspaceRoot (existing )
488+ if err != nil {
489+ t .Fatalf ("canonicalWorkspaceRoot(cached) error: %v" , err )
490+ }
491+ if ! samePathKey (gotCached , got ) {
492+ t .Fatalf ("canonicalWorkspaceRoot(cached) = %q, want %q" , gotCached , got )
493+ }
444494
445495 missing := filepath .Join (t .TempDir (), "missing" , "dir" )
446496 _ , err = sandbox .canonicalWorkspaceRoot (missing )
@@ -776,6 +826,54 @@ func TestWorkspaceExecutionPlanValidateForExecution(t *testing.T) {
776826 t .Fatalf ("expected valid plan, got %v" , err )
777827 }
778828 })
829+
830+ t .Run ("nearest existing path failure is returned" , func (t * testing.T ) {
831+ t .Parallel ()
832+
833+ root := t .TempDir ()
834+ file := filepath .Join (root , "file.txt" )
835+ mustWriteWorkspaceFile (t , file , "x" )
836+ plan := & WorkspaceExecutionPlan {
837+ Root : root ,
838+ Target : filepath .Join (file , "child.txt" ),
839+ RequestedTarget : filepath .Join ("file.txt" , "child.txt" ),
840+ anchorPath : file ,
841+ anchorSnapshot : pathSnapshot {},
842+ }
843+ err := plan .ValidateForExecution ()
844+ if err == nil || ! strings .Contains (err .Error (), "inspect path" ) {
845+ t .Fatalf ("expected inspect path error, got %v" , err )
846+ }
847+ })
848+
849+ t .Run ("anchor path mismatch is rejected" , func (t * testing.T ) {
850+ t .Parallel ()
851+
852+ root := t .TempDir ()
853+ targetA := filepath .Join (root , "a" )
854+ targetB := filepath .Join (root , "b" )
855+ if err := os .MkdirAll (targetA , 0o755 ); err != nil {
856+ t .Fatalf ("mkdir targetA: %v" , err )
857+ }
858+ if err := os .MkdirAll (targetB , 0o755 ); err != nil {
859+ t .Fatalf ("mkdir targetB: %v" , err )
860+ }
861+ snapshot , err := capturePathSnapshot (targetB )
862+ if err != nil {
863+ t .Fatalf ("capturePathSnapshot(targetB): %v" , err )
864+ }
865+ plan := & WorkspaceExecutionPlan {
866+ Root : root ,
867+ Target : targetA ,
868+ RequestedTarget : "a" ,
869+ anchorPath : targetB ,
870+ anchorSnapshot : snapshot ,
871+ }
872+ err = plan .ValidateForExecution ()
873+ if err == nil || ! strings .Contains (err .Error (), "changed before execution" ) {
874+ t .Fatalf ("expected changed-before-execution error, got %v" , err )
875+ }
876+ })
779877}
780878
781879func TestCapturePathSnapshotAndEqual (t * testing.T ) {
@@ -893,6 +991,28 @@ func TestNearestExistingPath(t *testing.T) {
893991 return cleanedPathKey (filepath .Join (root , "broken" ))
894992 },
895993 },
994+ {
995+ name : "returns inspect error for non-not-exist lstat" ,
996+ setup : func (t * testing.T ) (string , string ) {
997+ t .Helper ()
998+ root := t .TempDir ()
999+ file := filepath .Join (root , "file.txt" )
1000+ mustWriteWorkspaceFile (t , file , "x" )
1001+ return root , filepath .Join (file , "child.txt" )
1002+ },
1003+ expectErr : "inspect path" ,
1004+ },
1005+ {
1006+ name : "missing root path returns root" ,
1007+ setup : func (t * testing.T ) (string , string ) {
1008+ t .Helper ()
1009+ root := filepath .Join (t .TempDir (), "missing-root" )
1010+ return root , root
1011+ },
1012+ expect : func (root string , target string ) string {
1013+ return cleanedPathKey (root )
1014+ },
1015+ },
8961016 }
8971017
8981018 for _ , tt := range tests {
@@ -918,6 +1038,27 @@ func TestNearestExistingPath(t *testing.T) {
9181038 }
9191039}
9201040
1041+ func TestEnsureNoSymlinkEscapeReturnsNearestPathError (t * testing.T ) {
1042+ t .Parallel ()
1043+
1044+ root := t .TempDir ()
1045+ file := filepath .Join (root , "file.txt" )
1046+ mustWriteWorkspaceFile (t , file , "x" )
1047+
1048+ _ , err := ensureNoSymlinkEscape (root , filepath .Join (file , "child.txt" ), filepath .Join ("file.txt" , "child.txt" ))
1049+ if err == nil || ! strings .Contains (err .Error (), "inspect path" ) {
1050+ t .Fatalf ("expected inspect path error, got %v" , err )
1051+ }
1052+ }
1053+
1054+ func TestValidateTargetVolumeNoVolumeShortCircuit (t * testing.T ) {
1055+ t .Parallel ()
1056+
1057+ if err := validateTargetVolume (filepath .Join (t .TempDir (), "workspace" ), filepath .Join (t .TempDir (), "target" )); err != nil {
1058+ t .Fatalf ("validateTargetVolume() error = %v, want nil on non-volume paths" , err )
1059+ }
1060+ }
1061+
9211062func TestSplitRelativePath (t * testing.T ) {
9221063 t .Parallel ()
9231064
0 commit comments