Skip to content

Command Palette

Search for a command to run...

Automation & agents

Execution authorization

The single boundary between a validated plan and the deployment engine. Off by default, fail-closed once configured, and bound to each plan's content hash.

Phelix authorizes execution at a single boundary between a validated plan and the deployment engine - request → plan → authorization → (approval) → execution - and never request → execution for a protected path. This is the layer that makes Phelix safe to hand to automation: an agent decides what should be deployed, Phelix decides whether this caller may execute that exact plan, and a human decides who is allowed to and what needs signing off.

Authentication vs authorization

QuestionStatus in Phelix
Authentication - who is calling?Not implemented for execution. A local phelix invocation is an unauthenticated local process, and Phelix says so.
Authorization - may this caller execute this plan?Implemented - this guide.

Phelix does not fabricate identity. There is no --actor flag, no --as flag, and no environment variable through which a caller can name itself an authorized principal. phelix auth login is unrelated: it authenticates you to the dashboard backend for monitoring, grants no execution rights, and is never consulted by the authorization boundary.

The actor model

The boundary receives an abstract caller - never an HTTP header, a CLI flag, or MCP metadata. An actor has three fields, matching the machine contract’s actor block:

actorjson
{ "type": "cli", "id": null, "authenticated": false }
FieldMeaning
typecli, api, agent, mcp, or service - the transport, not a privilege level
idthe established identity, or null; only an authenticated actor ever has one
authenticatedwhether the identity was verified

The invariant id != null ⇒ authenticated is enforced where actors are validated, so an unauthenticated caller can never match a rule that names an identity. Today there is exactly one authenticator - the local CLI - which takes no input and always reports {type: "cli", id: null, authenticated: false}. What a local invocation proves is write access to this host’s Phelix data directory: recorded as provenance, never as an identity.

Modes

Configuring the policy

The policy lives on the host under the data directory at ~/.phelix/authz/policy.json (0600, relocatable with PHELIX_DATA_DIR). It is deliberately not in a project’s phelix.yaml: an agent editing the repository it deploys could otherwise rewrite the file that grants it execution. Find the exact path with phelix authz status.

policy.jsonjson
{  "schema_version": "1",  "mode": "enforced",  "rules": [    {      "actor": { "type": "cli", "authenticated": false },      "action": "rebuild",      "target": "billing",      "effect": "allow",      "require_approval": true,      "description": "local operator may deploy billing, with sign-off"    }  ]}

Evaluation is fixed and total:

  • A malformed request or policy is AUTHZ_INVALID - fail closed.
  • An execution with no plan binding is denied.
  • An explicit deny rule wins over any allow rule, always.
  • An allow rule with require_approval is satisfied only by an approval bound to this exact plan.
  • Anything else is denied by default.

What is protected

PathGated
phelix plan apply <plan-id>Yes - plan-bound, fully authorized
phelix rebuildYes - planless, so denied under enforcement
phelix rollbackYes - planless, so denied under enforcement
Webhook deploymentsYes, transitively - the webhook queue re-invokes phelix rebuild
Remote rebuild / rollback (monitor daemon)Yes - same mechanism, gated in-process
plan create / show / list, inspect, context, status, logNo - read-only
phelix buildNo - produces an artifact without serving it
phelix start / stop / restartNo - does not change the deployed version

Under enforced authorization, a planless mutation is denied even by the most permissive possible rule set: the plan binding is not a rule, it is the boundary’s shape. That closes the obvious bypass of “skip the plan, run rebuild directly”, and it is why build / start / stop being ungated is not a hole - they cannot promote a new version.

Plan binding

Authorization is never granted for a vague action like “deploy billing”. It is evaluated against the immutable plan, and the binding is both plan_id (which artifact) and plan_hash (what it will do):

  • A decision for plan A can never permit plan B, even when the action and target are identical.
  • A plan whose content hash differs from the approved one no longer matches that approval.
  • There is no reusable authorization token: the decision is re-evaluated against the exact plan every time.

Approval

An allow rule with require_approval: true means: permitted, once an approval bound to this exact plan exists.

Terminal
phelix authz check   <plan-id>                        # what would the boundary say?phelix authz approve <plan-id> --expect-hash sha256:… # record the approvalphelix plan apply    <plan-id>                        # now executesphelix authz revoke  <plan-id>                        # remove the approval

--expect-hash asserts the hash you inspected; a mismatch is refused rather than approved - machine callers should always pass it, so an approval is never granted to content the approver did not see. An approval is immutable: the file at ~/.phelix/authz/approvals/<plan-id>.json is created exactly once, re-approving is ALREADY_EXISTS, and a changed decision is a revoke followed by a new approve.

Local compatibility

Introducing authorization changes nothing on a host that has not configured it. The boundary between “unchanged” and “enforced” is a single file:

Policy filePosture
absentlegacy_local - existing local CLI behavior, preserved
present, mode: legacy_localthe same, stated explicitly
present, mode: enforcedthe rule set decides; no match means deny
present, unreadableAUTHZ_UNAVAILABLE - fail closed (retryable)
present, invalidAUTHZ_INVALID - fail closed (not retryable)

Error codes

CodeExitRetryableMeaning
AUTHZ_DENIED11noThis caller may not execute this. Do not retry.
AUTHZ_UNAVAILABLE11yesThe boundary could not be consulted (I/O). Nothing executed.
AUTHZ_INVALID11noThe host policy or the request is malformed. Needs an operator.
APPROVAL_REQUIRED11noA plan-bound approval is required and none exists.
APPROVAL_STALE11noAn approval exists but its binding no longer matches this plan.
APPROVAL_INVALID11noThe approval artifact is corrupt or was edited after creation.

Exit 11 uniformly means the boundary refused or could not decide, and nothing mutated. AUTHZ_UNAVAILABLE is the only retryable one - an agent must never be told to repeat a denied action, and an approval requirement is not a transient failure. Plan staleness keeps its own PLAN_* codes and exit 3. See Exit codes and the machine contract.