Security
The platform runs other people's code for a living, so every capability is opt-in, scoped, and bounded — and credentials live where JavaScript can't reach them.
API keys
Keys look like rk_live_<lookup>_<secret>. Only a hash of the secret is stored; the lookup is a random handle, so a token exposes no row identity. Create and revoke them in the console — revocation propagates to every server instance in under a second via the same invalidation channel everything else uses. rusted login mints one per machine through device sign-in, so nothing is ever copy-pasted.
The sandbox
Each invocation gets a fresh QuickJS runtime with an uncatchable wall-clock interrupt, a heap cap, a stack cap, and an output cap. There is no filesystem, no process, no Node — the only way out is fetch, which resolves DNS first and refuses loopback, private, and link-local destinations (the SSRF guard), follows no redirects on host-side calls, and is budgeted per invocation by plan. Response headers a handler sets are vetted; framing headers can't be forged.
Secrets
Credentials don't belong in source — deployed code is stored, revisioned, and visible in the console. Declare what a function needs; store values in the console's Secrets page:
export const config = { secrets: ["GITHUB_CLIENT_SECRET"] };
export default async function handler(request, context) {
const secret = context.env.GITHUB_CLIENT_SECRET;
}
- Asking is the grant. No declaration → no
context.envat all, so what a function can see is visible in its source. - All-or-nothing. Any declared name unset refuses the invocation before the handler runs — callers get a generic error, your logs name the missing secret. Never an empty string, never a fallback.
- Encrypted at rest. AES-256-GCM under a key from the server's environment — the database never holds a plaintext credential, and neither the database nor the key alone reveals anything. Values are write-only: re-enter to rotate.
Host-only secrets go further: object-storage credentials, context.seal/open keys, and MCP OAuth introspection credentials name vault entries that are resolved server-side and used for the function without ever being readable by it — they're refused in config.secrets outright.
Environments
One deployed function, different configuration per environment, selected by the URL — never by code:
https://rusted.sh/f/settle → prod (every existing URL, unchanged)
https://rusted.sh/f/@stage/settle → stage
Each environment is a full overlay: its own secret values for the same declared names, its own durable state, its own object namespace — a stage invocation cannot read prod's counters or address prod's blobs, by construction. Handlers see context.currentEnv (always a string; "local" under rusted run). A secret unset in the resolved environment refuses the invocation; an environment you never created answers 404 exactly like a missing function. Because the environment is part of the address, it survives redirects, cookies, and third-party callbacks — register a dev OAuth app against /f/@stage/…/callback and the whole flow stays out of prod.
Sealed values & crypto primitives
Sessions and anything else a browser hands back want authenticated encryption. context.seal/open do it host-side with AES-256-GCM, keyed by a vault secret the function can never read; tampering, a wrong key, or a wrong context string all answer the same null. Alongside: context.randomBytes/randomBase64Url (OS CSPRNG — Math.random() never mints credentials), context.sha256, base64url/hex codecs, and context.timingSafeEqual for comparing tokens without a timing oracle. All native — no npm crypto, no interpreter-speed loops.
Public functions and --require-auth
Who may call an HTTP function is the access declaration in its http export, resolved against the server's mode. Undeclared, the function follows the server: open on rusted.sh, key-gated on a server running --require-auth. access: "private" demands one of the owner's API keys on every call, on any server — someone else's perfectly valid key is refused, and the refusal lands in the owner's logs. access: "public" stays keyless even under --require-auth — what an OAuth callback or webhook target needs, since the third party calling it cannot present your key. (public: true is the legacy spelling of that and still works.) The gate consults the stored record, never anything the caller sent. Unpublished functions answer 404 indistinguishably from missing ones, and probing environment names reveals nothing either.
Everything above is observable: refused invocations — rate limits, wrong methods, missing secrets, rejected OAuth tokens — are recorded with their HTTP status in rusted logs, so "my logs show nothing" and "callers are being turned away" can never be the same picture.
rusted