Skip to content

fix(cli): reject ignored root flags and validate capabilities on every mutating command #91

Closed

Description

@entelecheia

Context

Audit of the CLI on 2026-11-09. Root persistent flags are accepted everywhere but read by few commands, and two command groups skip the capability gate.

  • verified: fleet apply --module ssh applies every module; fleet.Apply never reads --module (internal/cli/fleet.go:247). All fleet commands also accept and ignore --profile, --config, --force, --tunnel-token.
  • verified: --module is ignored by status and doctor; --force is only read by the ssh module and reused by config init to mean "overwrite file".
  • verified: tunnel and gpu mutations build their context through loadRunContext (tunnel.go:202) without ValidateCapabilities, although docs/rocky-support.md marks gpu and cloudflared unsupported on Rocky and AGENTS.md requires rejection before changes. It also always builds an APT backend.
  • verified: backup writes root-only archives but is not in mutatingCommands (no lock, no audit). ROOTFILES_YES is honoured by apply and tunnel but not rollback. fleet validates -o only after the SSH work.

Implementation starts only on the owner's go-ahead.

Decisions

  • A flag a command does not read is an error on that command, not silently ignored. Use the existing Visit refusal pattern from tenant ops (tenant.go:524) for fleet; move --module and --force off the root where possible.
  • Every root-mutating subcommand validates capabilities for the modules it touches before acting.

Design

  • buildRunContext(cmd, modules ...string) validates capabilities for the given modules and picks the package manager by distro (NewPackageManager).
  • fleet rejects --module, --profile, --config, --force, --tunnel-token with a message that says where the setting belongs (inventory or remote config).
  • status/doctor: either honour --module or reject it (pick honour for status, reject for doctor).
  • config init --overwrite; --force kept there as a deprecated alias.
  • Add {"backup"} to mutatingCommands; read ROOTFILES_YES in one place (yesFlag(cmd)).
  • Validate -o before connecting in fleet.

Changes

  • internal/cli/{runtime,tunnel,gpu,fleet,status,doctor,configcmd,backup,rollback,apply}.go and tests; README flag notes.

Acceptance criteria

  • AC1: rootfiles fleet apply --module ssh ... exits non-zero before any SSH connection, naming the flag.
  • AC2: on a Rocky system stub, gpu assign and tunnel setup refuse before any change with the capability message.
  • AC3: backup takes the lock and writes an audit record.
  • AC4: ROOTFILES_YES=true behaves the same on apply, tunnel, rollback.
  • AC5: fleet status -o yaml fails before connecting.
  • AC6: the fleet sudoers command strings and remote command lines are unchanged (module/fleet_sudo.go:24, internal/fleet/rollout.go).

Tasks

  • T1: capability validation in buildRunContext (AC2)
  • T2: fleet flag refusal + early -o validation (AC1, AC5, AC6)
  • T3: --module/--force scoping, config init --overwrite (deprecated alias)
  • T4: backup as mutating, yesFlag (AC3, AC4)
    • Done when: go test ./internal/cli/... -race passes.

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions