This is an explicit temporary exception. Hosted content and the control plane
share the registrable domain smol.ai, which creates risks absent from a
dedicated hosting domain:
a hosted response can attempt to set a parent-domain cookie;
browsers consider hosted content and Forge same-site, so SameSite is not a
sufficient CSRF boundary;
incorrect CORS, OAuth, redirect, postMessage, or frame allowlists may trust
a user-controlled origin;
phishing, malware, and reputation incidents affect the broader domain.
Current Forge browser authentication uses a bearer token stored at the
forge.smol.ai origin and sends it explicitly in the Authorization header.
The interim deployment must preserve that isolation:
hosted requests never receive Forge bearer tokens, PATs, sessions, provider
credentials, or control-plane headers;
the Forge API allows only exact trusted CORS origins and never trusts
*.sites.smol.ai;
state-changing Forge APIs require explicit authorization and are
side-effect-free on GET;
OAuth callbacks, redirect allowlists, WebSocket origins, frame ancestors, and
postMessage receivers treat every hosted origin as untrusted;
Forge never opts into document.domain relaxation;
the hosted edge rejects or rewrites any HTTP Set-Cookie attempting to set
Domain=smol.ai, .smol.ai, or another parent domain; hosted application
cookies must remain host-only;
Forge must never introduce an authentication cookie scoped to smol.ai or
.smol.ai; if authentication cookies are introduced later, they must be
host-only __Host- cookies and state changes must also validate CSRF tokens
and exact origin;
before any pre-production route is enabled, the hosted edge must add and test
X-Robots-Tag: noindex, nofollow unless a narrower reviewed policy replaces
it.
HTTP response filtering cannot prevent hosted JavaScript from attempting
document.cookie = "...; Domain=smol.ai". The bearer-token design prevents
direct authentication-token theft, but the shared cookie jar still permits
cookie tossing and cookie-jar denial of service against forge.smol.ai.
Consequently, the shared-domain period is an access-controlled Alpha
program:
accounts and projects must have explicit Alpha access;
repository administrators within an enrolled project may use the
self-service setup flow after checks and Terms acceptance;
preview access defaults to unauthenticated public_unlisted during the trial;
preview hostnames contain 128 independent random bits, are not exposed
through public listing APIs, and carry noindex; this reduces accidental
discovery but is not an authentication or confidentiality boundary;
operator enrollment of the project authorizes its deployments to use the
shared-domain trial rather than requiring approval for every exact
deployment; and
general public self-service does not launch until domain graduation.
These controls reduce exposure but do not make the shared registrable domain a
production-quality boundary.
All deployment operations pass through one consistent authorization boundary
using actor, action, tenant, repository, project, environment, deployment,
application component, state scope, and realtime topic.
Requirements:
repository administration alone does not bypass project eligibility,
environment policy, feature entitlements, quotas, or suspension;
auto production publication is effective only when the production branch
is protected and the project has a non-empty mandatory check baseline;
when auto is effective, actors allowed to merge or write the protected
production branch are explicitly delegated production-deployer authority;
if those protections are absent, the effective publication policy is
owner_review, even if the repository requested auto;
agent task capabilities are short-lived, repository- and branch-scoped, and
cannot directly push the protected production branch or invoke production
publication by default;
every state-changing request carries an idempotency key where retry or
duplicate delivery is possible;
tenant predicates are present in every query and negative authorization tests
cover cross-user and cross-organization access;
approval, promotion, rollback, suspension, resource destruction, domain,
secret, and budget-extension actions are audited.
The graduated product allows repository administrators to enable Deploy after
automated eligibility checks and acceptance of the current hosting Terms of
Use. During the sites.smol.ai Alpha, this self-service flow is available only
inside Alpha-enabled accounts and projects.
Enablement fails closed when:
repository, account, organization, or project state is ineligible;
identity, abuse, quota, billing, or policy services are unavailable;
the hostname is invalid, reserved, ambiguous, or already claimed;
the manifest asks for unsupported or unentitled capabilities;
source visibility is outside the public-only initial policy.
Enablement records actor, repository, project, terms version, configuration
digest, check results, quotas, feature flags, and timestamp.
Operators retain immediate suspension and takedown authority.
Branches select work; an exact commit SHA identifies execution.
The manifest is read from and snapshotted with that exact SHA.
A release is the environment-neutral, immutable result of building that SHA.
A deployment applies one release to one environment and its stable or
preview-isolated state scope; activation separately moves the environment
pointer.
Production deployment and activation recheck branch head, manifest digest,
compiled plan digest, required checks, eligibility, and environment policy.
Transactional outbox events and idempotency prevent lost or duplicated
ref-change work from publishing the wrong release.
Production pointer updates use compare-and-set semantics.
Provider mutations carry the desired generation and compiled plan digest,
acquire a bounded operation lease, use provider preconditions where
available, and recheck current desired state immediately before mutation.
Stale deployments remain inspectable but cannot silently replace a newer
eligible release.
Builds reuse the restricted public-repository runner with these invariants:
detached checkout at the exact SHA;
no user, Forge, storage, or provider deployment credentials;
a build-specific dependency allowlist enforced through the trusted network
policy;
bounded CPU, memory, disk, processes, wall time, output, and logs;
no privileged containers, host sockets, nested provider CLIs, or metadata
service access;
cache isolation by tenant, repository, lockfile or build inputs, image, and
trust level;
process termination and quiescence verification before trusted ingest.
Every build network path, including package managers, redirect targets, Git
subresources, and arbitrary URLs requested by build tools, receives the same
destination-class, DNS-at-connect, redirect, internal-service, metadata,
credential, port, size, duration, and quota protections described under
Build and runtime egress. Build allowlists and
runtime allowlists remain independently configurable.
Trusted ingest:
accepts only declared output roots;
preserves the existing static-ingest checks for path traversal, absolute
paths, symlinks, special files, duplicates, unsupported formats, observed
size changes, and quota violations;
before dynamic Deploy is enabled, adds and tests link-count/file-identity
checks plus digest-after-quiescence verification so hard-link and same-size
mutation races cannot evade validation;
derives or validates MIME and artifact types under versioned policy;
computes digests before publication;
stores artifacts under immutable prefixes;
writes completion metadata last;
never returns storage or provider credentials to the build.
The author-facing runtime is one Forge application. The initial Cloudflare
adapter may compile it into a Workers for Platforms user Worker plus
Forge-owned state and realtime gateways, but repository code cannot observe or
choose that topology.
Requirements:
the trusted wrapper alone receives raw provider bindings;
every managed call is bound to the current tenant, project, environment or
preview deployment, deployment generation, and compiled plan;
a project cannot address another project's state scope or realtime topic by
supplying provider IDs, prefixes, class names, or namespaces;
production and preview state are distinct;
CPU, memory, duration, subrequests, scope cardinality, connection count,
fan-out, storage, bandwidth, log, retention, and spend limits are enforced;
provider IDs and management endpoints are not exposed as authority;
user code cannot create unmanaged provider resources;
cancellation, terminal failure, disablement, and suspension revoke
capabilities and reconcile provider state; and
serving checks current repository, project, deployment, access epoch,
realtime generation, and suspension state before routing.
No raw Cloudflare binding metadata, Durable Object namespace, account token,
deployment API, Railway token, Modal credential, or Forge service binding is
available to user code.
env.forge.state is available to every application entrypoint and retains
scope storage only after a successful mutation. A small, stable set of
Forge-owned SQLite-backed Durable Object classes implements the initial
adapter. Repository code never executes inside those objects.
Isolation and integrity requirements:
scope identity is derived by trusted code from tenant, project, production
environment or exact preview deployment, and the caller's logical name;
raw object IDs are never accepted or returned;
operations serialize strictly within one scope;
a multi-operation mutation, including a versioned realtime event intent and
sequence, commits atomically or not at all; live fan-out is best effort and
may be suppressed without losing the state mutation;
cross-scope transactions and application-defined state methods are rejected;
scopes are hashed over a fixed, recorded pool of 16 production or four
preview shards, so rotating caller names cannot create unbounded provider
objects;
distinct cold scope names are rate-limited before a provider call;
preview scopes are isolated per deployment and expire under recorded
retention policy;
production state persists across releases and rollback;
state is never implicitly deleted because code stops using it;
and
explicit destruction requires separate authorization, audit, an impact
summary, and a retention decision.
Forge-owned state objects cannot make user-directed external requests.
Repository-authored Durable Object classes, alarms, arbitrary methods,
migrations, and networking remain deferred.
env.forge.realtime is also part of the application runtime, but creates no
provider object or connection until used. Realtime is a non-authoritative,
server-emit/browser-subscribe view of durable state. The reserved
/.forge/realtime endpoint is owned by the edge and never dispatched to
repository code.
Security requirements:
hibernating WebSockets are mandatory; raw application status 101, custom
socket handlers, client publishing, presence, and durable replay are
rejected;
application code authenticates an ordinary HTTP request and asks the
Forge-managed facade to authorize one scope and topic;
the facade returns an unforgeable opaque grant that application code must
return directly; only the trusted wrapper can serialize its random,
single-use, 60-second ticket;
repository code cannot inspect, clone, stringify, or log ticket bytes;
the ticket is scoped to tenant, project, environment or exact preview
deployment, normalized public origin, scope, topic, deployment generation,
access epoch, and issuing release;
the expected origin comes from the trusted normalized request URL, and an
incoming HTTP or WebSocket Origin must match exactly;
the ticket travels as forge-ticket.<base64url-ticket> alongside the fixed
forge.realtime.v1 WebSocket subprotocol, never in a path or query;
the edge echoes only the fixed protocol, consumes the ticket immediately
before upgrade, and redacts both protocol fields from platform/provider
logs;
handshake replay, origin mismatch, stale deployment generation, stale access
epoch, and ineligible project state fail closed;
reconnect always calls the application authorization endpoint for a new
ticket;
WebSocket compression is disabled initially;
connection attachments contain only bounded platform metadata; and
message bodies, tickets, cookies, and session credentials are not logged.
State remains authoritative. Events carry a contiguous sequence per
(scope, topic). The SDK connects and buffers before fetching an atomic
versioned snapshot, deduplicates by sequence, and refetches after a gap,
reconnect, activation, or rollback. No replay is promised.
Activation and rollback move the application pointer first, increment the
realtime generation, stop old-generation tickets, drain for at most 30
seconds, and close remaining sockets with 1012 (Service Restart).
Suspension increments access and realtime epochs, stops tickets, handshakes,
polls, state mutations, and event delivery, and closes active sockets with
1008 (Policy Violation).
Realtime uses the same deterministic 16 production or four preview shards as
the state scope hash. The control plane can therefore wake every bounded
shard, enumerate hibernating sockets, require an acknowledgment, and retry
missed notifications without storing one database row per connection. A
fail-closed mutation/delivery eligibility check is the second enforcement
path. Sockets become inert even if a provider close is delayed. Physical close
has a measured ten-second objective while the provider control path is
available; an unavailable provider cannot authorize new state or event work.
Sockets and conditional polling share one realtime budget. Fallback starts at
15 seconds, backs off to five minutes, and runs only while the page is visible
and the opaque grant permits it. Access and budget close codes forbid
fallback; HTTP 401, 403, 410, and 429 stop it. Reaching a realtime
ceiling disables both transports for that project while preserving ordinary
HTTP, static assets, and durable state. Numeric connection, message, fan-out,
lifetime, cardinality, byte, and spend ceilings are defined in the
application runtime contract.
Build and runtime egress are separate capabilities and separate policies.
The default is an allowlist of normalized public destinations. A project may
receive the broad_public_egress feature flag, but all user-authored external
requests from the application HTTP runtime still traverse a trusted outbound
policy layer.
The default build allowlist covers Forge source access, npm registry and
package downloads, GitHub source downloads, and Prisma's pinned engine host
binaries.prisma.sh. These are dependency-install destinations only; adding a
runtime destination or a deployment-provider API requires a separate policy.
rejects unsafe raw-IP destinations and unapproved ports;
resolves and validates DNS at connection time;
revalidates every redirect and protects against DNS rebinding;
applies request, response-size, duration, concurrency, and bandwidth limits;
never attaches Forge or provider credentials;
records bounded, privacy-conscious audit and usage evidence;
supports immediate per-project revocation.
Runtime code cannot widen its own network policy. A manifest request is
intersected with the trusted project entitlement.
Cloudflare Outbound Workers do not intercept requests made from Durable
Objects. The managed state and realtime gateways therefore expose no
user-controlled outbound networking; custom repository-authored Durable Object
classes remain deferred until Forge has an enforceable network boundary for
them.
Supported policy modes are public, repository, and share_token.
Access mode is snapshotted per deployment.
Forge credentials are never sent to a preview origin.
Repository access starts at the reserved
/.forge/access/bootstrap#code=... route. The URL fragment is not sent in
the HTTP request or referrer.
Edge-owned bootstrap code posts the single-use code to
/.forge/access/exchange before repository code runs, then navigates to a
clean URL.
The exchange issues a signed, short-lived, Secure, HttpOnly, host-only
__Host-forge-access cookie with Path=/, scoped to one deployment and
hostname; codes and credentials are redacted from logs.
Every authenticated request checks the deployment access epoch. Token
revocation, repository visibility changes, or removal of a user's qualifying
repository read permission increments that epoch.
The same access epoch gates realtime ticket issuance and active sessions;
revocation invalidates unused tickets and closes affected connections.
After access validation, the edge strips every edge-owned access cookie and
authentication header before dispatch. Repository code receives only
application cookies and ordinary request headers.
Share tokens are high entropy, hashed at rest, revocable, scoped, and removed
from logs and referrers.
A share link carries its raw secret only in
/.forge/access/bootstrap#share=.... The self-contained edge page loads no
third-party resources, clears browser history, and exchanges the secret
before repository code runs.
Share exchange returns the same kind of short-lived, Secure, HttpOnly,
host-only __Host-forge-access cookie with Path=/; every request checks a
share-access epoch incremented on revocation or replacement.
Authenticated preview responses use private or appropriately partitioned
caching.
Preview state is isolated from production by default. A preview cannot request
broader visibility, raw provider access, egress, or quotas than its trusted
environment policy.
never list storage or synthesize arbitrary object keys;
prevent traversal and ambiguous encoding;
serve or route only deployments recorded as ready and eligible;
attach baseline nosniff, referrer, and permissions headers;
reserve platform headers and remove provider internals;
reject parent-domain Set-Cookie response headers during the
sites.smol.ai period while explicitly accepting that hosted JavaScript can
still attempt parent-domain cookie writes;
avoid platform-generated broad CORS;
keep suspension and access checks outside immutable artifact caches.
Dynamic applications may need their own response headers. The exact
forwarding, override, cookie, caching, CORS, framing, and service-worker rules
are part of the proposed
application response-header contract.
Application headers cannot override platform security, routing, access,
cookie, or suspension controls.
state serialization, atomic mutation-and-emit, cross-scope rejection,
preview isolation, fixed-shard/cold-scope limits, retention, and explicit
destruction;
realtime cross-tenant scope and topic access, ticket replay, exact-origin
mismatch, opaque-grant inspection and forgery, stale generation, stale
access epoch, high-cardinality topic creation, fan-out amplification,
hibernation wakeup, ticket and message-body redaction;
connect-buffer-snapshot races, snapshot cursor/ETag behavior, duplicate and
missing event sequences, bootstrap buffer overflow, and fallback close-code
behavior;
activation and rollback drain, suspension of active and hibernated sockets,
missed-provider-notification containment, quota exhaustion, polling
equivalence, and project and global circuit breakers;
egress private, metadata, internal host, raw IP, unsafe port, redirect, DNS
rebinding, size, duration, and quota bypass;
repository and share-token bootstrap leak, browser-history, replay, scope,
revocation-epoch, cache, and suspension;
HTTP and JavaScript parent-domain cookie behavior, cookie-jar denial of
service, CORS, OAuth redirect, frame, postMessage, and same-site requests;
provider partial failure, cancellation, retry, cleanup, and reconciliation;
state retention, explicit destruction, and recovery;
abuse suspension while artifacts and runtimes are cached.