Server Command Allowlist Management
- Feature:
server-command-allowlist-management - Owner: server feature implementation
Intent
Operators need a controlled server-side workflow to grant Stary exec_cmd capabilities to selected devices without weakening deny-by-default enforcement. The feature adds authoritative command policy authoring, grouping, assignment, and client delivery so script deployment permissions are explicit, auditable, and revocable.
Requirements
- Treat one command entry as the atomic record: one stable command name mapped to one absolute executable path.
- Allow operators to create and maintain command entries with:
- command name (stable identifier)
- absolute executable path
- optional metadata/description
- Group command entries with first-class category tags. Categories have a slug/display name/description and command entries may carry multiple categories.
- Device assignment may select individual command entries and/or category tags; category resolution selects enabled command entries with matching tags.
- Enforce validation rules for entries:
- command name must be present, normalized, case-insensitively unique, and limited to lowercase letters, digits,
_,-, and. - path must be absolute and syntactically valid without whitespace or shell metacharacters
- reject shell fragments, empty values, and duplicate command names with different paths in the same resolved policy
- allow duplicate absolute paths under different command names
- command name must be present, normalized, case-insensitively unique, and limited to lowercase letters, digits,
- Assign command entries or categories to selected approved devices.
- Show the resolved effective policy for each target device before assignment and require confirmation before enqueueing delivery.
- Deliver policy to clients through the existing heartbeat command pipeline using an
apply_command_policycommand. - Persist policy versioning and assignment state for audit and rollback.
- Respect device authorization boundaries for visibility and mutation.
Constraints
exec_cmdremains deny-by-default when no allowlist applies.- Server cannot authorize shell strings; only explicit command names and absolute paths are in scope.
- Additive composition must surface conflicts (same command name -> different path).
- Policy assignment and usage are admin/operator-level capabilities, narrowed by device-scope claims.
- Viewers may see policy status for visible devices, but full command paths and global inventory are operator/admin-only in v1.
- No cross-feature UI changes to script testing/deployment behavior beyond required integration.
Non-goals
- Interactive shell proxying or general interactive command execution.
- Argument-level sandboxing/validation in the first increment unless already present in client runtime.
- Automatic inference of required commands by scanning script bodies.
- Replacing existing client-side enforcement model; policy application must remain bounded by client checks.
Security and failure boundaries
- Server-side allowlist records must be auditable and enforce explicit actor identity/time.
- Device-scoped authorization prevents operators from modifying policies for unauthorized devices.
- Offline and duplicate assignment scenarios must not break existing command semantics (idempotent delivery semantics required).
- Delivery failures should be visible and retriable.
- The persisted assignment snapshot is the source of truth for what the server intended to deliver; the client acknowledgement is evidence of durable application, not authorization by itself.
- Failed assignments keep the last acknowledged client policy active; they do not implicitly deny all or partially apply non-conflicting commands.
- Removing an assignment must enqueue a higher-revision replacement snapshot recalculated from remaining assignments; empty policy means deny all when nothing remains.
Data model
- Command Entry
- name
- description
- absolute command path
- immutable version/revision per security-relevant save
- archived/read-only state instead of hard-delete when referenced
- actor/timestamps
- Category
- slug used by policy resolution, lowercase and unique
- display name
- description
- referenced as tags from command entries rather than owning membership rows
- deletion blocked only while an active device assignment selects the category; otherwise deletion removes the tag from command entries after confirmation
- Device Policy Assignment
- device_id for an approved device
- assigned command entry/category refs pinned to versions
- resolved policy snapshot with immutable full paths and source provenance
- status (
pending,queued,acknowledged,failed,revoked) - optional drift warning/health flag separate from lifecycle status
- monotonically increasing per-device policy revision
- version/assignment id for audit/debug
- Policy Delivery Result
- assignment id
- command ref
- client result (delivered/acknowledged/failed)
- result payload/timestamp
- failure reason when unsupported, stale, conflicting, or persistence fails
Client/runtime contract
- First transport: use the authenticated heartbeat command pipeline (
PollResponse.Commands) to carry anapply_command_policycommand with a versioned allowlist payload. - Payload v1 content type is
application/vnd.nixstasis.command-policy+json;version=1. - Payload v1 shape is
{ "assignment_id": "uuid", "version": "human/audit version", "revision": 123, "commands": { "name": "/absolute/path" } }. - The server must queue the command with the same pending-command/result flow documented in
website/docs/data-flow.md; no new/api/v1client endpoint is needed for this increment. - Client must parse and durably persist policy outside the script directory, then install it into runtime config used by Stary execution path before acknowledging success.
- Server-delivered policy overrides local configured allowlists when present; local config is fallback only when no server policy has been delivered. An empty delivered policy means deny all.
- Policy delivery must be safe against out-of-order or repeated deliveries: reject lower revisions, accept same revision with identical commands as idempotent, and fail same revision with different commands as a conflict while keeping the current policy.
- Clients that do not support policy updates already report unsupported command types through command results; surface that as an operator-visible assignment failure.
- Heartbeat may include a minimal current-policy revision/status echo if cheap; command results remain sufficient for v1 if not.
- If heartbeat echo exists, drift is reported as a warning/health flag only; the server must not auto-redeploy without operator action.
- Heartbeat response extensions for delivering policy are out of scope unless the command pipeline cannot carry the payload after implementation evidence.
Suggested UX
- Place the feature under Scripts as Scripts → Command Policies; keep device detail to status summaries and shortcuts.
- Use LiveView pages with periodic/manual refresh in v1.
- Provide three main sections: Command Entries, Categories, and Device Assignments.
- Command Entries: table shows name, path, enabled/disabled state, latest version, categories, assignment counts, updated by/at, filters for category/status/assignment/text, copy-path action, and modal create/edit with name, path, description, and category tags.
- Categories: first-class slug/display name/description records with modal create/edit, counts for command entries and active device assignments, delete blocked only by active device assignments, and delete confirmation showing affected command entries.
- Device Assignments: lightweight wizard: select devices within scope, select entries/categories, then preview/confirm. Row shortcuts from command entry/category preselect assignment sources.
- Preview: show combined resolved policy, expandable provenance by source, added/unchanged/removed diff against current active policy, affected devices, conflict table, and collapsed raw payload for operators/admins.
- Conflicts block confirmation for conflicted devices; batch assignment can proceed only for non-conflicted devices after the operator excludes conflicted targets.
- Assignment detail/status: show delivery result, retry/resend, remove selected sources, revoke all, rollback-as-new-revision, raw payload debug for operators/admins, command result failure details, optional drift warning, pending-offline state, and unsupported-client upgrade guidance.
- Clear statuses for
pending,queued,acknowledged,failed, andrevokedassignment states. - Show policy status in both Command Policies and device detail; full resolved policy is operator/admin-only, viewers see status only.
- Include activity/events with actor, timestamp, target devices, source entries/categories, and result. CSV export is out of scope for v1.
Future considerations
- Script workbench/test orchestration should later account for effective command policy when a script uses
exec_cmd; this feature does not block tests on policy assignment in v1. - Typed confirmation, device-group assignment, argument allowlisting, nested categories, real-time PubSub refresh, policy/audit CSV export, and heartbeat drift remediation are deferred until concrete need appears.
Risks
- Client runtime drift between deliver methods and installed policy format.
- Permission overreach if device scoping leaks into policy assignment UI.
- Conflict ambiguity causing silent shadowing if composition rules are unclear.
- Deliverability regressions to offline or legacy clients.
Validation and close-out
- Server tests for auth scope, policy conflict handling, assignment, command queueing, result acknowledgement, and revoke/narrow behavior.
- Client tests for policy ingestion, persistence, idempotency, and execution rejection/allowance behavior.
- End-to-end test for add/assign/update/revoke of policy and resulting
exec_cmdbehavior. - Documentation reconciliation in close-out via
T999, includingwebsite/docs/planned-features.mdstatus if the implementation completes.
User-facing docs sections to update during implementation
- Operations: update
website/docs/operations/index.mdor a new operations page with assignment workflow, conflict handling, rollback/revoke, and stale/failed policy troubleshooting. - Architecture: update
website/docs/client-server-interface.md,website/docs/data-flow.md,website/docs/modules/client-command-handler.md,website/docs/modules/client-starlark-runtime.md, andwebsite/docs/modules/server-devices.mdwith the policy-delivery contract and deny-by-default enforcement model. - Development: update
website/docs/development.mdand relevant module docs with Ash resources/domain APIs, migrations, and test commands for this feature. - Reference: update
website/docs/reference/contracts.mdwithapply_command_policypayload schema and allowlist field semantics.
Dependencies
packages/server/lib/nixstasis_web/permissions.exand device permission plumbing.packages/server/lib/nixstasis_web/controllers/device_command_controller.exor equivalent command dispatch path.packages/server/lib/nixstasis/devices.*- Stary runtime execution path in
packages/clientfor policy consumption. website/docs/features/server-stary-script-workbench/design.md(integration point forexec_cmdbehavior)website/docs/planned-features.md(status tracking)