Skip to content

Modal.Secret reference

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))),
) {}

Source: packages/modal/src/CreateSecretHttp.ts Kind: Layer · Provides: Modal.CreateSecret

CreateSecret over Modal’s API. Grants the host’s identity (a scoped Modal.ServiceUser) the contributor role on the environment.

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.

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") },
});

As environment variables

// construction phase: read the Secret's id
const openaiId = yield* (yield* OpenAI).secretId;
// in a handler
const openai = { secretId: yield* openaiId };
const sandbox = yield* sandboxes.create({ secrets: [openai] });

Injected into outbound requests only

// construction phase: read the Secret's id
const openaiId = yield* (yield* OpenAI).secretId;
// in a handler
const 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: {} },
],
},
});
const openai = yield* Modal.WriteSecret(OpenAI);
yield* openai.put({ OPENAI_API_KEY: newKey });

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" });

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.

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)),
) {}

Source: packages/modal/src/WriteSecretHttp.ts Kind: 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.