Skip to content

State machines & security

xcore-agent is designed so that security-relevant steps cannot be skipped, even by a bug. This page collects the mechanisms that enforce that, and the threat model they assume.

Closed action whitelist

install.yaml has no generic "run a command" action. Every step is one of a fixed, closed set (prepare, provision, install_plugin, install_extension, configure_plugin, write_env, start, stop, restart, healthcheck, rollback, ...), validated by a discriminated Pydantic union (schema/install.py) before the agent executes anything.

A tampered or malicious artifact therefore cannot turn xcore-agent into an arbitrary remote-execution primitive. The only shell-out mechanism is provision, and it is deliberately not driven by the artifact — see Provisioning.

State machines

Each pipeline is driven by an explicit, closed transition table:

  • .xdeploy: agent/state.pyTRANSITIONS
  • marketplace: agent/marketplace_state.pyMARKETPLACE_TRANSITIONS

DeploymentRunner._transition / MarketplaceDeploymentRunner._transition raise if a stage tries to move anywhere that isn't an allowed successor. The consequence is structural: it is impossible to reach install without a stage having verified the artifact's signature first. Terminal states are SUCCEEDED, FAILED, and ROLLED_BACK.

Signature verification before key use

In the .xdeploy pipeline, verify_signature (Ed25519) runs before obtain_key/decrypt. In the marketplace pipeline, verify_signature (HMAC-SHA256) runs immediately after fetch. In both, a failed verification transitions to failed and the artifact never reaches extraction.

Content integrity

  • Post-extraction re-verification: the manifest carries a content hash; after extraction the agent re-hashes the installed tree and compares (DeploymentRunner._verify_manifest). Extraction can't silently substitute files.
  • Path-traversal guarding: both tar extraction (pipeline._safe_extract) and ZIP extraction (marketplace_pipeline._safe_extract_zip) reject paths that escape the extraction root.
  • PluginRef.sha256: embedded plugins are re-hashed after install.

Trust boundaries

Input Trusted? Why
Artifact bytes from the Hub Untrusted until verified Ed25519 (.xdeploy) or HMAC (marketplace) signature checked first
install.yaml inside a .xdeploy Part of the signed artifact Signed by the project publisher
install.yaml for marketplace Operator-supplied local file The marketplace ships plain source, never deployment instructions
provision command Operator-supplied local config --provisioners-config, never from the artifact
Git tokens, API keys, signing secrets Operator-supplied Passed at the CLI/env, never fetched or stored by the agent

Key custody

xcore-agent never generates or stores long-lived secrets. The artifact's per-version DEK is obtained at deploy time from the Hub and kept only in memory for the duration of one deployment. The XDevKey authenticates the project; a separate deployment credential authorizes this deployment to have the DEK unwrapped — a leaked XDevKey does not, by itself, grant artifact decryption. See Key custody.

The HMAC trust caveat

The marketplace signature is HMAC-SHA256 over the ZIP bytes with the plugin developer's own xdevkeys signing secret — a symmetric secret, meaning whoever holds it can also forge a signature. --signing-secret is deliberately a required CLI argument, not something the agent fetches on your behalf, so this trust decision stays visible at the call site. The HMAC check catches transport corruption and accidental tampering, but it is not a substitute for trusting the marketplace connection itself (TLS + your own API key). See The real marketplace flow.