Use seven separate controls to decide who signs in, calls the API, whether a published link opens at all, who opens it, who mutates it through /v1, who mutates it from its public URL, and who uploads one file without a token. This model is for publishers, operators, and reviewers assessing an Energon handoff. Keep the controls separate because a share password, write password, upload grant, or creator-only write policy never substitutes for reader-specific authorization.
Security model
Map each request to its control
Start by identifying the caller and surface:
| Question | Control | What it protects |
|---|---|---|
| Who may use the human hub? | Cloudflare Access | Hub, agent keys, and account actions |
| Who may call the agent API? | Agent key sent as a bearer credential | /v1 reads and writes |
| Does this public content URL open at all, and which accounts see the object? | Link access, public or private | One site’s or file’s public URL and its /v1 visibility |
| Who may open this public content URL? | Optional share password | One site’s or file’s public content URL |
Who may change this object through /v1? | write_policy | Authenticated PUT, PATCH, and DELETE |
| Who may change this object from its public URL? | Optional write password | Guest PUT, and site-path DELETE, without a token |
| Who may upload one file once without a token? | Upload grant minted by a token holder | One target, one upload, at most an hour |
Cloudflare Access authenticates humans on the hub origin. Agents send agent keys with Authorization: Bearer, so /v1 remains reachable without an Access cookie. Published links live on the separate content origin and are public by default. The host split also keeps uploaded HTML, SVG, and XML away from the authenticated hub origin.
The trust boundaries
Human identity
In production, Energon accepts a human identity from Cloudflare’s verified Worker Access runtime or from a cryptographically verified hostname Access JWT. Hostname-based applications require ACCESS_TEAM_DOMAIN and the hub application’s ACCESS_AUD; verification checks the signature, issuer, audience, expiry, and application token type. An authenticated-email header alone is not trusted. It requires an email and a stable identity-provider subject, then applies ALLOWED_EMAIL_DOMAINS. The stable subject matters: if an email address is later reused by a different identity, the replacement does not inherit owner-only objects or old tokens.
Local development is intentionally different. On localhost or 127.0.0.1, Access is skipped and identity comes from DEV_ACCESS_EMAIL, an Access-like test header, or dev@example.com. Never use that local behavior as evidence that production trusts client-provided Access headers.
Agent identity
A signed-in human approves an agent connection at /connect or manually creates an Agent key at /keys. Connection approval delivers the secret once to the waiting agent; manual minting displays it once to the human. Energon stores its hash and a masked hint, not a recoverable secret. The public connection request and polling endpoints do not grant file access before approval. Every authenticated /v1 request becomes the token owner’s actor, so a leaked token carries that account’s read privileges on every public object, on that account’s private objects, and whatever mutation privileges each object permits.
Use Set safe Energon boundaries for approval checks, credential persistence, and unattended provisioning.
Check a configured agent without exposing the token:
export ENERGON_ORIGIN="https://energon.company.example"
# ENERGON_TOKEN must be a valid credential issued by this Energon.
test -n "$ENERGON_TOKEN" || { echo "Set ENERGON_TOKEN first" >&2; exit 1; }
curl -sS "$ENERGON_ORIGIN/v1/whoami" \
-H "Authorization: Bearer $ENERGON_TOKEN"The response identifies the owning email, token label, expires_at, scope, and admin. A 401 with error: token_expired is terminal: tokens cannot be extended, so a human must approve a new connection or mint a replacement.
Public reader
Anyone with an unprotected public content URL can open it, unless the object is private. A share password adds a gate to that URL for browsers and unauthenticated agents. It does not change /v1: a valid agent key can read the bytes without the share password. The write-password header also unlocks GET. The HTML gate form accepts the write password for reading when a share password is also set. The share-password header does not accept the write phrase.
Private link access
From v1.15.0, a creator can make a site or loose file private. Its public URL then refuses every request, including guest writes, with 403 link_private, and only the creating account’s agent keys can read or write it through /v1. Other accounts get 404 and do not see it in lists. A private object cannot have a share password or write password, and its write policy is always owner. It is not a shared-with list: there is no way to add another account, and anyone holding one of the creator’s keys reads it as the creator. See Keep work private to your account.
Authenticated writer
Each site and loose file stores one write policy:
owner: only the creating account may PUT, PATCH, or DELETE through/v1.org: any valid agent key on this Energon may mutate it through/v1.
Only the creator can change the policy. Neither value changes read access. A valid agent key can still read or duplicate an owner-only public object; the duplicate becomes a new object owned by the duplicating account.
Guest writer
A write password is a second per-object secret for someone who is not on the host. It is not an account, not Access, and not a token. The creator sets or clears it on create or PATCH. Duplicate does not copy it. Guest mutation happens on the public content URL with X-Energon-Write-Password, not on /v1. A valid write-password PUT succeeds even when write_policy is owner.
A loose-file write password only replaces that file. A site write password can add, replace, or delete paths under that slug and cannot delete the site. The cookie minted by the HTML gate never authorizes PUT or DELETE. Last writer stays the last account; the Hub shows “Updated via shared write.”
Do not mint the outside collaborator a token. They learn the protocol from GET on the content origin’s /llms.txt. See Let an outside agent update a URL.
Upload grant holder
An upload grant lets a machine without a token upload one file once, directly to the content origin. A token holder mints it on /v1 for exactly one target, and the upload acts as that account under its write policy. The grant secret goes only in the Authorization header on the grant’s upload URL; it never works on /v1, and an agent key never redeems a grant. A grant lasts at most an hour, never outlives the minting token, and ends when that token is revoked or expires. The uploader cannot choose the content type. See Let a machine upload without a token.
Defense around published content
Energon serves production content from CONTENT_ORIGIN, separate from PUBLIC_ORIGIN. Requests for content on the hub redirect to the content hostname, while account routes are not served there. Published HTML, XHTML, SVG, and XML (application/xml, text/xml, and any +xml type) receive a sandbox Content Security Policy without allow-same-origin, because browsers can run script inside any of them. Rendered Markdown is sanitized, and password-protected responses are private and not stored in the shared cache.
These protections reduce what active uploads can do, but they do not make an open link private. Choose private link access when only your own account should read the object, a share password for link-level reads, a write password only when an outsider should replace bytes at that URL, and an appropriate TTL for how long the object should exist.
Continue reading
- Sharing, access, and expiry explains how the seven controls interact over an object’s lifetime.
- Keep work private to your account is the recipe for private link access.
- Let an outside agent update a URL is the creator and outside-agent recipe for the write-password door.
- Set safe Energon boundaries gives operators a deployment checklist and verification commands.
- Manage shared work covers the everyday create, update, duplicate, and delete flows.
- Connect an agent covers installing the Energon skill and providing a human-authorized token.