From 5c7dd252db827a04b718cfa8ab90ec3b5608c4e2 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Mon, 22 Jun 2026 23:03:17 +0100 Subject: [PATCH] test(cli): prove agent key cannot run admin commands Initialize a DB, drop EMCLI_ADMIN_KEY, attempt every admin command with only EMCLI_KEY: each is refused and the DB is byte-for-byte unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/cli/security_invariant_test.go | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 internal/cli/security_invariant_test.go diff --git a/internal/cli/security_invariant_test.go b/internal/cli/security_invariant_test.go new file mode 100644 index 0000000..c138fad --- /dev/null +++ b/internal/cli/security_invariant_test.go @@ -0,0 +1,56 @@ +package cli + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "git.dcglab.co.uk/steve/emcli/internal/crypto" + "git.dcglab.co.uk/steve/emcli/internal/store" +) + +func dbBytes(t *testing.T, path string) []byte { + t.Helper() + b, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read db: %v", err) + } + return b +} + +// A forced agent holding ONLY EMCLI_KEY must not be able to run any admin +// command, and the DB must be unchanged after it tries. +func TestAgentKeyCannotRunAdminCommands(t *testing.T) { + db := filepath.Join(t.TempDir(), "emcli.db") + t.Setenv("EMCLI_ADMIN_KEY", b64Key()) + t.Setenv("EMCLI_KEY", b64AgentKey()) + t.Setenv("EMCLI_DB", db) + + st, _ := store.Open(db) + ak, _ := crypto.AdminKeyFromEnv() + gk, _ := crypto.AgentKeyFromEnv() + if err := st.InitKeys(ak, gk); err != nil { + t.Fatalf("InitKeys: %v", err) + } + st.Close() + + // Simulate the agent's environment: admin key absent. + t.Setenv("EMCLI_ADMIN_KEY", "") + + before := dbBytes(t, db) + adminAttempts := [][]string{ + {"account", "list"}, + {"config", "set", "audit_retention_days", "30"}, + {"audit"}, + } + for _, args := range adminAttempts { + code, out, errOut := run(t, args...) + if code == 0 { + t.Fatalf("admin command %v must be refused with only EMCLI_KEY (out=%q err=%q)", args, out, errOut) + } + } + if !bytes.Equal(before, dbBytes(t, db)) { + t.Fatal("DB changed despite all admin commands being refused") + } +}