Skip to content

Security model

Wayglass applies Zero Trust to code changes: trust is decided per change, from what the change is, and never granted to an agent as a standing permission. This page lists the mechanisms that make that hold, and the places where it does not hold yet.

All repo access goes through Artifacts repo tokens that the Worker mints for one repo, with an explicit short lifetime.

Token Repo Scope Lifetime Who gets it
Init token The new trunk write 900 s wayglass init, once, to push the first commit
Fork token One intent’s fork write 900 s The agent that declared the intent; refreshed on every push, older ones revoked
Land token The trunk write 300 s wayglass land, only for an approved decision whose fork has not moved; revoked as soon as the land is verified or aborted
Session fork token The new fork made by session fork write 900 s The person or agent forking the session; the source fork’s token is never handed out

So no agent holds a write token for the trunk except for the few seconds a land takes, a fork token works only on its own fork (the trunk refuses it), and only one land per project can hold a trunk token at a time.

The CLI never writes a token to disk. Tokens reach git through GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n and GIT_CONFIG_VALUE_n environment variables as an http.extraHeader, so they appear neither in the remote URL, nor in argv (where ps would show them), nor in .git/config. The mirror push in land uses your own git credentials, never an Artifacts token.

The Worker itself holds no Cloudflare API token with access to Artifacts; it reaches Artifacts only through its Workers bindings. The one optional API token, CF_EVENTS_API_TOKEN, is limited to Queues › Edit and is used only to subscribe new trunks to push events.

The decision that counts is made on the Worker, not on the client:

  • The Worker reads the fork head and the base commit from Artifacts, computes the diff itself, and classifies it.
  • It runs the same Go policy evaluator the CLI uses for its preview, compiled to WebAssembly, so the two cannot drift.
  • It reads .wayglass/policy.yaml from the trunk at the commit the intent forked from, never from the fork. Editing the policy in a fork does not change how that fork is judged.
  • Any change under .wayglass/ goes to a person at minimum (builtin:protect-policy). So does a change too large to read in full, and a change judged against a missing or invalid policy file.

What the Worker never takes from the client

Section titled “What the Worker never takes from the client”
The client sends The Worker uses instead
Tags, matched rule, action, reason from the local preview Its own classification and decision. The client’s preview is stored only for comparison and is never shown on public routes
A claimed commit The fork head it reads from Artifacts; propose fails if they differ
A jurisdiction The jurisdiction of the namespace the project lives in
“I landed it” A check that the trunk now holds exactly the approved commit, or a merge of it touching only the decided paths. A mismatch is recorded as land.mismatch

The agent that declared an intent cannot approve or reject its decision; the Worker refuses with conflict. multi_approve counts distinct approvers. Every verdict needs a reason, and placeholder reasons (TODO, n/a, <what you checked> and similar) are refused, because the audit log keeps them forever.

Only the agent that declared an intent can get a fresh fork token for it, and only that agent or the admin can abandon it (wayglass intent abandon, with a reason). Abandoning revokes the intent’s live fork tokens and refuses new ones; the fork repo is kept.

There are two kinds of key:

  • The admin key (the Worker secret WAYGLASS_ADMIN_KEY). It can do everything, including minting agent keys, and its caller names itself with X-Wayglass-Agent (WAYGLASS_AGENT).
  • Agent keys (wgk_…), minted by the admin with wayglass keys create --agent <name>. Each is bound to one name. A request made with one is recorded under that name on every route; the Worker ignores X-Wayglass-Agent and any name in the request body, so an agent key cannot approve its own change under another name. The Worker stores only a SHA-256 of each key; wayglass keys revoke takes effect on the next request.

wayglass doctor shows which kind of key you are logged in with and the name it acts as (from GET /v1/whoami).

Every intent, push, overlap, decision, verdict, token issue, land and abort is appended to a per-project log in a Durable Object. There is no route to edit or delete entries. The same decision record is written into a git note on the landed commit.

  • The admin key still names itself. Whoever holds it can act as any agent name. Give it only to the person who runs the Worker, and give every agent and every other person an agent key from wayglass keys create --agent. The self-approval check is only as strong as the keys you hand out.
  • Agent keys are long-lived and not scoped to a project. A key works on every project on its Worker until it is revoked. There are no short-lived session tokens exchanged for a key yet, and minting a key is not recorded in any project’s audit log.
  • A declared model family is self-reported. A policy rule can require an approver from another model family; the Worker compares the families the author and the approver declared, not ones it can verify.
  • Artifacts has no branch protection. Someone with a trunk write token could push something other than the approved change. The Worker detects that after the fact (land.mismatch, and trunk push events from the event subscription) but cannot prevent it.
  • Policy at the fork base. A change that forked before a policy change is judged by the older policy, even if it lands after the new one.