claude-code: write the agent's home as its own account where it has one

On the control node agents are to run as an account that cannot become root
(hq ADR 0266). The runtime keeps running as the operator, so the token, the
identity and the home-scope items go to the agent's home through a helper
started as that account; the licence and the account file follow the move,
what was placed in the operator's home goes once the agent holds the licence,
and the guard refuses every tool of a session running as anyone else,
recording to the journal. An API key is taken only from the agent's home and
only when it is one, here and in the licence manager, so the tool cannot carry
off the runtime's credential. Unchanged where no agent account is named.
This commit is contained in:
jochen
2026-10-09 10:14:18 +02:00
parent 7339ce8556
commit b57e6e8144
17 changed files with 1467 additions and 102 deletions
+18 -2
View File
@@ -234,7 +234,11 @@ func status(p Paths) map[string]any {
for n := range Registered(p) {
names = append(names, n)
}
return map[string]any{"node": p.Node, "licence": licence, "token": token, "holdings": HoldingsOf(p),
agent := map[string]any{"account": p.Agent, "home": p.Home, "own_account": p.Confined()}
if !p.Confined() {
agent["note"] = "agents here run as the operator's account"
}
return map[string]any{"node": p.Node, "agent": agent, "licence": licence, "token": token, "holdings": HoldingsOf(p),
"managed": managed, "registered": names}
}
@@ -372,7 +376,19 @@ func main() {
if len(os.Args) == 3 && os.Args[1] == "guard" {
os.Exit(runGuard(os.Args[2], os.Stdin, os.Stderr))
}
// The helper that touches the agent's home as the agent's account (novox/hq ADR 0266), started by this
// module through sudo: one answer per request on stdin, until it ends.
if len(os.Args) == 2 && os.Args[1] == HelperArg {
if err := ServeHelper(os.Stdin, os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return
}
p, launched := PathsFrom(os.Getenv)
if launched {
fsys = FilesFor(p)
}
if !launched {
// Outside a launch — a build, a check — it serves nothing and says why.
say("not launched by the runtime with this module's words; serving no tools")
@@ -515,7 +531,7 @@ func run(p Paths, view *ServerView, configView *ConfigView) {
report()
var last string
for range time.Tick(5 * time.Second) {
info, err := os.Stat(p.credentials())
info, err := fsys.Stat(p.credentials())
now := "absent"
if err == nil {
now = fmt.Sprintf("%d/%d", info.ModTime().UnixNano(), info.Size())