Relay security model

Deep security analysis of the ArcFlow Relay pattern for static browser products. Relay keeps LLM provider keys on the server while exposing a site-scoped token to the frontend.

Threat model summary

AssetTrust levelExposure
LLM API keysHigh secretServer only
ARCFLOW_SERVER_API_KEYHigh secretServer, backend, Relay upstream
ARCFLOW_ADMIN_API_KEYHigh secretBFF/CI only
Site tokenPublic to frontendBounded by Relay policy
User chat inputUntrustedValidated server-side

Primary goal: prevent LLM key exfiltration via browser bundles or DevTools.

Trust boundaries

text
Browser (untrusted)
 → Relay (site token + Origin header)
 → ArcFlow Server (scoped upstream runtime key)
 → Postgres / Qdrant / LLM providers

Operator browser (untrusted)
 → BFF (operator session)
 → Admin API (ARCFLOW_ADMIN_API_KEY)

See Static product security model.

Site token scope

Site tokens authenticate Relay routes only. They do not grant:

  • Admin API access
  • Arbitrary workflow definitions (when allow_inline: false)
  • LLM provider key retrieval

Upstream server calls use a scoped runtime key limiting callable workflows (typically published chat workflow).

Origin enforcement

Relay middleware checks Origin or Referer against site allowed_origins before accepting runs.

ScenarioResult
Allowed production HTTPS originRun proxied
Missing/wrong originRejected
Attacker with scraped token from other originRejected (token alone insufficient)

Operators must PATCH origins when frontend URL changes.

Rate limiting

Per-site rate_limit_rpm returns 429 when exceeded. Limits abuse from leaked tokens or scripted traffic. Does not replace CDN DDoS protection.

Monitor 429 and origin failure metrics in Relay logs.

What an attacker can do with a compromised site token

ActionPossible?
Call allowed workflows via Relay from allowed originYes (by design for real users)
Call admin routesNo
Read LLM keysNo
Inline arbitrary workflow JSONNo when allow_inline: false
Exfiltrate other sites' dataNo (site scoped)
Run from arbitrary originNo (origin check)

Why mode: "direct" is insecure for production

Static SDK mode: "direct" embeds ARCFLOW_SERVER_API_KEY in the browser. Any visitor can extract the key and call /v1/runs directly, bypassing site workflow allowlists and rate limits.

modeProduction use
relayYes
bffYes (your backend holds keys)
directLocal dev only

Example secure config:

typescript
await runPublished("chat", "^1.0.0", message, {
 mode: "relay",
 relayUrl: import.meta.env.VITE_ARCFLOW_RELAY_URL,
 siteId: import.meta.env.VITE_ARCFLOW_SITE_ID,
 siteToken: import.meta.env.VITE_ARCFLOW_SITE_TOKEN,
});

Trace data policy and browser trace poll

Relay exposes GET.../trace to the browser. Trace events are metadata-only trace only. See Trace data policy compliance.

Token rotation and race conditions

After POST.../tokens/rotate, old tokens fail immediately at Relay. Plan deploys to avoid user-visible auth errors during rotation. See Token rotation (ArcFlow repository).

CSRF on operator BFF

Dashboard BFF mutating routes should use SameSite cookies and CSRF tokens. Admin key never reaches the browser.