Files
emcli/internal/cli/security_invariant_test.go
T
steve 2140d9e173 feat(cli): agent-readable account list (reduced JSON view)
account list now routes to the agent role; an agent (EMCLI_KEY only) gets a
JSON envelope of name/from/can_send, while the admin keeps the full text
table. account add/edit/remove stay admin-only.

Also emit the agent path's missing-key/open failure as a JSON Failure
envelope (per spec), and update the stale run_test case that asserted the
old admin-only behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:37:37 +01:00

67 lines
1.6 KiB
Go

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, err := store.Open(db)
if err != nil {
t.Fatalf("store.Open: %v", err)
}
ak, err := crypto.AdminKeyFromEnv()
if err != nil {
t.Fatalf("AdminKeyFromEnv: %v", err)
}
gk, err := crypto.AgentKeyFromEnv()
if err != nil {
t.Fatalf("AgentKeyFromEnv: %v", err)
}
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", "add", "--name", "x", "--imap-host", "h", "--username", "u@x.com"},
{"config", "set", "audit_retention_days", "30"},
{"audit"},
}
for _, args := range adminAttempts {
code, out, errOut := run(t, args...)
if code == 0 {
t.Errorf("admin command %v must be refused with only EMCLI_KEY (out=%q err=%q)", args, out, errOut)
continue
}
}
if !bytes.Equal(before, dbBytes(t, db)) {
t.Fatal("DB changed despite all admin commands being refused")
}
}