GitHub
10/26/2026, 7:01 PMosctrl-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/osctrlGitHub
10/26/2026, 7:07 PM