package main // The page where a person says which automated programs are theirs // (uisanction.go), driven end to end through the real mux. // // NOT tagged !windows, for the same reason ui_test.go is not: this page is for // the machine where the bastion has no console, which is the Windows desktop. // // Every test below was MUTATION-CHECKED — the code it covers was broken, the // test confirmed red, and the code was restored. The mutation is named in each // comment. import ( "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" ) // agentsTestBox isolates the config dir and stubs the observe plane. // // observedAgents is the var contain.go:34 declares so a decision can be tested // without a live process tree underneath it; the same seam every other test in // this package uses (agents_kind_test.go:318, events_test.go:39). func agentsTestBox(t *testing.T, seen ...agentObs) { t.Helper() uiTestConfigDir(t) prev := observedAgents t.Cleanup(func() { observedAgents = prev }) observedAgents = func() ([]agentObs, error) { return seen, nil } } func agentsGet(t *testing.T, h http.Handler, path, cookie string) *httptest.ResponseRecorder { t.Helper() r := uiReq(http.MethodGet, path) r.Header.Set("Cookie", cookie) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%s = %d: %s", path, w.Code, w.Body.String()) } return w } // anObservedAgent is an HONEST observation, exactly as obs() (sanction_test.go) // is: the kernel's path and the path its own argv[0] claims agree. Anything // that can be marked as yours must have one, so a fixture without it would only // ever exercise the refusal. func anObservedAgent(sig, cmd string, pid int) agentObs { return agentObs{PID: pid, Kind: kindAgent, Signature: sig, Cmdline: cmd, User: "user", Origin: originInteractive, Exe: argv0(cmd), Evidence: []evidence{{Signal: "agent-signature", Tier: tierKnown, Value: sig}}} } // ── the headline the whole product claim rests on ─────────────────────────── // "Separate the good guys from the bad by identifying the good." The page has // to make the set nobody has vouched for countable, above everything else. // // MUTATION: render `{{.Observed}}` where the template says `{{.Unsanctioned}}`. // RESULT: red — the page announced 3 of 3 after one had been marked. func TestThePageLeadsWithHowManyAreNotMarkedAsYours(t *testing.T) { mine := anObservedAgent("claude-code", "/usr/local/bin/claude", 4242) agentsTestBox(t, mine, anObservedAgent("aider", "/usr/local/bin/aider", 4243), anObservedAgent("goose", "/usr/local/bin/goose", 4244)) u := uiTestServer(t) h := u.handler() cookie := uiSignedIn(t, u) body := agentsGet(t, h, "/agents", cookie).Body.String() if !strings.Contains(body, "3 of 3 are not marked as yours") { t.Fatalf("the page does not lead with the unsanctioned count: %s", firstLines(body)) } if _, err := addSanction(mine, sanctionByLocalUI); err != nil { t.Fatal(err) } body = agentsGet(t, h, "/fragments/agents", cookie).Body.String() if !strings.Contains(body, "2 of 3 are not marked as yours") { t.Errorf("the count did not move after a mark: %s", firstLines(body)) } } // A sanctioned entry must still be LISTED. A control that makes things // disappear is a control an attacker wants the user to use. // // MUTATION: `continue` past sanctioned observations in collectAgentsView. // RESULT: red — the marked agent vanished from the page. func TestAMarkedProgramIsStillListed(t *testing.T) { mine := anObservedAgent("claude-code", "/usr/local/bin/claude", 4242) agentsTestBox(t, mine) if _, err := addSanction(mine, sanctionByLocalUI); err != nil { t.Fatal(err) } u := uiTestServer(t) body := agentsGet(t, u.handler(), "/agents", uiSignedIn(t, u)).Body.String() for _, want := range []string{"claude-code", "/usr/local/bin/claude", ">yours<", "All 1 are accounted for"} { if !strings.Contains(body, want) { t.Errorf("a marked program is not on the page (missing %q)", want) } } if !strings.Contains(body, sanctionWhyLocal) { t.Error("the page does not say WHY it counts as marked") } } // ── marking and unmarking ─────────────────────────────────────────────────── // MUTATION: make agentsMark return before addSanction. // RESULT: red — the click recorded nothing. func TestMarkingAndUnmarkingThroughThePage(t *testing.T) { mine := anObservedAgent("claude-code", "/usr/local/bin/claude", 4242) agentsTestBox(t, mine) u := uiTestServer(t) h := u.handler() cookie := uiSignedIn(t, u) id := sanctionIdentityOf(mine).fingerprint() w := uiPost(t, h, "/agents/mark", url.Values{"id": {id}}, cookie) if w.Code != http.StatusOK { t.Fatalf("mark = %d: %s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "Marked as yours") { t.Errorf("the page did not confirm the mark: %s", firstLines(w.Body.String())) } if sanctioned, why := agentSanction(mine); !sanctioned || why != sanctionWhyLocal { t.Fatalf("after the click the predicate says %v / %q", sanctioned, why) } // The fragment that came back is the CURRENT state, not the one rendered // before the click — the rule renderWith states at ui.go:424-427. if !strings.Contains(w.Body.String(), ">yours<") { t.Error("the fragment returned by the click still shows the old state") } w = uiPost(t, h, "/agents/unmark", url.Values{"id": {id}}, cookie) if !strings.Contains(w.Body.String(), "Unmarked") { t.Errorf("the page did not confirm the withdrawal: %s", firstLines(w.Body.String())) } if sanctioned, _ := agentSanction(mine); sanctioned { t.Fatal("the mark survived being withdrawn") } } // A person can only vouch for something this machine can SEE. The posted id is // looked up among what is running rather than trusted, so a mark can never be // pre-planted for an identity that has not run yet — the same care // approvalTarget (ui.go:442-448) takes with a posted host name. // // MUTATION: in agentsMark, build the record from the posted id instead of // scanning (add a sanction for a synthesised identity). // RESULT: red — a mark was written for something that was never observed. func TestSomethingThisMachineCannotSeeCannotBeMarked(t *testing.T) { agentsTestBox(t, anObservedAgent("claude-code", "/usr/local/bin/claude", 4242)) u := uiTestServer(t) cookie := uiSignedIn(t, u) ghost := sanctionIdentityOf(obs("not-running", "/tmp/evil", "user")).fingerprint() w := uiPost(t, u.handler(), "/agents/mark", url.Values{"id": {ghost}}, cookie) if !strings.Contains(w.Body.String(), "not running on this machine") { t.Errorf("marking an unobserved identity was not refused: %s", firstLines(w.Body.String())) } recs, err := sanctionRecords() if err != nil { t.Fatal(err) } if len(recs) != 0 { t.Fatalf("a mark was written for something the machine never observed: %+v", recs) } // A form that named NOTHING is a different fact from one that named // something since exited, and saying the second about the first is a lie // about the machine. Added after a mutation run showed this branch had no // test on it at all. w = uiPost(t, u.handler(), "/agents/mark", url.Values{}, cookie) if !strings.Contains(w.Body.String(), "carried no program") { t.Errorf("a form naming nothing was reported as a program that had exited: %s", firstLines(w.Body.String())) } } // ── this page cannot stop anything ────────────────────────────────────────── // Containment is host-directed (run.go:575) and whether the local UI may act is // an open product decision. Marking something as yours is not the same act as // terminating it, and this page must not be able to reach the one side effect // in contain.go. // // MUTATION: call killProcess(a.PID) inside agentsMark. // RESULT: red — "this page terminated pid 4242". func TestNothingOnThisPageCanTerminateAnything(t *testing.T) { mine := anObservedAgent("claude-code", "/usr/local/bin/claude", 4242) agentsTestBox(t, mine) prevKill := killProcess t.Cleanup(func() { killProcess = prevKill }) var killed []int killProcess = func(pid int) error { killed = append(killed, pid); return nil } u := uiTestServer(t) h := u.handler() cookie := uiSignedIn(t, u) id := sanctionIdentityOf(mine).fingerprint() body := agentsGet(t, h, "/agents", cookie).Body.String() uiPost(t, h, "/agents/mark", url.Values{"id": {id}}, cookie) uiPost(t, h, "/agents/unmark", url.Values{"id": {id}}, cookie) if len(killed) != 0 { t.Fatalf("this page terminated %v — containment is host-directed and marking "+ "something as yours is a different act from taking it away", killed) } // And it offers no control that could grow into one: the only things the // page posts to are the two routes this unit owns. for _, frag := range strings.Split(body, `hx-post="`)[1:] { target := frag[:strings.IndexByte(frag, '"')] if target != "/agents/mark" && target != "/agents/unmark" { t.Errorf("the page posts to %q — this unit owns marking, nothing else", target) } } if !strings.Contains(body, "This page does not stop anything") { t.Error("the page does not say out loud that it stops nothing") } } // ── the misleading directions this page must not take ─────────────────────── // A read that FAILED is not a machine with nothing on it — ui_approvals_flow_test.go: // 113-151, the same lesson that bit /given. A registry this machine cannot read // must not produce a reassuring count, and must not draw a tick. // // MUTATION: drop the `{{if not .ReadError}}` guard around the count block. // RESULT: red — the page printed "0 of 1 are not marked as yours" about a // machine whose record of marks it had just failed to read. func TestAnUnreadableRegistryIsAnAlarmAndNeverACount(t *testing.T) { agentsTestBox(t, anObservedAgent("claude-code", "/usr/local/bin/claude", 4242)) d, err := configDir() if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(d, "sanctions.json"), []byte("{not json at all"), 0o600); err != nil { t.Fatal(err) } u := uiTestServer(t) body := agentsGet(t, u.handler(), "/agents", uiSignedIn(t, u)).Body.String() if !strings.Contains(body, "cannot read its own record") { t.Errorf("the page did not say the registry is unreadable: %s", firstLines(body)) } if strings.Contains(body, "are not marked as yours") || strings.Contains(body, "are accounted for") { t.Error("the page counted marks on a machine whose record of them it could not read") } if strings.Contains(body, ">yours<") { t.Error("the page drew a tick for a verdict it could not reach") } if !strings.Contains(body, ">unknown<") { t.Error("the row does not say its verdict is unknown") } } // A build with no enumerator must say so rather than render an empty list. // agentScanSupported is a compile-time constant (agents_other.go:13), so the // branch is driven through the template directly — which is where the sentence // either exists or does not. // // MUTATION: delete the `{{if .Blind}}` block from agents.html. // RESULT: red — a blind machine rendered as a clean one. func TestABlindBuildNeverRendersAsAQuietMachine(t *testing.T) { uiTestConfigDir(t) u := uiTestServer(t) rec := httptest.NewRecorder() u.renderAgents(rec, "agents", uiAgentsView{Version: clientVersion, Blind: true}) body := rec.Body.String() if !strings.Contains(body, "cannot watch itself") { t.Fatalf("a build with no enumerator does not say so: %s", firstLines(body)) } if strings.Contains(body, "Nothing automated is running on this machine") { t.Error("a blind machine printed the reassuring sentence — README's whole " + "quiet-versus-blind distinction is that these are different states") } } // A mark whose program is not running right now has to be listed, with the only // button that removes it. Without it, a record for something long uninstalled // vouches for an identity nobody can see and nobody can withdraw. // // MUTATION: skip the dormant loop in collectAgentsView. // RESULT: red — the mark was invisible and unreachable. func TestAMarkWithNothingRunningBehindItIsListedWithAWayToRemoveIt(t *testing.T) { gone := anObservedAgent("aider", "/usr/local/bin/aider", 4243) agentsTestBox(t, gone) if _, err := addSanction(gone, sanctionByLocalUI); err != nil { t.Fatal(err) } // It stops running. observedAgents = func() ([]agentObs, error) { return nil, nil } u := uiTestServer(t) h := u.handler() cookie := uiSignedIn(t, u) body := agentsGet(t, h, "/agents", cookie).Body.String() if !strings.Contains(body, "Marked as yours, but not running right now") { t.Fatalf("a dormant mark is not on the page: %s", firstLines(body)) } if !strings.Contains(body, "aider") { t.Error("the dormant entry does not name what was marked") } id := sanctionIdentityOf(gone).fingerprint() if !strings.Contains(body, `value="`+id+`"`) { t.Error("the dormant entry carries no id, so it cannot be withdrawn") } // And the button works with nothing observed at all. uiPost(t, h, "/agents/unmark", url.Values{"id": {id}}, cookie) recs, err := sanctionRecords() if err != nil { t.Fatal(err) } if len(recs) != 0 { t.Fatalf("a dormant mark could not be withdrawn: %+v", recs) } } // ── the exe identity, on the page ─────────────────────────────────────────── // The page shows the FILE a mark would be recorded against, next to the command // line and never instead of it. A person being asked to vouch for a program is // entitled to see the thing that is actually being recorded — and the two // differing is precisely the case worth noticing. // // MUTATION: delete the `{{if .Exe}}