Reference

Security

Model keys, redaction defaults, key storage, and workspace isolation.

Assemble is designed so that running your workflows against the hosted platform never exposes your model credentials or raw payloads by default. The security model has two halves: model keys that never leave your process, and trace payloads that are redacted before upload.

This page covers both, plus how the platform stores its own API keys and isolates workspaces. For where these keys are created and managed, see Dashboard and API keys.

Bring your own model keys#

You bring your own model keys. Provider adapters (openai, anthropic, gemini, azureOpenai, bedrock, openrouter, cloudflareWorkersAi) call the provider directly from your process using credentials you supply — read from the provider's environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, AZURE_OPENAI_API_KEY, OPENROUTER_API_KEY, CLOUDFLARE_API_TOKEN, or the AWS credential variables) or the adapter's credential options. See Model providers.

  • Assemble never proxies your model calls. Model requests go straight from your machine to the provider.
  • Provider keys are never serialized into trace events, run state, or uploads. They live only in your process memory and the model adapter.
  • Assemble never pays for your model usage; you are billed by your provider on your own account.

Redaction is the backstop, not the mechanism: keys stay out of traces by construction, and the upload pipeline additionally masks anything secret-shaped that ends up in a payload by accident.

Redaction defaults#

Trace events carry the inputs and outputs of each step. Before anything is uploaded to the hosted platform, payloads pass through redaction. The default mode is redacted.

ModeWhat is uploadedWhat is masked or dropped
redacted (default)Event structure, types, timing, agent and tool names, routing and policy decisions, and payload values that are not secret-shaped.Secret-shaped strings and substrings, secret-named fields, and your sensitiveFieldNames — all replaced with a redaction placeholder. Every uploaded event is marked redacted.
metadata-onlyEvent metadata plus a structural summary of each payload.The payload contents themselves are dropped entirely.
fullPayloads as-is.Provider-API-key-shaped values and your sensitiveFieldNames are still masked, even in this mode.

What is never uploaded in any mode:

  • Raw provider API keys and other provider-key-shaped values. These are masked before the event leaves your process — including in full mode.
  • Fields you list in a tool's sensitiveFieldNames, which are matched case-insensitively and masked in addition to automatic detection.

Warning: full mode still uploads request and response text verbatim apart from the exceptions above. Reserve it for traces whose contents you would be comfortable storing in the hosted workspace.

What automatic detection catches#

In redacted mode, a string value is fully masked when it matches a known secret shape, and secret-shaped fragments inside longer strings are masked in place. The detectors are:

  • Values starting with well-known secret prefixes followed by a secret-like body of at least 12 characters: sk-, sk_live_, sk_test_, asm_, ghp_, gho_, ghs_, ghu_, github_pat_, xoxb-, xoxp-, xoxa-, xoxs-, pk_live_, rk_live_, whsec_, and AKIA.
  • Bearer credentials — any string of the form Bearer <token>.
  • JWT-shaped strings (an eyJ... header segment followed by a payload segment).

Field names also trigger masking regardless of the value: a field named authorization, or any field whose name ends with api_key, apikey, secret, token, or password (case-insensitive), is replaced entirely.

Marking tool fields as sensitive#

Automatic detection cannot know that taxId or homeAddress is sensitive in your domain. Declare those on the tool:

src/tools/lookup-customer.ts
import { tool } from "@assemble-dev/sdk";
import { z } from "zod";

const lookupCustomer = tool({
  name: "lookup_customer",
  description: "Look up a customer profile by id",
  sensitiveFieldNames: ["taxId", "homeAddress"],
  inputSchema: z.object({ customerId: z.string() }),
  execute: async (input) => fetchCustomerProfile(input.customerId),
});

Fields with these names are masked in uploaded payloads in every mode that uploads payload content, on top of the automatic detectors.

Where redaction runs#

Redaction happens in the platform trace sink — the component that uploads events — not in the tracer itself. Local console output and the JSONL traces under .assemble/runs/ keep full fidelity on your machine, while the hosted copy of the same run stays redacted. You debug locally with everything; the workspace sees the redacted projection. See Tracing.

API key storage#

Assemble developer, production, and CI keys use the format:

txt
asm_<dev|prod|ci>_<32 hex>_<64 hex>

The 32-hex segment is a key identifier; the 64-hex segment is the secret.

  • Keys are shown once, at creation time in the dashboard. Copy the key then; it cannot be retrieved again.
  • The platform stores only a hash of the key, never the raw value, so a database read cannot recover your key.
  • The CLI saves your key to ~/.assemble/config.json on your machine and displays it masked (for example asm_dev_…1a2b) in asm whoami and asm login output.
  • If a key is revoked, asm login and asm whoami report it as rejected and prompt you to create a new one. Uploads with a revoked key fail.

Note that asm_ is itself one of the detected secret prefixes, so an Assemble key that accidentally lands in a payload is masked before upload like any other secret.

Workspace isolation#

Every run, trace, eval, and approval belongs to a workspace, and the API key you upload with is scoped to a single workspace. Runs from one workspace are never visible to another. The dashboard's members list controls who in your workspace can see runs and act on approvals.

You can share a read-only link to a trace so someone without a workspace login can view it. Shared links inherit the redaction that applied at upload time — a link to a run captured in redacted mode shows the redacted payloads, never the original values — and they grant read-only access to that single run, not to your workspace. Because redaction runs before upload, no share-time setting can reveal data the platform never received.