package agentguard import ( "context" "os" "path/filepath" "runtime" "strings" "testing" ) type admissionFixture struct { request AdmissionRequest baseRoot string taskRoot string commonGit string gitDir string } func TestAdmissionS17Matrix(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("symlink and worktree pointer fixtures require Unix semantics") } tests := []struct { name string mode IsolationMode mutate func(*testing.T, *admissionFixture) wantCode BlockerCode wantInvoke int }{ { name: "registered full clone allowed", mode: IsolationModeClone, wantInvoke: 1, }, { name: "registered worktree with exact metadata allowance allowed", mode: IsolationModeWorktree, wantInvoke: 1, }, { name: "unregistered workspace", mode: IsolationModeClone, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Grant = nil }, wantCode: BlockerCodeMissingWorkspaceGrant, }, { name: "worktree metadata allowance denied", mode: IsolationModeWorktree, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Grant.VCSMetadataRoots = nil }, wantCode: BlockerCodeVCSMetadataNotAllowed, }, { name: "working directory symlink escape", mode: IsolationModeClone, mutate: func(t *testing.T, fixture *admissionFixture) { outside := canonicalMkdir(t, filepath.Join(filepath.Dir(fixture.taskRoot), "outside")) link := filepath.Join(fixture.taskRoot, "escape") if err := os.Symlink(outside, link); err != nil { t.Fatalf("Symlink: %v", err) } fixture.request.Isolation.WorkingDir = link }, wantCode: BlockerCodeWorkspaceRootEscape, }, { name: "isolation cannot enforce writable roots", mode: IsolationModeClone, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Isolation.ConfinementRevision = "" }, wantCode: BlockerCodeWritableConfinementUnavailable, }, { name: "profile cannot confine writable roots", mode: IsolationModeClone, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Profile.WritableRootConfinement = false }, wantCode: BlockerCodeWritableConfinementUnavailable, }, { name: "provider unattended disabled", mode: IsolationModeClone, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Profile.Unattended = false }, wantCode: BlockerCodeProviderUnattendedUnavailable, }, { name: "provider approval bypass disabled", mode: IsolationModeClone, mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Profile.ApprovalBypass = false }, wantCode: BlockerCodeProviderApprovalBypassUnavailable, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { fixture := newAdmissionFixture(t, test.mode) if test.mutate != nil { test.mutate(t, &fixture) } invocations := 0 admission := Admit(fixture.request) if test.wantCode != "" { if admission.Allowed() || admission.Permit != nil || admission.Blocker == nil || admission.Blocker.Code != test.wantCode { t.Fatalf("admission = %#v, want blocker %q", admission, test.wantCode) } if admission.Notification == nil || admission.Notification.SetupGuidance == "" { t.Fatalf("notification = %#v", admission.Notification) } } else { if !admission.Allowed() { t.Fatalf("admission blocked: %#v", admission.Blocker) } invocationResult, err := Invoke( context.Background(), admission.Permit, fixture.request, func(_ context.Context, workspace CanonicalWorkspace) error { invocations++ if workspace.WorkingDir != fixture.taskRoot { t.Fatalf("working dir = %q, want %q", workspace.WorkingDir, fixture.taskRoot) } return nil }, ) if err != nil || !invocationResult.Allowed() { t.Fatalf("Invoke = result:%#v err:%v", invocationResult, err) } } if invocations != test.wantInvoke { t.Fatalf("invocations = %d, want %d", invocations, test.wantInvoke) } }) } } func TestAdmissionRejectsForgedStaleAndIdentityChangedPermitsWithoutInvocation(t *testing.T) { t.Run("forged", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) invocations := 0 result, err := Invoke( context.Background(), &Permit{}, fixture.request, func(context.Context, CanonicalWorkspace) error { invocations++ return nil }, ) if err != nil || result.Blocker == nil || result.Blocker.Code != BlockerCodePermitInvalid { t.Fatalf("Invoke forged = result:%#v err:%v", result, err) } if invocations != 0 { t.Fatalf("forged permit invoked provider %d times", invocations) } }) t.Run("stale revision", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) admission := Admit(fixture.request) if !admission.Allowed() { t.Fatalf("Admit: %#v", admission.Blocker) } fixture.request.Grant.Revision = "grant-r2" invocations := 0 result, err := Invoke( context.Background(), admission.Permit, fixture.request, func(context.Context, CanonicalWorkspace) error { invocations++ return nil }, ) if err != nil || result.Blocker == nil || result.Blocker.Code != BlockerCodePermitStale { t.Fatalf("Invoke stale = result:%#v err:%v", result, err) } if invocations != 0 { t.Fatalf("stale permit invoked provider %d times", invocations) } }) t.Run("filesystem identity replacement", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) admission := Admit(fixture.request) if !admission.Allowed() { t.Fatalf("Admit: %#v", admission.Blocker) } oldTaskRoot := fixture.taskRoot + "-old" if err := os.Rename(fixture.taskRoot, oldTaskRoot); err != nil { t.Fatalf("rename task root: %v", err) } canonicalMkdir(t, fixture.taskRoot) canonicalMkdir(t, filepath.Join(fixture.taskRoot, ".git")) invocations := 0 result, err := Invoke( context.Background(), admission.Permit, fixture.request, func(context.Context, CanonicalWorkspace) error { invocations++ return nil }, ) if err != nil || result.Blocker == nil || result.Blocker.Code != BlockerCodeWorkspaceIdentityMismatch { t.Fatalf("Invoke replaced = result:%#v err:%v", result, err) } if invocations != 0 { t.Fatalf("identity replacement invoked provider %d times", invocations) } }) } func TestAdmissionCanonicalContainmentBoundaries(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("symlink fixtures require Unix semantics") } tests := []struct { name string mutate func(*testing.T, *admissionFixture) wantCode BlockerCode }{ { name: "relative task root", mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Isolation.TaskRoot = "relative/task" }, wantCode: BlockerCodeWorkspaceNotCanonical, }, { name: "nonexistent task root", mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Isolation.TaskRoot = filepath.Join(filepath.Dir(fixture.taskRoot), "missing") }, wantCode: BlockerCodeWorkspaceNotCanonical, }, { name: "parent traversal task root", mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Isolation.TaskRoot = fixture.taskRoot + "/../" + filepath.Base(fixture.taskRoot) }, wantCode: BlockerCodeWorkspaceNotCanonical, }, { name: "canonical task root symlink", mutate: func(t *testing.T, fixture *admissionFixture) { link := fixture.taskRoot + "-link" if err := os.Symlink(fixture.taskRoot, link); err != nil { t.Fatalf("Symlink: %v", err) } fixture.request.Isolation.TaskRoot = link }, wantCode: BlockerCodeWorkspaceNotCanonical, }, { name: "working file symlink", mutate: func(t *testing.T, fixture *admissionFixture) { file := filepath.Join(fixture.taskRoot, "file") if err := os.WriteFile(file, []byte("fixture"), 0o600); err != nil { t.Fatalf("WriteFile: %v", err) } link := filepath.Join(fixture.taskRoot, "file-link") if err := os.Symlink(file, link); err != nil { t.Fatalf("Symlink: %v", err) } fixture.request.Isolation.WorkingDir = link }, wantCode: BlockerCodeWorkspaceRootEscape, }, { name: "prefix collision writable root", mutate: func(t *testing.T, fixture *admissionFixture) { sibling := canonicalMkdir(t, fixture.taskRoot+"-sibling") fixture.request.Isolation.WritableRoots = []string{sibling} }, wantCode: BlockerCodeWritableRootEscape, }, { name: "symlink loop", mutate: func(t *testing.T, fixture *admissionFixture) { a := filepath.Join(fixture.taskRoot, "loop-a") b := filepath.Join(fixture.taskRoot, "loop-b") if err := os.Symlink(b, a); err != nil { t.Fatalf("symlink a: %v", err) } if err := os.Symlink(a, b); err != nil { t.Fatalf("symlink b: %v", err) } fixture.request.Isolation.WorkingDir = a }, wantCode: BlockerCodeWorkspaceRootEscape, }, { name: "clone git metadata symlink", mutate: func(t *testing.T, fixture *admissionFixture) { if err := os.RemoveAll(filepath.Join(fixture.taskRoot, ".git")); err != nil { t.Fatalf("RemoveAll .git: %v", err) } externalGit := canonicalMkdir(t, filepath.Join(filepath.Dir(fixture.taskRoot), "external-git")) if err := os.Symlink(externalGit, filepath.Join(fixture.taskRoot, ".git")); err != nil { t.Fatalf("Symlink .git: %v", err) } }, wantCode: BlockerCodeVCSMetadataNotAllowed, }, { name: "base root revision mismatch", mutate: func(_ *testing.T, fixture *admissionFixture) { fixture.request.Isolation.BaseRoot = fixture.taskRoot }, wantCode: BlockerCodeRevisionMismatch, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) test.mutate(t, &fixture) result := Admit(fixture.request) if result.Blocker == nil || result.Blocker.Code != test.wantCode { t.Fatalf("Admit = %#v, want %q", result, test.wantCode) } }) } } func TestAdmissionNestedWorkingRepositoryMetadata(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("symlink and worktree pointer fixtures require Unix semantics") } t.Run("nested internal git directory allowed", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) nestedDir := canonicalMkdir(t, filepath.Join(fixture.taskRoot, "nested")) canonicalMkdir(t, filepath.Join(nestedDir, ".git")) fixture.request.Isolation.WorkingDir = nestedDir admission := Admit(fixture.request) if !admission.Allowed() { t.Fatalf("Admit = %#v, want allowed", admission.Blocker) } if admission.Workspace.WorkingDir != nestedDir { t.Fatalf("working dir = %q, want %q", admission.Workspace.WorkingDir, nestedDir) } }) t.Run("nested external gitdir denied without allowance", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) nestedDir := canonicalMkdir(t, filepath.Join(fixture.taskRoot, "nested")) outsideGit := canonicalMkdir(t, filepath.Join(filepath.Dir(fixture.taskRoot), "outside-git")) if err := os.WriteFile( filepath.Join(nestedDir, ".git"), []byte("gitdir: "+outsideGit+"\n"), 0o600, ); err != nil { t.Fatalf("write nested .git pointer: %v", err) } fixture.request.Isolation.WorkingDir = nestedDir admission := Admit(fixture.request) if admission.Allowed() || admission.Blocker == nil || admission.Blocker.Code != BlockerCodeVCSMetadataNotAllowed { t.Fatalf("Admit = %#v, want %s", admission, BlockerCodeVCSMetadataNotAllowed) } if admission.Notification == nil || admission.Notification.SetupGuidance == "" { t.Fatalf("notification = %#v", admission.Notification) } }) t.Run("nested external gitdir allowed with exact grant allowance", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) nestedDir := canonicalMkdir(t, filepath.Join(fixture.taskRoot, "nested")) outsideGit := canonicalMkdir(t, filepath.Join(filepath.Dir(fixture.taskRoot), "outside-git")) if err := os.WriteFile( filepath.Join(nestedDir, ".git"), []byte("gitdir: "+outsideGit+"\n"), 0o600, ); err != nil { t.Fatalf("write nested .git pointer: %v", err) } fixture.request.Isolation.WorkingDir = nestedDir fixture.request.Grant.VCSMetadataRoots = []string{outsideGit} admission := Admit(fixture.request) if !admission.Allowed() { t.Fatalf("Admit = %#v, want allowed", admission.Blocker) } found := false for _, root := range admission.Workspace.VCSMetadataRoots { if root == outsideGit { found = true break } } if !found { t.Fatalf("VCSMetadataRoots = %v, want to contain %q", admission.Workspace.VCSMetadataRoots, outsideGit) } }) t.Run("nested symlink git entry denied", func(t *testing.T) { fixture := newAdmissionFixture(t, IsolationModeClone) nestedDir := canonicalMkdir(t, filepath.Join(fixture.taskRoot, "nested")) outsideGit := canonicalMkdir(t, filepath.Join(filepath.Dir(fixture.taskRoot), "outside-git")) if err := os.Symlink(outsideGit, filepath.Join(nestedDir, ".git")); err != nil { t.Fatalf("Symlink nested .git: %v", err) } fixture.request.Isolation.WorkingDir = nestedDir admission := Admit(fixture.request) if admission.Allowed() || admission.Blocker == nil || admission.Blocker.Code != BlockerCodeVCSMetadataNotAllowed { t.Fatalf("Admit = %#v, want %s", admission, BlockerCodeVCSMetadataNotAllowed) } }) } func TestAdmissionAllowsSymlinkResolvedInsideWorkingDirectory(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("symlink fixture requires Unix semantics") } fixture := newAdmissionFixture(t, IsolationModeClone) inside := canonicalMkdir(t, filepath.Join(fixture.taskRoot, "inside")) link := filepath.Join(fixture.taskRoot, "inside-link") if err := os.Symlink(inside, link); err != nil { t.Fatalf("Symlink: %v", err) } fixture.request.Isolation.WorkingDir = link result := Admit(fixture.request) if !result.Allowed() { t.Fatalf("Admit: %#v", result.Blocker) } if result.Workspace.WorkingDir != inside { t.Fatalf("working dir = %q, want %q", result.Workspace.WorkingDir, inside) } } func TestBlockedProjectDoesNotStopIndependentProject(t *testing.T) { blockedFixture := newAdmissionFixture(t, IsolationModeClone) blockedFixture.request.Grant.ProjectID = "project-blocked" blockedFixture.request.Profile.ApprovalBypass = false allowedFixture := newAdmissionFixture(t, IsolationModeClone) allowedFixture.request.Grant.ProjectID = "project-allowed" invocations := map[string]int{} for _, fixture := range []admissionFixture{blockedFixture, allowedFixture} { admission := Admit(fixture.request) if !admission.Allowed() { continue } _, err := Invoke( context.Background(), admission.Permit, fixture.request, func(_ context.Context, workspace CanonicalWorkspace) error { invocations[workspace.ProjectID]++ return nil }, ) if err != nil { t.Fatalf("Invoke: %v", err) } } if invocations[blockedFixture.request.Grant.ProjectID] != 0 { t.Fatalf("blocked project invocation count = %d", invocations[blockedFixture.request.Grant.ProjectID]) } if invocations[allowedFixture.request.Grant.ProjectID] != 1 { t.Fatalf("independent project invocation count = %d", invocations[allowedFixture.request.Grant.ProjectID]) } } func newAdmissionFixture(t *testing.T, mode IsolationMode) admissionFixture { t.Helper() root := canonicalTempDir(t) baseRoot := canonicalMkdir(t, filepath.Join(root, "base-"+strings.ReplaceAll(t.Name(), "/", "-"))) taskRoot := canonicalMkdir(t, filepath.Join(root, "task-"+strings.ReplaceAll(t.Name(), "/", "-"))) fixture := admissionFixture{ baseRoot: baseRoot, taskRoot: taskRoot, request: AdmissionRequest{ Grant: &WorkspaceGrant{ ProjectID: "project-" + strings.ReplaceAll(t.Name(), "/", "-"), WorkspaceID: "workspace-a", Root: baseRoot, Revision: "grant-r1", }, Isolation: &IsolationDescriptor{ ID: "isolation-a", Revision: "isolation-r1", Mode: mode, BaseRoot: baseRoot, TaskRoot: taskRoot, WorkingDir: taskRoot, WritableRoots: []string{taskRoot}, PinnedBaseRevision: "base-r1", ConfinementRevision: "confinement-r1", }, Profile: ProviderProfile{ ProviderID: "codex", ModelID: "gpt", ProfileID: "codex-headless", Revision: "profile-r1", Unattended: true, ApprovalBypass: true, WritableRootConfinement: true, }, }, } switch mode { case IsolationModeClone: canonicalMkdir(t, filepath.Join(taskRoot, ".git")) case IsolationModeWorktree: commonGit := canonicalMkdir(t, filepath.Join(baseRoot, ".git")) gitDir := canonicalMkdir(t, filepath.Join(commonGit, "worktrees", "task-a")) if err := os.WriteFile( filepath.Join(taskRoot, ".git"), []byte("gitdir: "+gitDir+"\n"), 0o600, ); err != nil { t.Fatalf("write .git pointer: %v", err) } if err := os.WriteFile(filepath.Join(gitDir, "commondir"), []byte("../..\n"), 0o600); err != nil { t.Fatalf("write commondir: %v", err) } fixture.commonGit = commonGit fixture.gitDir = gitDir fixture.request.Grant.VCSMetadataRoots = []string{gitDir, commonGit} } return fixture } func canonicalTempDir(t *testing.T) string { t.Helper() path, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatalf("EvalSymlinks temp dir: %v", err) } return filepath.Clean(path) } func canonicalMkdir(t *testing.T, path string) string { t.Helper() if err := os.MkdirAll(path, 0o700); err != nil { t.Fatalf("MkdirAll %s: %v", path, err) } canonical, err := filepath.EvalSymlinks(path) if err != nil { t.Fatalf("EvalSymlinks %s: %v", path, err) } return filepath.Clean(canonical) }