srvgov-cli
v0.8.0
Published
Governed remote server command execution CLI for AI agents
Readme
srvgov-cli
Governed remote server command execution for AI agents and operators. srvgov
combines fail-closed command classification, R0-R3 authorization, strict TOFU
SSH host-key pinning, output redaction, and structured audit records.
Install
npm install -g srvgov-cli
# or
go install github.com/JiangHe12/srvgov-cli@latestGitHub Releases provide Linux, macOS, and Windows binaries for amd64 and arm64. The npm package downloads the matching release binary and verifies SHA-256 checksums by default.
Quickstart
srvgov ctx set dev --server ssh://[email protected]:22 --identity-file ~/.ssh/id_ed25519 -o json
srvgov ctx use dev -o json
srvgov status -o json
srvgov ports -o json
srvgov logs --unit sshd --since "1 hour ago" --lines 50 -o json
srvgov svc status sshd -o json
srvgov file stat /etc/hosts -o json
srvgov docker list -o json
srvgov exec --dry-run "uptime" -o json
srvgov exec "uptime" -o json
srvgov audit query --limit 20 -o jsonUse -o json for automation and AI agents.
Governance Model
| Risk | Meaning | Authorization |
|---|---|---|
| R0 | known read-only command | free to run, still audited |
| R1 | known benign change | --reason and --yes |
| R2 | unknown or elevated command | --reason, non-empty --ticket, and --yes |
| R3 | destructive, privileged, dynamic, or parser-uncertain command | --reason, --ticket, --allow-destructive, and --yes |
Protected contexts raise R1 to R2 and R2 to R3. AI agents must never auto-fill
--ticket, --allow-destructive, or high-risk --yes. Use exec --dry-run
to obtain the classifier's risk and required authorization; do not guess impact.
Contexts
srvgov ctx set prod \
--server ssh://[email protected]:22 \
--identity-file ~/.ssh/id_ed25519 \
--auth-method private-key,agent,password \
--env production \
--label env=prod \
--label role=web \
--protected \
-o json
srvgov ctx use prod -o json
srvgov ctx current -o json
srvgov ctx list -o json
srvgov ctx delete old-host -o json
srvgov ctx role set prod --target-operator alice --role writer -o json
srvgov ctx role list prod -o json
srvgov ctx export prod > prod.ctx.yaml
srvgov ctx import -f prod.ctx.yaml --rename prod-copy --yes -o json
srvgov ctx migrate-credentials --to encrypted-file --context prod -o jsonContext output never includes passwords, private-key contents, passphrases, or identity-file paths.
ctx set accepts repeated --label key=value flags. Labels are non-secret
metadata shown by ctx list and ctx current, exported/imported with portable
contexts, and used by fanout --selector. A ctx set call replaces that
context's label set with the labels supplied in the call.
Portable context export uses srvgov.io/ctx-export/v1. Literal password and
SSH identity passphrase values are redacted by default; credstore references are
preserved. --include-credentials is limited to plain-yaml contexts.
Observe Before Acting
The observation commands turn common read-only SSH output into stable JSON:
srvgov status -o json
srvgov ports -o json
srvgov status --targets web-a,web-b --concurrency 5 -o json
srvgov logs --targets web-a,web-b --unit nginx --lines 100 -o json
srvgov logs --unit nginx --since "30 minutes ago" --priority warning --lines 100 -o json
srvgov logs --file /var/log/nginx/error.log --grep "upstream" --lines 100 -o jsonEach underlying remote command is independently classified and authorized
through the same governance path as exec; probes are never joined with shell
operators. ports falls back from ss to netstat. Unit logs fall back from
journalctl to systemctl status when journalctl is unavailable. No command
adds sudo; unavailable PID/process fields remain empty. Log text, process
names, generated command text, caller output, and audit records are redacted.
Fleet fanout
status, ports, logs, and governed action commands accept comma-separated
context names, or a label selector:
srvgov status --targets web-a,web-b,web-c --concurrency 5 -o json
srvgov ports --targets web-a,web-b,web-c -o json
srvgov logs --selector env=prod,role=web --unit nginx --lines 100 -o json
srvgov exec --targets web-a,web-b,web-c "uptime" -o json
srvgov exec --targets web-a,web-b,web-c --dry-run "systemctl restart nginx" -o json
srvgov exec --selector env=prod,role=web --dry-run "systemctl restart nginx" -o json
srvgov exec --targets web-a,web-b,web-c "systemctl restart nginx" \
--reason "restart reviewed service" --ticket OPS-123 --yes -o jsonSelectors use key=value,key2=value2 AND matching against context labels.
--targets, --selector, and --context are mutually exclusive. status,
ports, and logs remain strictly R0-only, including every fallback command
they may run. exec uses two-phase authorize-all: it classifies and
non-interactively authorizes every sorted target before any SSH execution. If
one target rejects the ticket, role, confirmation, or required allow flag, the
whole batch stops with zero partial writes. After all targets pass, execution
is concurrent and each target re-authorizes immediately before SSH. Dry-run
does not authorize or connect; it reports the resolved target set, each
target's real base/effective risk, and maxEffectiveRiskTier. Targets are
deduplicated and sorted, each target is audited independently, and one remote
execution failure does not stop the others. Any per-target execution failure
returns exit code 7 after emitting the complete result.
Service Control
svc exposes only a fixed service-operation whitelist. Unit names are treated
as literal shell words, and every generated systemctl command goes through
the same classifier and authorization path as exec.
# R0 read, still audited
srvgov svc status nginx -o json
# R2 change: human-supplied reason, ticket, and confirmation
srvgov svc restart nginx \
--reason "apply reviewed configuration" --ticket OPS-123 --yes -o jsonAvailable actions are status, start, stop, restart, reload, enable,
and disable, for one unit at a time. Protected contexts raise service changes
from R2 to R3 and additionally require human-supplied --allow-destructive.
svc does not expose power, isolate, mask, or arbitrary systemctl operations.
File Operations
File reads are structured R0 operations and remain audited:
srvgov file read /etc/hosts --max-bytes 1048576 -o json
srvgov file stat /etc/hosts -o json
srvgov file list /var/log -o jsonWrites use tee -- '<path>' with content streamed over SSH stdin. They are R2
for ordinary paths and R3 for sensitive paths such as SSH authorization files,
shell dotfiles, and crontabs.
printf '%s\n' 'enabled=true' | srvgov file write /tmp/app.conf \
--reason "update reviewed configuration" --ticket OPS-123 --yes -o json
srvgov file write /tmp/app.conf --content "enabled=true" \
--reason "update reviewed configuration" --ticket OPS-123 --yes -o jsonWithout --content, stdin is the file content and explicit --yes is required
before authorization. With --content, stdin is never read and interactive
confirmation remains available. Write output and audit records never contain
file content; audit stores only the redacted path, byte count, and SHA-256.
Writes are direct and non-atomic; temporary-file plus rename is not implemented
in this release. file never uses SFTP and never adds sudo.
Docker Governance
Docker reads provide stable, redacted structures:
srvgov docker list -o json
srvgov docker inspect api -o json
srvgov docker logs api --tail 100 -o jsondocker list, inspect, and logs are audited R0 operations. Inspect uses a
remote fixed-field projection and excludes container environment variables and
the full inspect document. Logs default to 100 lines and accept --tail
between 1 and 10000.
Lifecycle changes are R2 and require human authorization:
srvgov docker restart api \
--reason "restart after reviewed deployment" --ticket OPS-123 --yes -o jsonThe fixed whitelist contains only ps/list, inspect, logs, start,
stop, restart, and rm, one container at a time. It never exposes Docker
run, create, exec, build, copy, compose, or prune. Protected contexts raise
lifecycle changes to R3 and require human-supplied --allow-destructive.
Container identifiers are shell-quoted.
Governed Execution
Preview without connecting or executing:
srvgov exec --dry-run "touch /tmp/deploy-ready" -o jsonExecute according to the reported tier:
# R0
srvgov exec "systemctl status nginx" -o json
# R1
srvgov exec "touch /tmp/deploy-ready" \
--reason "mark deployment ready" --yes -o json
# R2
srvgov exec "custom-maintenance-command" \
--reason "scheduled maintenance" --ticket OPS-123 --yes -o json
# R3
srvgov exec "rm -rf /tmp/old-release" \
--reason "remove failed release" \
--ticket OPS-123 --allow-destructive --yes -o jsonCommands run without a PTY. stdout, stderr, command text, and audit fields are
redacted before output or persistence. A remote non-zero exit returns structured
output and process exit code 7 (BACKEND_ERROR).
SSH Trust And Credentials
The first connection to an unknown host:port pins its SSH public key in
~/.srvgov/known_hosts. Later key mismatches, including an unpinned key type
for an already known address, are rejected. Host-key rotation requires manual
review and removal of the old pin; there is no insecure bypass.
Authentication order is private key, SSH agent, then password, subject to the
context's --auth-method preference. Passwords and key passphrases may use
opskit-core credential-store references. Credentials and raw SSH output are not
logged by the transport.
Audit And Diagnostics
srvgov capabilities -o json
srvgov audit query -o json
srvgov audit query --type authorization.denied --status denied -o json
srvgov audit verify --strict -o json
srvgov audit prune --keep-last 20 -o json
srvgov doctor -o json
srvgov version -o json
srvgov --versionAudit records live at ~/.srvgov/audit.log by default and include effective
risk, authorization status, target, redacted command/output, exit code, and
error details.
capabilities reports the current command surface, srvgov.io/context/v1,
srvgov.io/audit/v1, R0-R3 authorization rules, --allow-destructive, JSONL
audit, RBAC reader/writer/admin, dry-run, strict TOFU, and redaction.
AI Skill
srvgov install claude --skills
srvgov install codex --skills
srvgov install /custom/skills/path --skillsBuild
go build ./...
go test -count=1 ./...
gofmt -l main.go cmd internal
golangci-lint run --timeout=5m
go vet -tags=integration ./...Contributing, Security, License
See CONTRIBUTING.md, SECURITY.md, and LICENSE.
