Modal.Secret reference
CreateSecret
Section titled “CreateSecret”Source:
packages/modal/src/CreateSecret.ts
Store secrets by name at runtime, such as one per user, and hand them to that user’s sandboxes. With an outbound header replacement the sandbox never sees the value: Modal adds it to matching requests on the way out.
Bind it to a Modal.Environment to keep these runtime secrets in their
own namespace. They aren’t in Alchemy state: deleting the environment
removes them.
Objects created at run time are tagged with the stack, stage and binding
(alchemy::stack, alchemy::stage, alchemy::id) and are not deleted when
the stack is destroyed. A Modal secret has only a name, so the tags become
its name prefix, Modal.runtimePrefix({ stack, stage, id }): list them
with modal secret list and delete the ones whose name starts with it.
CreateSecret: A user’s token, injected at egress
Section titled “CreateSecret: A user’s token, injected at egress”import * as Cloudflare from "@alchemy.run/cloudflare";import * as Modal from "@alchemy.run/modal";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { HttpServerRequest } from "effect/http/HttpServerRequest";import * as HttpServerResponse from "effect/http/HttpServerResponse";import { Agents, Coder } from "./Coder.ts";
export default class Api extends Cloudflare.Worker<Api>()( "Api", { main: import.meta.url }, Effect.gen(function* () { const secrets = yield* Modal.CreateSecret(Agents); const sandboxes = yield* Modal.CreateSandbox(Coder); return { fetch: Effect.gen(function* () { const request = yield* HttpServerRequest; const user = request.headers["x-user"] ?? "anonymous"; const github = yield* secrets.put(`github-${user}`, { GITHUB_TOKEN: request.headers["x-github-token"] ?? "", }); const sandbox = yield* sandboxes.create({ outboundPolicy: { routes: [ { https: { domains: ["api.github.com"], headerReplacements: [{ secret: github, headers: { Authorization: "Bearer $GITHUB_TOKEN" } }], }, }, { tls: { domains: ["github.com"] } }, { any: { redirect: "block" } }, ], }, }); return HttpServerResponse.text(sandbox.sandboxId); }).pipe(Effect.orDie), }; }).pipe(Effect.provide(Layer.mergeAll(Modal.CreateSecretHttp, Modal.CreateSandboxHttp))),) {}CreateSecretHttp
Section titled “CreateSecretHttp”Source:
packages/modal/src/CreateSecretHttp.tsKind: Layer · Provides:Modal.CreateSecret
CreateSecret over Modal’s API. Grants the host’s identity (a scoped
Modal.ServiceUser) the contributor role on the environment.
Secret
Section titled “Secret”Source:
packages/modal/src/Secret.ts
A Modal Secret: a named set of environment variables stored by Modal and attached to Sandboxes and Functions by reference.
A Sandbox can see a secret two ways: as environment variables
(secrets: [OpenAI]), or through an outbound header replacement
(outboundPolicy), where Modal adds the value to matching HTTPS requests
on their way out and the sandbox never holds it. Prefer the second for
credentials an agent’s code shouldn’t be able to read.
Modal never returns secret values, so every deploy overwrites the full
env map. Modal secrets have no tags; ownership is inferred from Alchemy
state.
Secret: Creating a Secret
Section titled “Secret: Creating a Secret”import * as Alchemy from "alchemy";import * as Modal from "@alchemy.run/modal";
export const OpenAI = Modal.Secret("OpenAI", { env: { OPENAI_API_KEY: Alchemy.Secret("OPENAI_API_KEY") },});Secret: Using a Secret in a Sandbox
Section titled “Secret: Using a Secret in a Sandbox”As environment variables
// construction phase: read the Secret's idconst openaiId = yield* (yield* OpenAI).secretId;// in a handlerconst openai = { secretId: yield* openaiId };const sandbox = yield* sandboxes.create({ secrets: [openai] });Injected into outbound requests only
// construction phase: read the Secret's idconst openaiId = yield* (yield* OpenAI).secretId;// in a handlerconst openai = { secretId: yield* openaiId };const sandbox = yield* sandboxes.create({ outboundPolicy: { routes: [ { https: { domains: ["api.openai.com"], headerReplacements: [{ secret: openai, headers: { Authorization: "Bearer $OPENAI_API_KEY" } }], }, }, { any: {} }, ], },});Secret: Rotating a Secret at runtime
Section titled “Secret: Rotating a Secret at runtime”const openai = yield* Modal.WriteSecret(OpenAI);yield* openai.put({ OPENAI_API_KEY: newKey });Secret: Looking up an existing secret
Section titled “Secret: Looking up an existing secret”Secret.lookup references a secret Alchemy doesn’t manage (created with
the Modal CLI or by another stack). It is read during plan/deploy, never
created, updated or deleted. Its values are never read; keys lists their
names.
const openai = Modal.Secret.lookup({ name: "openai" });WriteSecret
Section titled “WriteSecret”Source:
packages/modal/src/WriteSecret.ts
Rotate a Modal Secret from any runtime. Modal never returns secret values, so there is no read binding: attach the secret to a sandbox instead, or inject it with an outbound header replacement.
WriteSecret: Rotate a secret
Section titled “WriteSecret: Rotate a secret”import * as Cloudflare from "@alchemy.run/cloudflare";import * as Modal from "@alchemy.run/modal";import * as Effect from "effect/Effect";import * as Redacted from "effect/Redacted";import * as HttpServerResponse from "effect/http/HttpServerResponse";import { OpenAI } from "./Storage.ts";
export default class Rotate extends Cloudflare.Worker<Rotate>()( "Rotate", { main: import.meta.url }, Effect.gen(function* () { const openai = yield* Modal.WriteSecret(OpenAI); return { fetch: Effect.gen(function* () { yield* openai.put({ OPENAI_API_KEY: Redacted.make("sk-new") }); return HttpServerResponse.text("rotated"); }).pipe(Effect.orDie), }; }).pipe(Effect.provide(Modal.WriteSecretHttp)),) {}WriteSecretHttp
Section titled “WriteSecretHttp”Source:
packages/modal/src/WriteSecretHttp.tsKind: Layer · Provides:Modal.WriteSecret
WriteSecret over Modal’s API. Grants the host’s identity (a scoped
Modal.ServiceUser) the contributor role on the secret’s environment.