Skip to content

w745: the ops worker may run every read-only fffctl form; a test fails on an unclassified fffctl command - #244

Merged
bryding merged 7 commits into
mainfrom
w745-ops-readonly
Oct 9, 2026
Merged

bryding merged 7 commits into
mainfrom
w745-ops-readonly

Conversation

@bryding

@bryding bryding commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Request: w745

lothsahn, 2026-10-09: "I would like the orchestrator to be able to run any vault commands that read data. In fact, please audit all fffctl commands that are read only and ensure the orchestrator can run any of them. And update the documentation that anytime a new fffctl command is provided, all read only parts of it should be allowed by the orchestrator worker"

What

  • fff-ops-priv (root wrapper) gains vault list [--names], vault help, machine-credential list, migrate --help, each with its arguments pinned (anything else exits 2 before fffctl runs).
  • server/opsWorker.ts: FFFCTL_FORMS, one table of every fffctl command with its allowed (read-only) and changes forms; OPS_FFFCTL is derived from it. The shell guard also checks the form word of vault, machine-credential and migrate.
  • server/opsFffctlForms.test.ts: reads fffctl, server/vaultCli.ts and scripts/fff-migrate.ts and fails when a command or form is not classified, when the table lists one that is gone, or when the guard and fff-ops-priv disagree.
  • Rule as lothsahn wrote it: docs/ops-worker.md ("A new fffctl command" and the "It may" table), CONTRIBUTING.md, the fffctl header comment.

Audit (fffctl and what it runs)

command / form read-only why allowed before allowed now
status, state yes reads systemd, tailscale, health, files yes yes
help yes prints the header yes yes
units, units --check yes reads unit state yes (#243) yes
logs [N] yes the worker's own logs N is journalctl -n N, redacted. fffctl's logs -f follows and never ends logs N logs N (-f is not in the wrapper)
vault list, vault list --names yes prints name, kind, env, last four characters, a 12-character hash fingerprint, grants and the Claude pools' meters from data/usage.json and state.json; never a value no yes
vault help yes prints lines 2-15 of vaultCli.ts no yes
vault (no word) yes (it is list) no no: the form is spelled out
vault init, init --new-key no writes the key, restarts the portal no no
vault export-key no copies the vault key to a file no no
vault new-key, add, put, rotate, grant, remove no write the vault or the key; add, put, rotate take a value no no
vault get or any form that prints a value does not exist the vault code never prints one; if it ever gets one it is not read-only n/a refused (not a form)
machine-credential list yes prints machine ids as credential list yes, under both names
machine-credential issue no creates a credential file only as credential issue ID --to TARGET (w676, with a machine record) unchanged
machine-credential revoke no cuts a machine off no no
migrate --help yes parseArgs prints the usage and exits 2 no yes
migrate --key no ssh to BEAST with StrictHostKeyChecking=accept-new: it trusts and writes a new host key to known_hosts no no
migrate --dry-run-copy, --rollback-dry-run, --cut-over no copy, swap or move the portal no no
watchdog run no runs the watchdog now; it can restart a unit no no
watchdog pause, resume, reset no write or delete the pause file and counters no no
restart, rollback, prepare-shutdown, start no stop or start the portal no no
update no builds and switches the release with the deploy grant only unchanged
claude-token, claude-login, tailscale-join, gh-login, base-clone no store a secret, log in, join the tailnet, clone no no
backup, backup-config, configure no run or change the backup, change config.json and restart no no
fff-vm (status, snapshots, events, watch status are read-only) n/a runs on the FFBox host as root. The portal's ssh reaches only machines.ssh (m3, m5, Loth2800, beast, biscuit): no path from the VM not reachable unchanged

Readers of the vault list see last four characters and a 12-character sha256 prefix per entry: what vault add already prints and what credential issue prints for a credential. One caveat: Vault.reload reads through readJsonDurable, which repairs a corrupt vault.json from its backup when it finds one. That is the portal's own read behaviour, not a new write path.

Evidence

  • Measured: node --test server/opsFffctlForms.test.ts server/opsWorker.test.ts 18/18, tsc -p . clean. The new classification test failed on a planted newthing command, a planted vault peek case and a planted restart case in fff-ops-priv, and passed again after I removed them.
  • Measured: the new fff-ops.test.sh block (5 allowed forms, 28 refused) ran green on macOS against a copy of the guard without its root check; CI runs it as root.
  • Not run: the real fffctl vault list on the VM (unchanged code). No token value appears in the code, the tests or this description.

Deploy

Scripts and server only; sudoers unchanged. A portal deploy (fffctl update) carries it. The host install.sh --guest-only adds nothing. Another worker is changing the vault scripts (w744); this PR touches none of vault.sh, vaultCli.ts or fffctl's code, only fffctl's header comment.

bryding and others added 7 commits October 9, 2026 00:39
… machine-credential list, migrate --help); a test fails on an unclassified fffctl command

Request: w745

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…y) case in fff-ops-priv

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… it now checks the read-only forms work and the changing ones are refused

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ci-env is gone by then)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant