VendoVendo Docs
Security

Secret handling

How platform secrets, tenant connection credentials, and per-deployment secrets are stored, encrypted, and exposed to running tools.

Vendo deals with three classes of secret, each with a different store, a different encryption story, and a different path to the running tool. Conflating them is the most common source of confusion, so this page treats them separately.

1. Platform secrets (Vendo's own)

These are the credentials Vendo itself needs to operate: the API keys it uses to talk to Cloudflare, Railway, Neon, Supabase, OpenRouter, etc. Tool code never reads these.

  • Source of truth: Infisical Cloud (one project, three environments — dev, test, prod).
  • Distribution: Infisical Secret Syncs push resolved values to Cloudflare Workers (per-Worker, scoped to that Worker's declared keys), Supabase Edge Functions, and GitHub Actions on every change. Each destination receives only the keys its schema declares — not the full set.
  • Local development uses infisical run to inject values into a dev shell. No .env file is materialised on disk.
  • Rotation goes through Infisical; every reference to the rotated key resolves to the new value on the next sync.

There is no code path by which a deployed tool can read these. Cloudflare Worker environment is per-Worker; the proxy doesn't relay them; the deploy worker uses them to provision tenant resources but never writes them into tenant env.

2. Tenant connection credentials

These are the credentials a tenant adds to integrate with a provider — a Telegram bot token they pasted, the OAuth tokens returned when they connected Notion, the API key they provided to bring their own OpenAI access.

Storage depends on the credential's profile:

ProfileWhere the credential lives
byok_static (the tenant brought a static key)connections.encrypted_credential, AES-256-GCM encrypted at rest
oauth_app_install (first-party OAuth, e.g. Notion)connections.encrypted_credential, AES-256-GCM. The schema also has an encrypted_refresh_token column for providers whose tokens expire; today's only first-party provider is Notion, whose tokens don't expire, so that column is left null.
composio_managed (OAuth managed by Composio)Not stored in Vendo's database; Composio holds the token. Vendo stores a Composio account id only.
vendo_managed_pool (Vendo's shared key for the provider)Held as a Cloudflare Worker secret in the proxy Worker's env, synced from Infisical, not in the tenant's row
webhook_inbound (inbound webhook signing secret)connections.metadata.webhook.signing_secret

Encryption keys for the ciphertext columns are platform secrets (see §1) — not derivable from a tenant credential, not exposed to tools. Decryption only happens inside the proxy or credentials worker at the moment a request needs the cleartext.

Tool code never sees the raw credential. When the tool calls openai-proxy.vendo.run with its vendo_sk_* key, the proxy resolves the bound connection, decrypts the credential in Worker memory, builds the upstream request with the real credential, sends it, and discards the cleartext. The plaintext provider key is never written back to the tool's env, response, or logs.

For OAuth-style credentials, the tool calls credentials.vendo.run (or uses the SDK's vendo.token(provider)) to receive a short-lived access token. The refresh token never leaves Vendo's infrastructure.

3. Per-deployment generated secrets

When the deploy worker spins up a tool, it generates a few things and writes them into Vendo's database for later use at restart / retry time:

  • The deployment's vendo_sk_* proxy key (created once at deploy time, returned to the deployment as the VENDO_API_KEY env var). The hashed-bearer form is stored in keys.key_hash; the cleartext is never re-readable from the database after issuance.
  • An admin password if the tool's manifest declares bootstrap_admin: true. This lands in deployment_credentials.secret (TEXT) alongside username. This row is plaintext today — protected by RLS (only the tenant owner reads it, via the dashboard's "show admin password" path) and service-role policy, not by application-layer encryption.
  • Any random_hex / random_base64 / uuid values declared as generated in the manifest, plus any user-edited env vars (see "Editing env vars" below). These are AES-256-GCM encrypted with INTEGRATION_ENCRYPTION_KEY and stored in deployment_env_vars.value_encrypted. They're decrypted in Worker memory to provision Railway env vars at deploy / restart / retry time and otherwise stay at rest.

What the tool actually sees at runtime

The deploy worker writes a flat set of environment variables into the Railway service before booting the tool. From the tool's perspective:

  • DATABASE_URL, REDIS_URL, R2_* — cleartext, pointing at the resources Vendo provisioned for this deployment.
  • OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. — these are vendo_sk_* keys, not the real provider keys. The proxy swaps them for the real ones on the way out.
  • OPENAI_BASE_URL, etc. — pointed at the relevant *-proxy.vendo.run subdomain.
  • Other env vars derived from bound connections (e.g. TELEGRAM_BOT_TOKEN) — these may be real tokens when the integration model is a static credential, or vendo_sk_* markers when access flows through a proxy.
  • VENDO_TENANT_ID, VENDO_DEPLOYMENT_ID, VENDO_DEPLOYMENT_SLUG — non-secret identifiers.

The tool sees no Vendo platform secrets, no other tenant's credentials, and no decryption keys.

Editing env vars

A tenant can edit the user-facing env vars (kind='user') from the dashboard. These are persisted encrypted in deployment_env_vars and pushed to Railway on the next restart. The dashboard rejects attempts to set keys that the platform owns (kind='system' or kind='integration') — those are managed by the deploy and binding flows.

Logs

The deploy logs surface the step name and structured progress, never the cleartext of any secret. Provider responses streamed through the proxy are not stored. Your tool's own stdout is captured by Railway / Cloudflare and visible to the tenant in the dashboard — if your tool prints a secret, it will appear there. Treat tenant-visible logs as untrusted output: do not log secrets your tool receives, even Vendo-owned ones, and don't rely on the platform to redact them after the fact.

The full Infisical-based platform secret runbook is internal. The piece that matters for tool authors: tenant credentials are encrypted at rest, decrypted only at the proxy edge, and never written into your tool's env or logs in cleartext.

On this page