// docs

Architecture: the machine surfaces

How Envpilot's five client surfaces share one auth core, and why machine credentials can read secrets but never write them.

Architecture: the machine surfaces

Envpilot exposes your projects and variables through five client surfaces. Two are driven by a human at a keyboard, two are driven by programs, and one is a hard-locked CI reader. They differ in how they authenticate and in what they are allowed to do — but they all funnel through a single enforcement core, so there is exactly one place where scope, plan, and audit are decided.

The five surfaces#

SurfaceWho drives itAuthWhat it can do
CLIA humanWorkOS device flow (browser login)Read and write (role-gated)
VS Code extensionA humanWorkOS device flow (browser login)Read and write (role-gated)
REST APIA programAPI key (envpk_…)Read
MCP serverAn agentAPI key (envpk_…)Read, and file a request
GitHub ActionCIAPI key (envpk_…)Read only — never requests

The CLI and the VS Code extension are human surfaces: a person completes a WorkOS device-flow login in a browser, and their organization/project role decides what they can read and write. The REST API and the MCP server are machine surfaces: a program or an AI agent presents an API key. The GitHub Action is a machine surface too, but a deliberately narrow one — a CI reader locked to a single project's variables.

One key, one core#

Every machine surface authenticates with the same credential: an API key minted in Organization Settings → API Keys. There is no separate "service token" concept anymore — Action credentials are API keys with the GitHub Action surface selected.

A key carries:

  • A SHA-256 hash only. The plaintext envpk_… is shown exactly once at creation; Envpilot stores only its hash. Lose it, and you revoke and re-issue.
  • Scope — projects (all or a list), environments (all or a list), and resources (variables, accounts, projects, and optionally requests).
  • Surfaces — which faces the key may use: rest_api, mcp_server, github_action.

Because all three machine surfaces route through one authorizer, there is no second implementation of "is this key allowed" to drift out of sync. Denials are returned, not thrown, so the audit write always survives; an unknown, revoked, or expired key gets one uniform answer; and the plan gate (public_api / mcp_server per surface) is re-checked on every request.

The trust model: agents request, humans approve#

The core rule for machine credentials is simple: a machine credential never writes a secret. No API key — for the REST API, the MCP server, or the GitHub Action — can create, edit, or delete a variable's value.

There is exactly one mutation a machine credential may perform, and only over the MCP server with a scope that includes the requests resource: it can file a variable request. A request is not a write. It is an ask, with a required justification, that lands in a human reviewer's queue in the dashboard. The reviewer decides, and if they approve, the reviewer supplies the value. The agent never proposes a value — it states what it needs and why, and a human fills in the secret.

This keeps the blast radius of a leaked machine key bounded to reads within its scope plus the ability to ask a human for more — never the ability to silently change what a deploy pulls.

GitHub Action keys are the strict case: they are locked to a single project and the variables resource, and they can never file a request. CI reads; it does not negotiate.

The agent request loop#

When an agent needs a value it can't see, the round trip looks like this:

If the reviewer rejects instead, envpilot_get_request_status returns rejected along with the reviewer's reason, so the agent can report back why it was turned down rather than silently retrying.

To keep a retry-looping agent from becoming reviewer-alert spam, request creation is tightly rate-limited and capped — see Rate limits. The auth and audit contract shared across every surface is covered in API Security.