<#1122 `osctrl-cli`: make commands work in both `-...
# osctrl
g
#1122 `osctrl-cli`: make commands work in both `--db` and `--api` modes Pull request opened by javuto osctrl-cli: make commands work in both --db and API modes Problem Many
osctrl-cli
commands worked in only one mode. An operator without database access couldn't manage settings, permissions or environment configuration from the CLI, and an operator on the server couldn't manage alerts. Several API-mode paths were also broken. This builds on the CLI mode guard (#1119). Change Every command now works with
--db
and through the API wherever osctrl-api or a database manager already exposes the capability. No new API endpoints were added.
settings add
and
settings delete
stay database-only, because the API has no create or delete for settings. No command is API-only any more; a test enforces both. Now work in both modes • Settings:
settings show
and
settings update
. • API mode sends only the typed field for
--type
, and
--info
is an error in API mode. • The database path now refuses a type mismatch and applies the same range checks as the API for
inactive_hours
and
audit_log_retention_days
. An unknown
--type
is now an error instead of a silent no-op. • The shell's settings commands use the same checks, and its
settings update
no longer creates missing settings. • Permissions:
permission show
,
all
,
reset
and
change
. •
change
is additive in both modes. • The lockout guards (no self-demotion, no demoting the last super-admin) are shared between the API handlers and the database path through
users.CheckLockout
and
users.IsSelfDemotion
. • The API handlers answer exactly as before, in every case. • In database mode self-demotion is refused only for global super-admins, and
change
runs no guard because it only adds access. • Environment configuration: scheduled queries, osquery options, packs, pack queries and posture queries. • Both modes use the same pure edit functions and store identical JSON for identical input. • API-mode edits are read-modify-write and not atomic, and the command help says so. • Edits keep any keys the code doesn't model, in schedules as well as packs. • Database mode now writes the same audit entry the API writes for every edit. • Environments:
env add
,
update
,
delete
and
new-flags
. • API-mode
env add
follows up to match database mode. If a step fails, the environment is left in place and the error names the step. •
env update
no longer resets
--debug
or
--enroll
when they are omitted, and
--debug=false
now works. •
env update
regenerates the stored flags only when a plugin flag,
--hostname
or an interval is given, which keeps the provision and docker scripts working. It resets plugin choices to defaults. •
env delete
now asks you to type the environment name. Skip the prompt with
--yes
or
--silent
. • Database-mode
new-flags
,
rotate-enroll
and
rotate-remove
now audit like the API. • Alerts in
--db
mode:
all seven alert commands. • The alert and service-command managers are built lazily and never by the shared setup. • Channel secrets are redacted like the API (
secret
and
password
), and a created channel's config is never echoed. • Audit text matches the API's, and
alert apply
queues the TLS reload. •
rule-create
gains
--environment
,
--node-uuid
,
--status-severity
,
--vuln-min-severity
and
--info
, and
channel-create
gains
--environment
and
--info
. • Database mode prints a note for
vuln_finding
rules, because it can't check the vulnerability setting. Bugs fixed on the way • URLs built with
path.Join
lost the host in every enroll and remove action and in
audit-logs --api
. • Every API-mode enroll and remove action returned 500 because the client sent no JSON body. •
query list
,
carve list
and
audit-logs
couldn't parse the API's paged responses. They now follow every page. •
tag add
dropped the custom value. •
carve complete
,
delete
and
expire
called the query endpoints in API mode. •
rotate-enroll
extended instead of rotating. • Database-mode
user delete
failed on permissions and left orphaned permission and MFA rows. • Adding a query to a pack panicked and rewrote the pack without the keys the code doesn't model. A re-add now replaces the query's modelled fields. • Wrong success messages. • The MCP
list_queries
tool couldn't parse the API's response. It now works and is capped (
limit
default 50, max 200, with a
truncated
flag). • Validation errors that used to print and call
os.Exit
now return errors. The CLI prints them to stderr with exit 1, and the leading
❌
is no longer duplicated. Behavior notes •
carves list
lists carve queries with
--api
and carved files with
--db
. This is intentional, and the help says so. • In API mode, the server can still refuse a permission
change
whose merged result has admin false for the caller's own user. This is existing server behavior and was not changed. • Database-mode CLI edits cannot invalidate the shared Redis environment cache, so the API and TLS services can serve the old environment for up to 5 minutes (60 minutes while the database is degraded). • Database-mode
extend
and
expire
for enroll and remove still write no audit entry, unlike the API. • The alert channel list shows webhook URLs in clear, as the API does. Only the webhook signing secret and the email password are redacted. • Release note:
osctrl-cli env update
no longer resets omitted
--debug
or
--enroll
. Stored flags are regenerated only when a plugin flag,
--hostname
or an interval is given.
env delete
now asks for the environment name. Security and operational impact • No new endpoints and no API permission changes. • The permission handlers' status codes, texts and order of checks are unchanged for every caller. A caller admitted only through an admin permission row on an empty environment is still refused self-demotion. • The CLI's
user delete
doesn't refuse deleting your own user. • Scripts:
deploy/provision.sh
and the docker entrypoints still work and never reach the new prompt. Validation •
go test ./...
,
go vet ./...
,
gofmt -l cmd pkg
,
golangci-lint run ./...
and `make openapi-check`: clean. • A test rig runs the same command through the managers and through the real API handlers on one SQLite database. A drift-guard test fails if its copied routes stop matching
cmd/api/main.go
. • Parity tests compare the stored result, the output and the audit rows across both modes. Further tests cover 403 responses, the lockout guards, malformed stored sections, the delete confirmation, secret redaction and the MCP cap. • Not tested: a staging run with a real osctrl and a global-admin token, comparing
--db
and API results for settings, permissions, environments, a schedule edit and the alert commands. jmpsec/osctrl