Do not raise agent-can-become-root while the first setuid search is still within its bound

After every node-engine restart the account verdict says not judged yet until
the engine's first search for setuid programs ends, and DA raised the urgent
condition each time. The account stays unconfined and node show still says
not judged; the condition is raised once the search fails, runs out its bound,
finds a way to root, or the statement goes stale.
This commit is contained in:
jochen
2026-10-09 12:37:18 +02:00
parent 95e7a7120a
commit d15eee61bb
3 changed files with 105 additions and 1 deletions
+55
View File
@@ -197,3 +197,58 @@ func TestTheNodeVerbOnlyShows(t *testing.T) {
}
}
}
// After every node-engine restart its search for setuid programs runs for up to its bound, and the agent account
// is not judged until it ends. DA does not raise that as urgent while the search is within its bound — the
// account is still not confined, and `node show` still says not judged — and raises it once the search failed,
// ran out its bound, or found a way to root.
func TestASearchStillRunningAfterARestartIsNotUrgent(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
inv := open.inventory
if _, err := inv.NodeByName(ctx, "anchor"); err != nil {
if _, err := inv.AddNode(ctx, "anchor"); err != nil {
t.Fatal(err)
}
}
if err := inv.SetAgentAccount(ctx, "anchor", "agent", ""); err != nil {
t.Fatal(err)
}
d := &doctor{open: open}
say := func(state, reason string, since time.Time) []conditions.Observation {
t.Helper()
v := inventory.ResourceHealth{Module: "claude-code", Resource: "claude-code.agent-account",
Kind: link.KindAccount, Target: "agent", State: state, Reason: reason, Root: link.RootNever,
Account: "agent", Since: since}
if _, err := inv.RecordHealth(ctx, inventory.NodeHealth{Node: "anchor", Contract: link.RootContract,
SaidAt: time.Now(), HeardAt: time.Now(), Resources: []inventory.ResourceHealth{v}}); err != nil {
t.Fatal(err)
}
found, err := probeAgentAccounts(ctx, d)
if err != nil {
t.Fatal(err)
}
return onlyMachine(found, "anchor")
}
running := link.ReasonRootPending + ": the search for setuid programs, started at 19:21, has not finished yet"
if found := say(link.StateUnknown, running, time.Now().Add(-2*time.Minute)); len(found) != 0 {
t.Fatalf("a search two minutes into its bound was raised: %+v", found)
}
if _, confined, why, _ := agentConfined(ctx, inv, "anchor"); confined || !strings.Contains(why, "not judged") {
t.Fatalf("an account whose search runs was read as confined: %q", why)
}
if found := say(link.StateUnknown, running, time.Now().Add(-searchQuietFor-time.Minute)); len(found) != 1 ||
found[0].Severity != conditions.Urgent {
t.Fatalf("a search past its bound was not urgent: %+v", found)
}
incomplete := "not judged (search incomplete): the search for setuid programs did not finish (last tried at 19:23: " +
"timeout); it is tried again later"
if found := say(link.StateUnknown, incomplete, time.Now().Add(-time.Minute)); len(found) != 1 ||
found[0].Severity != conditions.Urgent {
t.Fatalf("a search that did not finish was not urgent: %+v", found)
}
if found := say(link.StateUnhealthy, link.ReasonRoot+": in the group docker; "+running, time.Now()); len(found) != 1 ||
found[0].Severity != conditions.Urgent {
t.Fatalf("a way to root found while the search runs was not urgent: %+v", found)
}
}