Docs / Reference

Security model

The credentials AgentServe issues and what each can do, how secrets are stored, headers, webhook signing, rate limits and what is not production-ready.

AgentServe lets anyone deploy without an account, and lets a human take over the result later. This page lists the credentials that make that work, what each one grants, how the server stores them, and where the current version falls short.

Credentials#

CredentialFormatWho gets itWhat it can do
Manage tokenast_anon_ + 32 URL-safe charactersWhoever creates the stack, once, in the create response. The CLI and MCP server keep it in .agentserve/stack.json.Read, redeploy, extend, and delete one stack; read its logs and events. It cannot claim the stack.
Claim codeCLM-XXXX-XXXX-XXXXThe creator of an anonymous stack, once, inside claim_url.Take ownership of the stack, once. Anyone holding it can claim, unless the stack has a claim_email.
API keyas_live_ + 32 URL-safe charactersThe account holder, shown once at sign-up (or when an account is created on the claim page) and on each rotation at /account.Create stacks in the account, manage every stack the account owns, claim stacks with a claim code, read /v0/me.
Session cookieagentserve_sessionThe browser, after sign-in, sign-up or a claim.Use the dashboard: view, restart, revoke and delete the account's projects; rotate the API key.
Webhook secretwhsec_ + 32 URL-safe charactersThe creator, once, when the manifest sets notify.webhook.Nothing on the server. The receiver uses it to verify signatures.

Manage tokens after a claim#

Claiming does not revoke the manage token: the agent that deployed the stack can keep redeploying it, which is usually what the human wants. The owner can cut the agent off from the project's settings page in the dashboard (Revoke agent token). That clears the token's hash, emits stack.agent_revoked, and from then on only the owner's API key and session can manage the stack. Deleting a stack also clears its token and claim code.

Claim codes#

  • 12 characters drawn with secrets.choice from a 31-character alphabet without 0, O, 1, I or L, so codes survive being read aloud. That is about 59 bits.
  • Input is normalized before lookup: any case, any spaces or dashes, with or without the CLM prefix.
  • Single use. The claim is an atomic update that clears the code's hash, so two concurrent claims can't both succeed.
  • claim_email (manifest, form field or --claim-email) restricts claiming to the account with that email, compared case-insensitively. AgentServe does not send email: the field restricts, it doesn't notify.
  • Claim pages are served with Referrer-Policy: no-referrer, so the code in the URL isn't leaked to other sites.

Storage#

SecretStored as
Manage tokens, claim codes, API keysHMAC-SHA256 of the value, keyed with the server secret. The plaintext is never stored, so lost values can't be recovered, only replaced.
Passwordsscrypt with a random 16-byte salt, N=2^14, r=8, p=1, stored as scrypt$<salt>$<hash>. Compared in constant time. At least 8 characters.
Webhook secretsPlaintext in the database, because the server needs them to sign every delivery.
SessionsNot stored. The cookie is <user id>.<issued at>.<signature>, the signature being HMAC-SHA256 with the server secret, truncated to 32 hex characters.

Rotating the server secret invalidates every token, claim code, API key and session at once.

Sessions#

The cookie is HttpOnly, SameSite=Lax and Secure. It is valid for 30 days from sign-in. Signing out deletes the cookie in the browser, but there is no server-side session list: a copied cookie stays valid until it expires or the server secret changes. After sign-in, the next redirect is only followed for paths starting with a single /.

HTTP headers#

Every response from the control plane (API, claim pages, dashboard) carries:

HeaderValue
Referrer-Policyno-referrer
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Content-Security-Policyframe-ancestors 'none'

Responses from deployed services are passed through without these headers; the service controls its own. The proxy adds X-AgentServe-Stack: <stack id> to every service response and, while the stack is unclaimed, X-AgentServe-Unclaimed: deployed by an AI agent, unverified. Each service has its own hostname, so service pages can't read the control plane's cookies, which are scoped to the control-plane host.

Uploads#

  • Uploads larger than the size limit are rejected before extraction.
  • Archive entries whose path resolves outside the extraction directory reject the whole upload. Symlinks and hard links that point outside it, and device files, are skipped.
  • A service's path must resolve inside the upload.

Webhook signing#

Each delivery carries X-AgentServe-Signature: t=<unix>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" keyed with the stack's webhook secret. Receivers should compare in constant time and reject timestamps outside a few minutes. See Events for a verification snippet.

The webhook URL is taken from the manifest as is. The server will POST to any URL it can reach, including addresses on its own network.

Rate limits#

WhatLimit per client IP
Anonymous stack creation30 per hour
POST /v0/claim10 per minute
Sign-in, sign-up and claim-form submissions (combined)10 per minute
Unclaimed stacks building or live3 at a time

Rate limits are sliding windows. Details in Limits.

Not production-ready yet#

The current version is an early MVP. Don't deploy anything you can't afford to lose or expose.

  • No isolation. Services run as plain processes under the server's user. A deployed service can read and write anything that user can, including the database, the server secret and other stacks' files, and can reach the network without limits. A real deployment needs containers or microVMs (Firecracker, gVisor) with CPU, memory and network limits.
  • Build commands are arbitrary shell. build, start and npm install scripts run with the same privileges as the services.
  • Single node. One process, one SQLite database, local disk. No replication, no failover. Rate limits live in memory.
  • No managed add-ons. No Postgres or Redis; services keep state in $DATA_DIR.
  • No email. claim_email only restricts who can claim; nobody is notified. Account emails are never verified, and there is no password reset. Events go to notify.webhook.
  • Basic abuse controls. A per-IP cap, the TTL and in-memory rate limits. Public anonymous hosting also needs phishing and malware scanning and a way to report a stack.
  • No resource quotas. Disk use, memory and CPU per stack are not limited.
Stuck? Your agent can read /skill.md, or connect it over MCP.