Files
privatae-bastion/client-go/reads_slice3_test.go
T
Privatae 1176f7fd0b
ci / personal-data (push) Successful in 8s
ci / test (push) Successful in 7m17s
ci / fuzz (push) Successful in 6m33s
bastion 0.61.0
2026-11-02 06:20:42 +00:00

521 lines
20 KiB
Go

package main
import (
"bytes"
"encoding/json"
"errors"
"os"
"strings"
"testing"
"time"
)
// The switch list as JSON has every row the words have, each family of each
// paired host, and an unreadable list reads as every family off.
func TestTheSwitchListReadHasEveryRowTheWordsHave(t *testing.T) {
uiTestConfigDir(t)
const host = "box-one.example"
givenPairing(t, host, pairingActive)
if err := localDenySet(host, "ops", true); err != nil {
t.Fatal(err)
}
v, err := readSwitches(readOpts{})
if err != nil {
t.Fatal(err)
}
if len(v.Switches) != len(localSwitchFamilies()) {
t.Fatalf("want one row per family (%d), got %+v", len(localSwitchFamilies()), v.Switches)
}
var buf bytes.Buffer
printSwitches(&buf, v)
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
if len(lines) != len(v.Switches) {
t.Fatalf("%d rows, %d lines:\n%s", len(v.Switches), len(lines), buf.String())
}
for i, r := range v.Switches {
f := strings.Fields(lines[i])
if f[0] != r.Host || f[1] != r.Family || (f[2] == "off") != r.Off {
t.Errorf("row %+v printed as %q", r, lines[i])
}
if r.Family == "ops" && (!r.Off || r.Since == "") {
t.Errorf("ops was switched off: %+v", r)
}
}
// MUTATION: build rows from f.Off only, ignoring readErr. RESULT: red,
// the families read on while the gate refuses them all.
path, err := localDenyPath()
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte("{not json"), 0o600); err != nil {
t.Fatal(err)
}
v, err = readSwitches(readOpts{})
if err != nil {
t.Fatal(err)
}
if v.Unreadable == "" {
t.Error("an unreadable list is not said")
}
for _, r := range v.Switches {
if !r.Off {
t.Errorf("with the list unreadable, %s %s reads on", r.Host, r.Family)
}
}
}
// Read as JSON, the device's asks are a program's, not a person's: nothing is
// recorded as shown, so `device-approve all` signs nothing an agent read.
func TestDeviceApprovalsAsJSONRecordsNothingShown(t *testing.T) {
uiTestConfigDir(t)
p, err := deviceShownPath()
if err != nil {
t.Fatal(err)
}
if err := cmdDeviceApprovals([]string{"--json"}); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(p); err == nil {
t.Error("device-approvals --json recorded what it printed as shown")
}
if err := cmdDeviceApprovals(nil); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(p); err != nil {
t.Errorf("the words listing recorded nothing as shown: %v", err)
}
if err := cmdDeviceApprovals([]string{"--jsno"}); err == nil || !strings.Contains(err.Error(), "usage:") {
t.Errorf("a stray flag: %v", err)
}
}
// A restore request read leaves the sealed key the host sent, and the one
// resealed for it, out of the answer; their digest and the words stay.
func TestTheBackupRequestsReadLeavesTheSealedKeysOut(t *testing.T) {
uiTestConfigDir(t)
const host = "box-one.example"
givenPairing(t, host, pairingActive)
cfg, err := stepupPairing(host)
if err != nil {
t.Fatal(err)
}
const sealed, resealed = "-----BEGIN AGE ENCRYPTED FILE-----SIDECAR", "-----BEGIN AGE ENCRYPTED FILE-----RESEALED"
at := time.Now().UTC().Format(time.RFC3339)
// r1 waits, so it still carries the sidecar it came with; r2 was
// answered and carries the resealed key.
if err := resealSave(cfg.dcnDir(), map[string]*resealRequest{
"r1": {ID: "r1", Stamp: "20261101T000000Z", Pairing: host, HostNumber: 7, HostLabel: "home", To: "age1xyz",
ToFP: "fp", Sidecar: sealed, SidecarSH: "abc123", At: at, State: "pending"},
"r2": {ID: "r2", Stamp: "20261101T000000Z", Pairing: host, HostNumber: 7, HostLabel: "home", To: "age1xyz",
ToFP: "fp", SidecarSH: "def456", At: at, State: "done", Resealed: resealed},
}); err != nil {
t.Fatal(err)
}
stored, err := resealRead(cfg.dcnDir())
if err != nil || stored["r1"] == nil || stored["r1"].Sidecar != sealed || stored["r2"] == nil || stored["r2"].Resealed != resealed {
t.Fatalf("the fixture does not hold both sealed keys: %+v %v", stored, err)
}
// MUTATION: build the view from *resealRequest itself. RESULT: red, both
// sealed keys are in the answer.
v, err := readBackupRequests(readOpts{Host: host})
if err != nil {
t.Fatal(err)
}
b, _ := json.Marshal(v)
if strings.Contains(string(b), "SIDECAR") || strings.Contains(string(b), "RESEALED") {
t.Errorf("the read carries a sealed key: %s", b)
}
if len(v.Requests) != 2 || v.Requests[0].SidecarSHA == "" || !strings.Contains(v.Requests[0].Words, "home") {
t.Errorf("the requests as read: %+v", v.Requests)
}
}
// The backup key read carries the public halves only.
func TestTheBackupKeysReadHasNoSecretHalf(t *testing.T) {
b, err := json.Marshal(backupKeysRead{Keys: []*backupKeyFile{{Identity: "AGE-SECRET-KEY-1SECRET", Recipient: "age1pub",
Fingerprint: "fp"}}, Current: "fp"})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(b), "AGE-SECRET-KEY") {
t.Errorf("the backup keys read carries the secret: %s", b)
}
}
// agents show takes a pid, or a session name that matches exactly one, the
// rule stop keeps.
func TestAgentsShowPicksExactlyOneAgent(t *testing.T) {
views := []agentView{
{agentObs: agentObs{PID: 101, Signature: "claude-code"}, Self: &agentSelf{Name: "shop"}},
{agentObs: agentObs{PID: 102, Signature: "claude-code"}, Self: &agentSelf{Name: "twin"}},
{agentObs: agentObs{PID: 103, Signature: "aider"}, Self: &agentSelf{Name: "twin"}},
}
if v, err := pickAgentView(views, "101"); err != nil || v.PID != 101 {
t.Errorf("by pid: %+v %v", v, err)
}
if v, err := pickAgentView(views, "shop"); err != nil || v.PID != 101 {
t.Errorf("by name: %+v %v", v, err)
}
if _, err := pickAgentView(views, "twin"); err == nil || !strings.Contains(err.Error(), "give the pid") {
t.Errorf("two sessions with one name: %v", err)
}
if _, err := pickAgentView(views, "999"); err == nil {
t.Error("a pid no agent has was shown")
}
if _, err := pickAgentView(views, "nobody"); err == nil {
t.Error("a name no session has was shown")
}
if err := cmdAgents([]string{"show"}); err == nil || !strings.Contains(err.Error(), "usage:") {
t.Errorf("show with no agent: %v", err)
}
if err := cmdAgents([]string{"show", "--json"}); err == nil || !strings.Contains(err.Error(), "usage:") {
t.Errorf("show --json with no agent: %v", err)
}
}
// The detail view names the file the kernel says the agent runs, and keeps
// its command line (which can carry a key) out of the words.
func TestAgentsShowKeepsTheCommandLineOutOfTheWords(t *testing.T) {
v := agentView{agentObs: anObservedAgent("aider", "/usr/local/bin/aider --api-key sk-THEKEY", 4242)}
var b bytes.Buffer
printAgentDetail(&b, v, time.Now())
out := b.String()
if strings.Contains(out, "sk-THEKEY") {
t.Errorf("the words carry the command line:\n%s", out)
}
if !strings.Contains(out, "/usr/local/bin/aider") || !strings.Contains(out, "4242") {
t.Errorf("the words do not name the program and pid:\n%s", out)
}
}
// The file an agent runs is a path whoever placed it chose, so a path built
// of terminal control sequences is shown as such, never sent to the terminal.
//
// MUTATION: row("program", v.Exe). RESULT: red, the escape reaches the words.
func TestAgentsShowNeverPrintsControlCharactersFromTheProgramPath(t *testing.T) {
v := agentView{agentObs: anObservedAgent("claude-code", "claude", 4242)}
v.Exe = "/tmp/\x1b]52;c;cHduZWQ=\x07/\x1b[2Jclaude"
var b bytes.Buffer
printAgentDetail(&b, v, time.Now())
out := b.String()
if strings.ContainsAny(out, "\x1b\x07") {
t.Errorf("the words carry the program path's control characters: %q", out)
}
if !strings.Contains(out, `\x1b]52`) {
t.Errorf("the program path is not shown as what it is: %q", out)
}
v.Exe = "/usr/local/bin/claude"
b.Reset()
printAgentDetail(&b, v, time.Now())
if !strings.Contains(b.String(), "program: /usr/local/bin/claude\n") {
t.Errorf("a plain path is not shown as is: %q", b.String())
}
}
// A read is built in the root daemon now, from code that ran only in a
// command's own process before (readOps slices names it finds on disk). One
// that panics is that read's failure, and the daemon serving every pairing
// stays up.
//
// MUTATION: drop the recover in runMachineRead. RESULT: red, the suite
// itself panics.
func TestAReadThatPanicsFailsAndLeavesTheDaemonUp(t *testing.T) {
uiTestConfigDir(t)
laneOwner(t, 1000)
for _, heavy := range []bool{false, true} {
machineReads["test-panics"] = machineRead{heavy: heavy, build: func(readOpts) (any, error) {
var s []string
return s[3], nil
}}
r := serveRead(1000, "sam", agentAskRequest{Op: "read", Read: "test-panics"})
if r.OK || !r.Failed || !strings.Contains(r.Why, "test-panics") {
t.Errorf("a read that panics (heavy %v): %+v", heavy, r)
}
}
delete(machineReads, "test-panics")
if r := serveRead(1000, "sam", agentAskRequest{Op: "read", Read: "status"}); !r.OK {
t.Errorf("after a read panicked, status: %+v", r)
}
}
// doctor from an ordinary account beside a Windows system service would check
// that account's own empty bastion and report it as the machine's (not
// running, no owner, no factor), so it is refused there as status is; on
// Linux and macOS it reads the machine's bastion through the lane.
//
// MUTATION: drop "doctor" from serviceGuardedCommands. RESULT: red on windows.
func TestDoctorBesideAWindowsServiceIsRefusedAsStatusIs(t *testing.T) {
if err := serviceGuard("windows", "doctor", true, false); err == nil {
t.Error("doctor from an ordinary account beside a Windows service was let through")
}
for _, goos := range []string{"linux", "darwin"} {
if err := serviceGuard(goos, "doctor", true, false); err != nil {
t.Errorf("%s: doctor beside the service is a lane read: %v", goos, err)
}
}
if err := serviceGuard("windows", "doctor", true, true); err != nil {
t.Errorf("the administrator's doctor beside a Windows service: %v", err)
}
}
// doctorWorld is a machine doctor reads from fixtures only.
func doctorWorld(t *testing.T, system bool, latest string) {
t.Helper()
uiTestConfigDir(t)
prevSys, prevLatest, prevSandbox := systemInstalled, doctorLatest, doctorSandbox
t.Cleanup(func() { systemInstalled, doctorLatest, doctorSandbox = prevSys, prevLatest, prevSandbox })
systemInstalled = func() bool { return system }
doctorLatest = func() string { return latest }
doctorSandbox = func() (bool, string) { return true, "" }
}
func doctorCheckNamed(d doctorRead, name string) (doctorCheck, bool) {
for _, c := range d.Checks {
if c.Name == name {
return c, true
}
}
return doctorCheck{}, false
}
// With no second factor, doctor says what cannot be approved and the command
// that sets one up; with one, it is not a problem.
func TestDoctorFindsAMissingSecondFactorAndNamesTheFix(t *testing.T) {
doctorWorld(t, false, "")
d, err := readDoctor(readOpts{})
if err != nil {
t.Fatal(err)
}
c, ok := doctorCheckNamed(d, "second factor")
if !ok || c.OK || !strings.Contains(c.Fix, "stepup-enroll --machine") || strings.Contains(c.Fix, "sudo") {
t.Errorf("no factor, a per-account install: %+v", c)
}
systemInstalled = func() bool { return true }
d, _ = readDoctor(readOpts{})
if c, _ := doctorCheckNamed(d, "second factor"); !strings.HasPrefix(c.Fix, "sudo -H privatae-bastion stepup-enroll") &&
!strings.Contains(c.Fix, "Administrator") {
t.Errorf("no factor, installed for the machine: %+v", c)
}
dir, err := machineFactorDir()
if err != nil {
t.Fatal(err)
}
enrolFactorAt(t, dir, machineTestSecret)
d, _ = readDoctor(readOpts{})
if c, _ := doctorCheckNamed(d, "second factor"); !c.OK {
t.Errorf("with a factor enrolled: %+v", c)
}
}
// A newer published version is a problem with the installer as its fix; a
// latest version nobody could read is not one.
func TestDoctorSaysANewerVersionAndNeverAlarmsOffline(t *testing.T) {
doctorWorld(t, false, "999.0.0")
prev := clientVersion
t.Cleanup(func() { clientVersion = prev })
clientVersion = "1.0.0"
d, _ := readDoctor(readOpts{})
c, _ := doctorCheckNamed(d, "version")
if c.OK || !strings.Contains(c.Problem, "999.0.0") || !strings.Contains(c.Fix, "privatae.ai/bastion/install") {
t.Errorf("a newer version: %+v", c)
}
doctorLatest = func() string { return "" }
d, _ = readDoctor(readOpts{})
if c, _ := doctorCheckNamed(d, "version"); !c.OK {
t.Errorf("no answer from privatae.ai reads as a problem: %+v", c)
}
doctorLatest = func() string { return "1.0.0" }
d, _ = readDoctor(readOpts{})
if c, _ := doctorCheckNamed(d, "version"); !c.OK {
t.Errorf("the latest version reads as a problem: %+v", c)
}
}
// Installed for the whole machine with no owner recorded, doctor says what
// that stops and how to record one; recorded, it names the account.
func TestDoctorFindsNoOwnerAccountOnAMachineInstall(t *testing.T) {
doctorWorld(t, true, "")
d, _ := readDoctor(readOpts{})
c, ok := doctorCheckNamed(d, "owner")
if !ok || c.OK || !strings.Contains(c.Fix, "approvals-owner") {
t.Errorf("no owner: %+v", c)
}
laneOwner(t, 1000)
d, _ = readDoctor(readOpts{})
if c, _ := doctorCheckNamed(d, "owner"); !c.OK || !strings.Contains(c.Detail, "sam") {
t.Errorf("an owner recorded: %+v", c)
}
systemInstalled = func() bool { return false }
d, _ = readDoctor(readOpts{})
if _, ok := doctorCheckNamed(d, "owner"); ok {
t.Error("a per-account install was checked for a machine owner")
}
}
// An account whose agents' asks are held is named, with the command that
// lifts the hold.
func TestDoctorNamesAHeldAsker(t *testing.T) {
doctorWorld(t, false, "")
dir, err := machineFactorDir()
if err != nil {
t.Fatal(err)
}
enrolFactorAt(t, dir, machineTestSecret)
now := time.Now()
for i := 0; i < quarantineStrikes; i++ {
askStrike(dir, "lee", now)
}
d, _ := readDoctor(readOpts{})
c, ok := doctorCheckNamed(d, "held asker")
if !ok || c.OK || !strings.Contains(c.Problem, "lee") || !strings.Contains(c.Fix, "quarantine clear --asker lee") {
t.Errorf("a held asker: %+v (checks %+v)", c, d.Checks)
}
}
// doctor exits 1 when it finds a problem, so a script can branch, and the
// lane not answering becomes the check rather than an error.
func TestDoctorExitsOneOnAProblemAndSaysTheLane(t *testing.T) {
doctorWorld(t, false, "")
err := cmdDoctor(nil)
var q quietExit
if !errors.As(err, &q) || q.code != 1 {
t.Errorf("no factor: want exit 1, got %v", err)
}
if err := cmdDoctor([]string{"--jsno"}); err == nil || !strings.Contains(err.Error(), "usage:") {
t.Errorf("a stray flag: %v", err)
}
if c := laneCheck(&laneError{down: true, why: "nothing answers"}); c.OK || c.Name != "running" || c.Fix == "" {
t.Errorf("the lane down: %+v", c)
}
if c := laneCheck(&laneError{why: "this machine's bastion answers reads only for its owner's account (sam), not lee"}); c.OK ||
c.Name != "reads" || !strings.Contains(c.Fix, "doctor") {
t.Errorf("the lane refusing: %+v", c)
}
}
func TestTheSliceThreeReadCommandsRefuseStrayFlags(t *testing.T) {
for name, cmd := range map[string]func([]string) error{
"switch --list": func(a []string) error { return cmdSwitch(append([]string{"--list"}, a...)) },
"backup key show": func(a []string) error {
return cmdBackup(append([]string{"key", "show"}, a...))
},
"backup requests": func(a []string) error { return cmdBackup(append([]string{"requests"}, a...)) },
"backup shares": func(a []string) error {
return cmdBackup(append([]string{"shares", "--with", "h"}, a...))
},
} {
if err := cmd([]string{"--jsno"}); err == nil || !strings.Contains(err.Error(), "usage") {
t.Errorf("%s --jsno: %v", name, err)
}
}
}
// Not paired, the command that pairs is said the way it is typed here:
// with sudo -H where the bastion is installed for the whole machine.
func TestNotPairedSaysThePairCommandForThisInstall(t *testing.T) {
doctorWorld(t, true, "")
_, err := stepupPairing("")
if err == nil || !strings.Contains(err.Error(), asAdmin("pair <token>")) {
t.Errorf("not paired, a machine install: %v", err)
}
if runtimeIsUnix() && !strings.Contains(err.Error(), "sudo -H privatae-bastion pair") {
t.Errorf("not paired, a machine install, says no sudo: %v", err)
}
systemInstalled = func() bool { return false }
if _, err := stepupPairing(""); err == nil || strings.Contains(err.Error(), "sudo") {
t.Errorf("not paired, a per-account install: %v", err)
}
}
func runtimeIsUnix() bool { return os.PathSeparator == '/' }
// doctor asks privatae.ai at update.go's rate: once, then not again within
// updateCheckEvery, whether that ask was answered or not; the last answer
// stands meanwhile. MUTATION: drop the doctorAskedLoad shortcut. RESULT: red,
// every run fetches.
func TestDoctorAsksPrivataeAtTheUpdateCheckRate(t *testing.T) {
uiTestConfigDir(t)
prevFetch, prevNow := doctorFetch, doctorNow
t.Cleanup(func() { doctorFetch, doctorNow = prevFetch, prevNow })
forget := func() { latestMu.Lock(); latestPublished = ""; latestMu.Unlock() }
t.Cleanup(forget)
now := time.Date(2026, 11, 2, 0, 0, 0, 0, time.UTC)
doctorNow = func() time.Time { return now }
fetches, answer := 0, "0.61.0"
doctorFetch = func() (string, error) {
fetches++
if answer == "" {
return "", errors.New("no route")
}
return answer, nil
}
forget()
if v := doctorLatest(); v != "0.61.0" || fetches != 1 {
t.Fatalf("first run: %q after %d fetches", v, fetches)
}
forget() // a new process: nothing in memory
now = now.Add(updateCheckEvery - time.Minute)
if v := doctorLatest(); v != "0.61.0" || fetches != 1 {
t.Errorf("inside the window: %q after %d fetches", v, fetches)
}
forget()
now = now.Add(2 * time.Minute)
answer = ""
if v := doctorLatest(); v != "0.61.0" || fetches != 2 {
t.Errorf("past the window, no answer: %q after %d fetches (the last answer should stand)", v, fetches)
}
forget()
now = now.Add(time.Hour)
if v := doctorLatest(); v != "0.61.0" || fetches != 2 {
t.Errorf("a failed ask is not repeated inside the window: %q after %d fetches", v, fetches)
}
}
// A folder the agent chose, holding an escape sequence, is printed quoted in
// agents and agents show, never sent to the terminal raw.
func TestAnAgentsFolderNeverReachesTheTerminalRaw(t *testing.T) {
v := agentView{agentObs: anObservedAgent("aider", "/usr/local/bin/aider", 4242),
Folder: "/home/u/\x1b]52;c;cGF3bmVk\x07work", GitBranch: "main"}
var b bytes.Buffer
printAgents(&b, []agentView{v}, time.Now())
printAgentDetail(&b, v, time.Now())
if strings.ContainsAny(b.String(), "\x1b\x07") {
t.Errorf("a control character reached the output: %q", b.String())
}
if !strings.Contains(b.String(), `\x1b]52`) {
t.Errorf("the folder is not shown at all: %q", b.String())
}
}
// On Windows, where nothing goes over the socket, the slice-3 reads from an
// ordinary account beside the service are refused as status is; on Linux and
// macOS they ask the machine's bastion. The rest of backup is not guarded.
func TestTheSliceThreeReadsAreGuardedBesideAWindowsService(t *testing.T) {
for _, line := range [][]string{
{"privatae-bastion", "ops", "--json"},
{"privatae-bastion", "device-approvals"},
{"privatae-bastion", "backup", "requests"},
{"privatae-bastion", "backup", "shares", "--with", "h"},
{"privatae-bastion", "backup", "key", "show"},
} {
key := serviceGuardKey(line[1], line)
if err := serviceGuard("windows", key, true, false); err == nil {
t.Errorf("windows: %v from an ordinary account beside the service was let through", line[1:])
}
for _, goos := range []string{"linux", "darwin"} {
if err := serviceGuard(goos, key, true, false); err != nil {
t.Errorf("%s: %v was refused before it could ask the machine's bastion: %v", goos, line[1:], err)
}
}
}
for _, line := range [][]string{
{"privatae-bastion", "backup", "reseal", "--sidecar", "f", "--to", "k"},
{"privatae-bastion", "backup", "restore-plan", "--dir", "d"},
} {
if err := serviceGuard("windows", serviceGuardKey(line[1], line), true, false); err != nil {
t.Errorf("windows: %v works offline and was refused: %v", line[1:], err)
}
}
}