bastion 0.53.2
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s

This commit is contained in:
Privatae
2026-10-20 04:42:14 +00:00
commit e4c9b69b46
471 changed files with 159794 additions and 0 deletions
+744
View File
@@ -0,0 +1,744 @@
package main
import (
"encoding/binary"
"encoding/json"
"errors"
"os"
"strings"
"testing"
)
func TestParseEnvToken(t *testing.T) {
env := []byte("PATH=/usr/bin\x00PRIVATAE_AGENT_TOKEN=del_abc123\x00HOME=/root\x00")
if got := parseEnvToken(env, "PRIVATAE_AGENT_TOKEN"); got != "del_abc123" {
t.Errorf("parseEnvToken = %q, want del_abc123", got)
}
if got := parseEnvToken([]byte("PATH=/usr/bin\x00HOME=/root\x00"), "PRIVATAE_AGENT_TOKEN"); got != "" {
t.Errorf("parseEnvToken(absent) = %q, want empty", got)
}
// must not partial-match a different var ending in the key
if got := parseEnvToken([]byte("X_PRIVATAE_AGENT_TOKEN=nope\x00"), "PRIVATAE_AGENT_TOKEN"); got != "" {
t.Errorf("parseEnvToken(suffix) = %q, want empty", got)
}
}
func TestPidFromSSLine(t *testing.T) {
line := `ESTAB 0 0 10.0.0.1:54321 1.2.3.4:443 users:(("python3",pid=12345,fd=7))`
if got := pidFromSSLine(line); got != 12345 {
t.Errorf("pidFromSSLine = %d, want 12345", got)
}
if got := pidFromSSLine("ESTAB 0 0 10.0.0.1:1 1.2.3.4:443"); got != 0 {
t.Errorf("pidFromSSLine(no proc) = %d, want 0", got)
}
}
func TestBinBase(t *testing.T) {
cases := map[string]string{
"claude": "claude",
"/usr/local/bin/claude": "claude",
`C:\Program Files\node\node.exe`: "node",
`C:/tools/claude.EXE`: "claude",
" /opt/x/AIDER ": "aider",
"": "",
}
for in, want := range cases {
if got := binBase(in); got != want {
t.Errorf("binBase(%q) = %q, want %q", in, got, want)
}
}
}
func TestMatchAgentSignature(t *testing.T) {
cases := []struct {
argv []string
want string
ok bool
}{
{[]string{"claude"}, "claude-code", true},
{[]string{"/usr/local/bin/claude", "--resume"}, "claude-code", true},
{[]string{"node", "/usr/lib/node_modules/@anthropic-ai/claude-code/cli.js"}, "claude-code", true},
{[]string{"aider", "--model", "gpt-4"}, "aider", true},
{[]string{"cursor-agent"}, "cursor", true},
{[]string{"python3", "-m", "interpreter"}, "open-interpreter", false}, // bare "interpreter" only matches argv0 basename
{[]string{"interpreter"}, "open-interpreter", true},
{[]string{"sleep", "60"}, "", false},
{[]string{"bash"}, "", false},
{[]string{"/home/claudefile/script.sh"}, "", false}, // no false positive on a path substring
{[]string{"bash", "-c", "echo deploying claude-code now"}, "", false}, // a shell that MENTIONS the marker is NOT an agent
{[]string{"sh", "-c", "exec -a claude-code sleep 600"}, "", false}, // ditto — argv0=sh, not an interpreter
{[]string{"claude-code"}, "claude-code", true}, // direct binary named claude-code
{nil, "", false},
// Windows: Toolhelp hands back an image name, and the raw command line
// arrives as ONE extra element (never tokenized — see agents_windows.go).
{[]string{"claude.exe"}, "claude-code", true},
{[]string{"node.exe", `"C:\Program Files\nodejs\node.exe" C:\Users\a\AppData\npm\@anthropic-ai\claude-code\cli.js`}, "claude-code", true},
{[]string{"node.exe", `"C:\Program Files\nodejs\node.exe" server.js`}, "", false},
{[]string{"NOTEPAD.EXE"}, "", false},
}
for _, c := range cases {
got, ok := matchAgentSignature(c.argv)
if ok != c.ok || (ok && got != c.want) {
t.Errorf("matchAgentSignature(%v) = (%q,%v), want (%q,%v)", c.argv, got, ok, c.want, c.ok)
}
}
}
func TestSplitHostPortLoose(t *testing.T) {
cases := []struct {
in string
ip string
port int
ok bool
}{
{"1.2.3.4:443", "1.2.3.4", 443, true},
{"[fd00::1]:11434", "fd00::1", 11434, true},
{"[::1]:1234", "::1", 1234, true},
{"127.0.0.1:11434", "127.0.0.1", 11434, true},
{"nonsense", "", 0, false},
{"1.2.3.4:0", "", 0, false},
{"1.2.3.4:99999", "", 0, false},
{"", "", 0, false},
}
for _, c := range cases {
ip, port, ok := splitHostPortLoose(c.in)
if ok != c.ok || ip != c.ip || port != c.port {
t.Errorf("splitHostPortLoose(%q) = (%q,%d,%v), want (%q,%d,%v)", c.in, ip, port, ok, c.ip, c.port, c.ok)
}
}
}
func TestPortOfAddr(t *testing.T) {
cases := map[string]int{
"127.0.0.1:11434": 11434,
"http://127.0.0.1:8080": 8080,
"[::1]:1234": 1234,
"localhost:5000/v1": 5000,
"11434": 0, // a bare port is not an address
"localhost": 0,
"": 0,
}
for in, want := range cases {
if got := portOfAddr(in); got != want {
t.Errorf("portOfAddr(%q) = %d, want %d", in, got, want)
}
}
}
// The local-inference tiering is the part most able to cause harm: a wrong
// "strong" verdict raises an unrequested-agent alert on a developer running an
// ordinary web server on :8080.
func TestLocalLLMPortTiers(t *testing.T) {
t.Setenv("OLLAMA_ADDR", "")
t.Setenv("PRIVATAE_LLM_ADDR", "")
strong, weak := localLLMPorts()
if strong[11434] == "" {
t.Error("11434 must be strong: it is ollama's published default")
}
if weak[8080] == "" {
t.Error("8080 must be WEAK by default — it collides with ordinary web development")
}
if strong[8080] != "" {
t.Error("8080 must not be strong on a box that never declared an inference server there")
}
// Declared by the operator => the same port becomes evidence on its own.
t.Setenv("PRIVATAE_LLM_ADDR", "127.0.0.1:8080")
strong, weak = localLLMPorts()
if strong[8080] == "" {
t.Error("a port named by PRIVATAE_LLM_ADDR must be strong — the operator declared it")
}
if weak[8080] != "" {
t.Error("a port promoted to strong must not also remain weak")
}
}
func TestClassifyConn(t *testing.T) {
cloud := map[string]string{"1.2.3.4": "api.anthropic.com"}
strong := map[int]string{11434: "ollama"}
weak := map[int]string{8080: "llama.cpp/localai"}
if label, alone := classifyConn(connObs{RemoteIP: "1.2.3.4", RemotePort: 443}, cloud, strong, weak); label != "api.anthropic.com" || !alone {
t.Errorf("cloud endpoint = (%q,%v), want (api.anthropic.com,true)", label, alone)
}
if label, alone := classifyConn(connObs{RemoteIP: "127.0.0.1", RemotePort: 11434}, cloud, strong, weak); label != "local:11434 (ollama)" || !alone {
t.Errorf("known local = (%q,%v), want (local:11434 (ollama),true)", label, alone)
}
// A LAN GPU box is the same signal as loopback — the address is the user's
// choice, the port is the server's default.
if label, _ := classifyConn(connObs{RemoteIP: "192.168.1.50", RemotePort: 11434}, cloud, strong, weak); label != "local:11434 (ollama)" {
t.Errorf("LAN ollama = %q, want local:11434 (ollama)", label)
}
if label, alone := classifyConn(connObs{RemoteIP: "127.0.0.1", RemotePort: 8080}, cloud, strong, weak); label == "" || alone {
t.Errorf("ambiguous local = (%q,%v), want (non-empty,false)", label, alone)
}
if label, _ := classifyConn(connObs{RemoteIP: "10.0.0.9", RemotePort: 22}, cloud, strong, weak); label != "" {
t.Errorf("ssh = %q, want no classification", label)
}
}
func TestMergeConnections(t *testing.T) {
cloud := map[string]string{"1.2.3.4": "api.openai.com"}
strong := map[int]string{11434: "ollama"}
weak := map[int]string{8080: "llama.cpp/localai"}
info := func(pid int) procDetail {
return procDetail{User: "user", Cmdline: "some-cmd", Exe: "/opt/x/some-cmd"}
}
noParent := func(int) int { return 0 }
// A strong endpoint on an unknown process CREATES the disguised-agent observation.
byPid := map[int]agentObs{}
mergeConnections(byPid, []connObs{{PID: 7, RemoteIP: "127.0.0.1", RemotePort: 11434}}, cloud, strong, weak, info, noParent)
if a, ok := byPid[7]; !ok || a.Signature != "llm-client" || a.User != "user" || a.Kind != kindClient {
t.Fatalf("a strong local endpoint must create an llm-client observation, got %+v", byPid)
}
// A WEAK endpoint on an unknown process must NOT — this is the alarm-noise wall.
byPid = map[int]agentObs{}
mergeConnections(byPid, []connObs{{PID: 8, RemoteIP: "127.0.0.1", RemotePort: 8080}}, cloud, strong, weak, info, noParent)
if len(byPid) != 0 {
t.Fatalf("an ambiguous port must never promote an unknown process, got %+v", byPid)
}
// ...but it DOES annotate a process the signature scan already matched.
byPid = map[int]agentObs{9: {PID: 9, Signature: "aider"}}
mergeConnections(byPid, []connObs{{PID: 9, RemoteIP: "127.0.0.1", RemotePort: 8080}}, cloud, strong, weak, info, noParent)
if len(byPid[9].LLMEndpoints) != 1 {
t.Fatalf("an ambiguous port must annotate a known agent, got %+v", byPid[9])
}
// Endpoints de-duplicate across many connections to the same place.
byPid = map[int]agentObs{9: {PID: 9, Signature: "aider"}}
mergeConnections(byPid, []connObs{
{PID: 9, RemoteIP: "1.2.3.4", RemotePort: 443},
{PID: 9, RemoteIP: "1.2.3.4", RemotePort: 443},
}, cloud, strong, weak, info, noParent)
if got := byPid[9].LLMEndpoints; len(got) != 1 || got[0] != "api.openai.com" {
t.Fatalf("endpoints must de-duplicate, got %v", got)
}
}
// ── Linux ───────────────────────────────────────────────────────────────────
// Fixture copied from real `ss -tnpH state established` output. Filtering by
// state makes ss OMIT the State column, so the peer is field 3, not field 4 —
// the reason this parser is pinned by a test rather than eyeballed.
func TestParseSSOutput(t *testing.T) {
out := `0 0 10.0.0.1:54321 1.2.3.4:443 users:(("claude",pid=111,fd=7))
0 0 [fd00::2]:5555 [fd00::1]:11434 users:(("python3",pid=222,fd=9))
0 0 10.0.0.1:1 10.0.0.2:22
0 0 10.0.0.1:2 10.0.0.3:443 users:(("self",pid=999,fd=3))`
conns := parseSSOutput(out, 999)
if len(conns) != 2 {
t.Fatalf("want 2 (no-pid line and self dropped), got %d: %+v", len(conns), conns)
}
if conns[0].PID != 111 || conns[0].RemoteIP != "1.2.3.4" || conns[0].RemotePort != 443 {
t.Errorf("v4 row = %+v", conns[0])
}
if conns[1].PID != 222 || conns[1].RemoteIP != "fd00::1" || conns[1].RemotePort != 11434 {
t.Errorf("v6 row = %+v", conns[1])
}
}
// ── macOS ───────────────────────────────────────────────────────────────────
func TestParsePSOutput(t *testing.T) {
out := ` 501 400 user /usr/local/bin/claude --resume
502 1 root /usr/bin/node /opt/x/@anthropic-ai/claude-code/cli.js
1 0 root /sbin/launchd
junk`
users, argvs, parents := parsePSOutput(out)
if users[501] != "user" || len(argvs[501]) != 2 || argvs[501][0] != "/usr/local/bin/claude" {
t.Fatalf("pid 501 = %q %v", users[501], argvs[501])
}
if parents[501] != 400 || parents[502] != 1 {
t.Fatalf("parents = %v, want 501->400 and 502->1", parents)
}
if sig, ok := matchAgentSignature(argvs[502]); !ok || sig != "claude-code" {
t.Errorf("interpreter-wrapped agent from ps = (%q,%v)", sig, ok)
}
if _, ok := argvs[0]; ok {
t.Error("a malformed line must not produce a pid")
}
}
// ── the kernel's executable path (sanitizeExePath, parseProcargs2ExePath) ────
// The rule every platform's procExe answers through: ABSOLUTE OR EMPTY, and
// Linux's " (deleted)" marker stripped.
//
// The deleted case is measured, not assumed: a running binary here read
// "/tmp/x/sleepy" before `rm` and "/tmp/x/sleepy (deleted)" after. Keeping the
// suffix would silently un-mark a running agent the moment its package updated
// in place, which is the shape of control people stop using.
//
// MUTATION: drop the TrimSuffix for " (deleted)".
// RESULT: red — the same running program produced two different identities
// either side of an in-place update.
// MUTATION: return s unchanged instead of "" when !isAbsExePath(s).
// RESULT: red — "claude" (a relative exec) became an identity that any process
// started from any directory could match.
func TestSanitizeExePath(t *testing.T) {
for _, c := range []struct{ in, want string }{
{"/usr/local/bin/claude", "/usr/local/bin/claude"},
{"/tmp/x/sleepy (deleted)", "/tmp/x/sleepy"},
{`C:\Program Files\Claude\claude.exe`, `C:\Program Files\Claude\claude.exe`},
{`\\fileserver\tools\claude.exe`, `\\fileserver\tools\claude.exe`},
{" /usr/bin/node ", "/usr/bin/node"},
// Not identities: they name a file only against a working directory
// nobody recorded, so two processes started from different places would
// share one. Dropped to "" — unmarkable, which is the safe direction.
{"claude", ""},
{"./bin/claude", ""},
{"../claude", ""},
{"", ""},
{" ", ""},
// A relative path that merely LOOKS absolute on the other platform's
// convention is still relative. binBase (agents_parse.go:18-21) records
// why filepath.* cannot be used for this.
{`Program Files\claude.exe`, ""},
{"C:claude.exe", ""}, // drive-relative: no separator after the colon
} {
if got := sanitizeExePath(c.in); got != c.want {
t.Errorf("sanitizeExePath(%q) = %q, want %q", c.in, got, c.want)
}
}
// A path past any kernel's own limit is REFUSED, not cut down: two
// executables under one long directory would truncate to the same string
// and become one identity, which is a collision in the field whose whole
// job is telling programs apart.
//
// MUTATION: `return s[:4096]` instead of "" for an over-long path.
// RESULT: red — a path this machine could not have produced became an
// identity, and two of them would have become the same one.
long := "/" + strings.Repeat("a", 5000)
if got := sanitizeExePath(long); got != "" {
t.Errorf("a %d-character path became an identity of %d characters", len(long), len(got))
}
if got := sanitizeExePath("/" + strings.Repeat("a", 4095)); got == "" {
t.Error("a 4096-character path is inside Linux's own PATH_MAX and must be kept")
}
}
// macOS: the kernel's saved executable path is the first string after argc in a
// KERN_PROCARGS2 blob, and `ps -o comm=` cannot answer this question because it
// SKIPS that string to reach argv[0] (adv_cmds ps/print.c, getproclline).
//
// The fixture is built the way the kernel lays it out: argc, the exec path, the
// NUL padding it is aligned with, then argv[0] — which is given a DIFFERENT
// value on purpose, because reading argv[0] by mistake is the bug this parser
// exists to avoid and a fixture where they matched could not catch it.
//
// MUTATION: return the SECOND string instead of the first (skip past the exec
// path, as ps does).
// RESULT: red — the parser returned /home/user/.local/bin/claude, the forged
// argv[0], instead of the file the kernel loaded.
func TestParseProcargs2ExePath(t *testing.T) {
blob := func(argc int32, strs ...string) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, uint32(argc))
for i, s := range strs {
b = append(b, []byte(s)...)
b = append(b, 0)
if i == 0 {
b = append(b, 0, 0, 0) // the kernel pads the exec path for alignment
}
}
return b
}
got := parseProcargs2ExePath(blob(2, "/usr/bin/sleep",
"/home/user/.local/bin/claude", "25"))
if got != "/usr/bin/sleep" {
t.Errorf("parseProcargs2ExePath = %q, want the kernel's /usr/bin/sleep — "+
"argv[0] is the value this must never return", got)
}
// The same sanitising as everywhere else: an app bundle path with spaces is
// kept whole, a relative saved path is not an identity.
if got := parseProcargs2ExePath(blob(1, "/Applications/My App.app/Contents/MacOS/My App", "x")); got !=
"/Applications/My App.app/Contents/MacOS/My App" {
t.Errorf("a path with spaces must survive whole, got %q", got)
}
if got := parseProcargs2ExePath(blob(1, "claude", "claude")); got != "" {
t.Errorf("a relative saved path must not become an identity, got %q", got)
}
// Refusals: nothing here may guess past a blob it does not understand. A
// short read is what a racing exec produces (the kernel then copies out the
// TAIL of the argument area, not its head — xnu sysctl_procargsx), and
// parsing that would invent a path out of somebody's environment.
for _, bad := range [][]byte{
nil,
{1, 2, 3}, // shorter than argc
{1, 0, 0, 0}, // argc with nothing after it
append(blob(1)[:4], []byte("/usr/b")...), // no terminator inside the buffer
{0xff, 0xff, 0xff, 0xff, '/', 'x', 0}, // negative argc: not this layout
} {
if got := parseProcargs2ExePath(bad); got != "" {
t.Errorf("parseProcargs2ExePath(% x) = %q, want empty", bad, got)
}
}
}
func TestParseLsofOutput(t *testing.T) {
out := `p111
n127.0.0.1:52000->127.0.0.1:11434
n10.0.0.1:52001->1.2.3.4:443
p222
n[::1]:52002->[::1]:1234
p999
n10.0.0.1:1->10.0.0.2:443
pbogus
n10.0.0.1:2->10.0.0.3:443`
conns := parseLsofOutput(out, 999)
if len(conns) != 3 {
t.Fatalf("want 3 (self and bogus-pid blocks dropped), got %d: %+v", len(conns), conns)
}
if conns[0].PID != 111 || conns[0].RemotePort != 11434 {
t.Errorf("row 0 = %+v", conns[0])
}
if conns[2].PID != 222 || conns[2].RemoteIP != "::1" || conns[2].RemotePort != 1234 {
t.Errorf("v6 row = %+v", conns[2])
}
}
func TestParseEnvTokenSpaced(t *testing.T) {
out := "node cli.js PATH=/usr/bin PRIVATAE_AGENT_TOKEN=del_xyz HOME=/Users/user"
if got := parseEnvTokenSpaced(out, "PRIVATAE_AGENT_TOKEN"); got != "del_xyz" {
t.Errorf("= %q, want del_xyz", got)
}
if got := parseEnvTokenSpaced("node cli.js PATH=/usr/bin", "PRIVATAE_AGENT_TOKEN"); got != "" {
t.Errorf("(absent) = %q, want empty", got)
}
}
// ── Windows ─────────────────────────────────────────────────────────────────
//
// The Win32 calls cannot run here, but every byte layout they produce can be
// built and decoded — which is the whole reason the parsers live outside the
// build-tagged file.
func winRow4(state uint32, remoteIP [4]byte, remotePort uint16, pid uint32) []byte {
row := make([]byte, winTCPRow4Size)
binary.LittleEndian.PutUint32(row[0:4], state)
copy(row[12:16], remoteIP[:])
binary.BigEndian.PutUint16(row[16:18], remotePort) // network order, low 16 bits
binary.LittleEndian.PutUint32(row[20:24], pid)
return row
}
func winRow6(state uint32, remoteIP [16]byte, remotePort uint16, pid uint32) []byte {
row := make([]byte, winTCPRow6Size)
copy(row[24:40], remoteIP[:])
binary.BigEndian.PutUint16(row[44:46], remotePort)
binary.LittleEndian.PutUint32(row[48:52], state)
binary.LittleEndian.PutUint32(row[52:56], pid)
return row
}
func winTable(rows ...[]byte) []byte {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(rows)))
for _, r := range rows {
buf = append(buf, r...)
}
return buf
}
func TestParseTCPTableV4(t *testing.T) {
buf := winTable(
winRow4(winTCPStateEst, [4]byte{1, 2, 3, 4}, 443, 111),
winRow4(2 /* SYN_SENT */, [4]byte{5, 6, 7, 8}, 443, 222), // not established
winRow4(winTCPStateEst, [4]byte{127, 0, 0, 1}, 11434, 333),
winRow4(winTCPStateEst, [4]byte{9, 9, 9, 9}, 443, 999), // self
)
conns := parseTCPTable(buf, false, 999)
if len(conns) != 2 {
t.Fatalf("want 2 (non-established and self dropped), got %d: %+v", len(conns), conns)
}
if conns[0].PID != 111 || conns[0].RemoteIP != "1.2.3.4" || conns[0].RemotePort != 443 {
t.Errorf("row 0 = %+v", conns[0])
}
if conns[1].PID != 333 || conns[1].RemoteIP != "127.0.0.1" || conns[1].RemotePort != 11434 {
t.Errorf("row 1 = %+v", conns[1])
}
}
func TestParseTCPTableV6(t *testing.T) {
var loop [16]byte
loop[15] = 1 // ::1
buf := winTable(winRow6(winTCPStateEst, loop, 1234, 444))
conns := parseTCPTable(buf, true, 1)
if len(conns) != 1 {
t.Fatalf("want 1, got %d: %+v", len(conns), conns)
}
if conns[0].PID != 444 || conns[0].RemoteIP != "::1" || conns[0].RemotePort != 1234 {
t.Errorf("v6 row = %+v", conns[0])
}
}
func TestParseTCPTableTruncated(t *testing.T) {
// A table claiming more rows than the buffer holds must stop, not panic.
buf := make([]byte, 4+winTCPRow4Size)
binary.LittleEndian.PutUint32(buf[0:4], 50)
binary.LittleEndian.PutUint32(buf[4:8], winTCPStateEst)
binary.LittleEndian.PutUint32(buf[24:28], 7)
if got := parseTCPTable(buf, false, 1); len(got) > 1 {
t.Fatalf("truncated table must stop at the buffer end, got %d rows", len(got))
}
if got := parseTCPTable(nil, false, 1); len(got) != 0 {
t.Fatalf("nil buffer = %d rows, want 0", len(got))
}
if got := parseTCPTable([]byte{1, 2}, true, 1); len(got) != 0 {
t.Fatalf("short buffer = %d rows, want 0", len(got))
}
}
func utf16le(s string) []byte {
b := make([]byte, 0, len(s)*2)
for _, r := range s {
b = append(b, byte(r), byte(r>>8))
}
return b
}
func TestParseWinEnvBlock(t *testing.T) {
var blk []byte
for _, e := range []string{`PATH=C:\Windows`, "PRIVATAE_AGENT_TOKEN=del_win1", `HOME=C:\Users\a`} {
blk = append(blk, utf16le(e)...)
blk = append(blk, 0, 0)
}
blk = append(blk, 0, 0) // terminating empty entry
if got := parseWinEnvBlock(blk, "PRIVATAE_AGENT_TOKEN"); got != "del_win1" {
t.Errorf("= %q, want del_win1", got)
}
// Windows environment names are case-insensitive.
var lower []byte
lower = append(lower, utf16le("privatae_agent_token=del_win2")...)
lower = append(lower, 0, 0, 0, 0)
if got := parseWinEnvBlock(lower, "PRIVATAE_AGENT_TOKEN"); got != "del_win2" {
t.Errorf("case-insensitive lookup = %q, want del_win2", got)
}
// A block truncated by the bounded read must not loop or panic.
if got := parseWinEnvBlock(utf16le(`PATH=C:\Windows`), "PRIVATAE_AGENT_TOKEN"); got != "" {
t.Errorf("truncated block = %q, want empty", got)
}
if got := parseWinEnvBlock(nil, "PRIVATAE_AGENT_TOKEN"); got != "" {
t.Errorf("nil block = %q, want empty", got)
}
}
// ── the bastion must not report its own local agent as an unsanctioned agent ──
func TestFaOwnChildExclusion(t *testing.T) {
if faIsOwnChild(4242) {
t.Fatal("an untracked pid must not read as our own child")
}
faTrackPID(4242)
if !faIsOwnChild(4242) {
t.Fatal("a tracked local agent child must be excluded from the observe plane")
}
faUntrackPID(4242)
if faIsOwnChild(4242) {
t.Fatal("an exited child must stop being excluded — its pid gets reused")
}
faTrackPID(0)
if faIsOwnChild(0) {
t.Fatal("pid 0 is not a child")
}
}
// TestScanAgentsLive exercises the REAL enumerators on the machine running the
// tests. Guarded like the local agent end-to-end test: it asserts nothing about
// what it finds (a quiet box is a valid result) — it proves the platform path
// executes and returns coherent records. PRIVATAE_OBSERVE_LIVE=1 to run.
func TestScanAgentsLive(t *testing.T) {
if os.Getenv("PRIVATAE_OBSERVE_LIVE") != "1" {
t.Skip("set PRIVATAE_OBSERVE_LIVE=1 to scan this machine")
}
if !agentScanSupported {
t.Skipf("no observe-plane enumerator for this platform (%s)", agentScanKind)
}
agents, err := scanAgents()
if err != nil {
t.Fatal(err)
}
t.Logf("scan kind=%s observed=%d", agentScanKind, len(agents))
for _, a := range agents {
if a.PID <= 0 || a.Signature == "" {
t.Errorf("incoherent observation: %+v", a)
}
t.Logf(" pid=%-8d sig=%-14s user=%-10s endpoints=%v cmd=%.60s",
a.PID, a.Signature, a.User, a.LLMEndpoints, a.Cmdline)
}
}
// ── review follow-ups ───────────────────────────────────────────────────────
func TestIsLocalScope(t *testing.T) {
local := []string{"127.0.0.1", "::1", "192.168.1.50", "10.0.0.9", "172.16.0.1",
"fd00::1", "fe80::1", "100.64.0.9"} // incl. the mesh's own CGNAT range
for _, ip := range local {
if !isLocalScope(ip) {
t.Errorf("isLocalScope(%q) = false, want true", ip)
}
}
for _, ip := range []string{"1.2.3.4", "8.8.8.8", "2606:4700::1", "", "garbage"} {
if isLocalScope(ip) {
t.Errorf("isLocalScope(%q) = true, want false", ip)
}
}
}
// A model served from a PUBLIC address is still self-hosted inference — the
// behaviour is deliberate — but labelling it "local:" reads as a lie.
func TestClassifyConnNamesNonLocalByAddress(t *testing.T) {
strong := map[int]string{11434: "ollama"}
weak := map[int]string{8080: "llama.cpp/localai"}
cloud := map[string]string{}
if label, alone := classifyConn(connObs{RemoteIP: "192.168.1.50", RemotePort: 11434}, cloud, strong, weak); label != "local:11434 (ollama)" || !alone {
t.Errorf("LAN = (%q,%v), want (local:11434 (ollama),true)", label, alone)
}
if label, alone := classifyConn(connObs{RemoteIP: "203.0.113.7", RemotePort: 11434}, cloud, strong, weak); label != "203.0.113.7:11434 (ollama)" || !alone {
t.Errorf("public = (%q,%v), want (203.0.113.7:11434 (ollama),true)", label, alone)
}
// The two facts stay independent: address class in the prefix, port
// confidence in the "?" suffix.
if label, alone := classifyConn(connObs{RemoteIP: "203.0.113.7", RemotePort: 8080}, cloud, strong, weak); label != "203.0.113.7:8080 (llama.cpp/localai?)" || alone {
t.Errorf("public ambiguous = (%q,%v)", label, alone)
}
if label, _ := classifyConn(connObs{RemoteIP: "127.0.0.1", RemotePort: 8080}, cloud, strong, weak); label != "local:8080 (llama.cpp/localai?)" {
t.Errorf("loopback ambiguous = %q", label)
}
}
// The contain guard must ask the same question the heartbeat answers, on every
// platform — it was /proc-only (inert on Windows and macOS) and
// signature-only (an observed llm-client could never be contained).
func TestObservedAgentPIDsCoversEveryObservation(t *testing.T) {
prev := observedAgents
defer func() { observedAgents = prev }()
observedAgents = func() ([]agentObs, error) {
return []agentObs{
{PID: 101, Signature: "claude-code"},
// caught by the NETWORK signal only — no process signature at all
{PID: 202, Signature: "llm-client", LLMEndpoints: []string{"local:11434 (ollama)"}},
}, nil
}
got := observedAgentMap()
if _, ok := got[101]; !ok {
t.Error("a signature-matched agent must be containable")
}
if _, ok := got[202]; !ok {
t.Error("an llm-client must be containable — it is exactly what the network signal exists to catch")
}
if _, ok := got[303]; ok {
t.Error("a pid that was never observed must NEVER be containable")
}
// A platform with no enumerator contains nothing: a box that cannot see
// must not kill on the host's belief.
observedAgents = func() ([]agentObs, error) { return nil, nil }
if len(observedAgentMap()) != 0 {
t.Error("a blind box must contain nothing")
}
}
func TestMaybeContainRefusesUnobserved(t *testing.T) {
prev := observedAgents
defer func() { observedAgents = prev }()
observedAgents = func() ([]agentObs, error) { return []agentObs{{PID: 101, Signature: "claude-code"}}, nil }
// Nothing here may kill: pid 1, our own pid, and an unobserved pid are all
// refused. If the guard regressed, this test would terminate the test run.
self := os.Getpid()
body := []byte(`{"contain":[1,` + itoa(self) + `,303]}`)
maybeContain("", "", body) // must return without killing anything
maybeContain("", "", []byte(`{}`))
maybeContain("", "", []byte(`not json`))
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var b []byte
for n > 0 {
b = append([]byte{byte('0' + n%10)}, b...)
n /= 10
}
return string(b)
}
// A walk that did not happen must not travel as an empty list. The host reads
// `agents: []` from a box advertising agent-observe as "looked and found
// nothing" — which marks a containment the user ordered as done — so a failed
// enumeration withholds the key and the host holds the order.
func TestTheBeatWithholdsAgentsWhenTheWalkFails(t *testing.T) {
t.Setenv("OLLAMA_ADDR", "127.0.0.1:1")
prev := scanProcessesFn
t.Cleanup(func() { scanProcessesFn = prev })
beat := func() map[string]any {
t.Helper()
var m map[string]any
if err := json.Unmarshal([]byte(buildHeartbeatBody("", "box", "linux", "test", "pub", "fp", "", "", false, nil, nil, nil)), &m); err != nil {
t.Fatal(err)
}
return m
}
scanProcessesFn = func() (map[int]agentObs, error) { return nil, errors.New("read /proc: permission denied") }
m := beat()
if v, present := m["agents"]; present {
t.Fatalf("a failed walk travelled as %v — the host reads that as 'looked, nothing there'", v)
}
if _, present := m["agent_scan_ok"]; present {
t.Fatal("a failed walk must not claim the walk happened")
}
if _, present := m["agent_scan"]; !present {
t.Fatal("the enumerator kind must still travel: the failure is this beat's, not the platform's")
}
// The same box, the walk working: the key travels, as a list. (Not
// asserted empty — the connection scan is a second enumerator and on a
// live box it still finds model clients when the process map is empty.)
// Absent and present are the two answers, and they must stay two.
scanProcessesFn = func() (map[int]agentObs, error) { return map[int]agentObs{}, nil }
m = beat()
if _, ok := m["agents"].([]any); !ok {
t.Fatalf("a walk that happened must travel as a list, got %T %v", m["agents"], m["agents"])
}
// ...and says so: the host clears a containment only on a beat that
// carries this, so a box that cannot say it (an older build) holds.
if v, _ := m["agent_scan_ok"].(bool); !v {
t.Fatalf("a walk that happened must travel with agent_scan_ok=true, got %v", m["agent_scan_ok"])
}
}
// observedAgents — what the kill guard, the sanction page and the policy
// census consult — reads a failed walk as nothing observed: a box that cannot
// see must not kill on belief.
func TestAFailedWalkObservesNothingForTheGuard(t *testing.T) {
prev := scanProcessesFn
t.Cleanup(func() { scanProcessesFn = prev })
scanProcessesFn = func() (map[int]agentObs, error) { return nil, errors.New("boom") }
got, err := observedAgents()
if err == nil || len(got) != 0 {
t.Fatalf("a failed walk observed %v with err=%v — the seam must hand the error on", got, err)
}
if got := observedAgentMap(); len(got) != 0 {
t.Fatalf("the guard's map was %v after a failed walk", got)
}
// The error wins over anything that came back beside it: a seam stub (or
// a future enumerator) handing a partial list WITH an error must not have
// that list believed by the thing that kills.
prevObs := observedAgents
t.Cleanup(func() { observedAgents = prevObs })
observedAgents = func() ([]agentObs, error) {
return []agentObs{{PID: 4242, Kind: kindAgent, Signature: "claude-code"}}, errors.New("partial walk")
}
if got := observedAgentMap(); len(got) != 0 {
t.Fatalf("a list that came with an error was believed by the guard: %v", got)
}
}