✨ Add workspace-level encrypted secrets (server-only resolution) [draft — refs #627]#2458
Draft
klinux wants to merge 1 commit intobaptisteArno:mainfrom
Draft
✨ Add workspace-level encrypted secrets (server-only resolution) [draft — refs #627]#2458klinux wants to merge 1 commit intobaptisteArno:mainfrom
klinux wants to merge 1 commit intobaptisteArno:mainfrom
Conversation
Introduces a new workspace-scoped `WorkspaceSecret` entity that lets users
define reusable encrypted values (API keys, tokens) and reference them from
typebots via `{{$secrets.NAME}}`.
Security model:
- AES-256-GCM at rest (reuses @typebot.io/credentials encrypt/decrypt).
- Resolution only happens in explicitly marked server-side execution points.
This PR wires it into the HTTP request block (URL, headers, body, basic
auth). Additional integrations (email, etc.) can follow in a separate PR.
- Secrets are never written to Result.variables, SetVariableHistoryItem, or
any client-visible payload. They are fetched and substituted just-in-time
inside the block executor and discarded.
- WorkspaceSecret is a distinct entity from Variable, so there is no way to
accidentally mix a secret into a plaintext variable pipeline.
- assertNoSecretReferences helper is available to make builder-side
validation reject `{{$secrets.X}}` in client-side contexts (UI wiring
comes in the follow-up PR).
Refs baptisteArno#627
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@klinux is attempting to deploy a commit to the Typebot Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Draft implementation of workspace-scoped encrypted secrets — a new
WorkspaceSecretentity that lets users define reusable encrypted values (API keys, tokens, etc.) at the workspace level and reference them from typebots via{{$secrets.NAME}}. Refs #627.Opening as a draft because I'd like design feedback before investing in the builder UI. We're using typebot in production and this gap (no way to store reusable secrets safely) is blocking a few flows, so I started an implementation — happy to rework the design if you'd prefer a different direction.
Why a new entity (not a flag on
Variable)The issue mentions "encryption option for a variable." I considered adding an
isSecretflag toVariable, but the existingVariabletype flows through ~35 call sites (text bubbles, client responses,Result.variables,SetVariableHistoryItem, code blocks executed in the browser, etc.). A runtime flag there is fragile — a single missed check leaks the secret.A separate entity with a distinct type makes accidental leakage a compile error: secrets and variables literally can't be mixed. Downside: more surface area (schema + package + router) vs. a single field. I went with the safer option but happy to pivot if you prefer the flag approach.
Security model
@typebot.io/credentialsencrypt/decrypt(sameENCRYPTION_SECRET).Result.variables,SetVariableHistoryItem, chat replies, or any client-visible payload. They're fetched, substituted, and discarded.WorkspaceSecretis separate fromVariable, so they can't be mixed by accident.assertNoSecretReferenceshelper is exported for builder-side validation (UI wiring comes in PR Sign in by email doesn't work behind a company proxy #2) to reject{{$secrets.X}}in client-side contexts like text bubbles.isWriteWorkspaceForbidden). Members can reference them in typebots they already have write access to.Scope of this PR
This is PR 1 of ~3 (kept small for easier review):
I'm not opening PR 2/3 until you validate the design here so we don't waste effort if you want a different approach.
Files changed
New package
@typebot.io/workspace-secrets:constants.ts—{{$secrets.NAME}}regex (uppercase, digits, underscores; max 64 chars)schemas.ts— Zod schemas for name/valueencryptSecretValue.ts/decryptSecretValue.ts— thin wrappers over credentials encryptextractSecretReferences.ts— pull names out of a stringgetWorkspaceSecretsByNames.ts— batch fetch + decrypt by (workspaceId, names[])resolveSecretReferences.ts— substitute{{$secrets.NAME}}with decrypted valueassertNoSecretReferences.ts— throw if a string contains secret refs (for builder validation)Prisma: new
WorkspaceSecretmodel in bothmysqlandpostgresqlschemas (@@unique([workspaceId, name]), cascade on workspace delete). Migration needs to be generated by a maintainer with access to the migration tooling.tRPC router:
apps/builder/src/features/workspaceSecrets/api/withcreateWorkspaceSecret,listWorkspaceSecrets(metadata only, no values),updateWorkspaceSecret,deleteWorkspaceSecret. Registered inapps/builder/src/app/api/router.tsasworkspaceSecrets.HTTP request block integration:
parseHttpRequestAttributesinpackages/bot-engine/src/blocks/integrations/httpRequest/executeHttpRequestBlock.tsnow resolves secret references in URL, headers, body and basic auth after variable substitution. Server-only — viewer never sees the values.Questions for you
isSecretflag onVariable. I went with separate; which do you prefer?{{$secrets.NAME}}. Alternatives considered:{{secret:NAME}},{{@NAME}}. Open to changing before UI work.NEXT_PUBLIC_WORKSPACE_SECRETS_ENABLED) so it can merge early and enable progressively?Test plan
bunx nx typecheck— passes for new/modified packagesbunx nx test @typebot.io/workspace-secrets— 10/10 unit tests passing (regex, extract, assert)bunx nx format-and-lint --fix— cleannx affected -t format-and-lint,lint-repo,check-broken-links,test) — all greenResult.variablesorSetVariableHistoryItem(happy to add before merge)🤖 Generated with Claude Code