← Back to all posts
4 min readCentrali Team

Encrypted Parameters: Keeping Secrets Safe in Orchestrations

Orchestration compute steps can now hold encrypted parameters — API keys, tokens, and credentials that are encrypted at rest, masked in responses, and decrypted only at execution time.

orchestrationArchitectureFeature

Orchestrations coordinate multi-step workflows — evaluate a request, wait for approval, call an external API. But that last step often needs credentials: a GitHub App private key, a Stripe secret key, an API token for a third-party service.

Before encrypted parameters, you had two options: hardcode secrets in function code (bad), or pass them through trigger parameters in plaintext (also bad). Now there's a proper answer.

How It Works

Compute steps in orchestrations now support an `encryptedParams` field. When you create or update an orchestration, include the secrets on the step that needs them:

```json { "steps": [{ "id": "provision-repo", "type": "compute", "functionId": "fn-create-github-repo", "encryptedParams": { "GITHUB_PRIVATE_KEY": { "value": "-----BEGIN RSA...", "encrypt": true }, "GITHUB_APP_ID": { "value": "12345", "encrypt": true } } }] } ```

When you set `encrypt: true`, the value is encrypted before it hits the database. The orchestration service uses AES-256-GCM with a random IV per encryption, so even identical values produce different ciphertexts.

What Happens at Each Stage

On Save

When you create or update an orchestration with encrypted params:

  1. The orchestration service encrypts each param value using the active secret key
  2. The plaintext is replaced with the encrypted blob: `{ value: "iv:ciphertext:tag", encrypted: true, keyVersion: 1 }`
  3. The definition is stored in PostgreSQL with encrypted values

On Read

When you fetch an orchestration via the API, encrypted values are masked:

```json { "GITHUB_PRIVATE_KEY": { "value": "********", "encrypted": true } } ```

The API never returns encrypted blobs or plaintext. You can see which params exist, but not their values.

On Execute

When the orchestration engine runs a compute step:

  1. It loads the full definition from the database (with encrypted blobs)
  2. Decrypts each param using the stored key version
  3. Merges decrypted values into the step input (encrypted params take precedence on key collisions)
  4. Sends the merged input to the compute function as `triggerParams`

The function receives plaintext secrets in `triggerParams` — identical to how it would receive them from a regular trigger. The function code doesn't need to know whether it's running in an orchestration or from a direct trigger invocation.

Key Rotation

The `SECRET_KEYS` configuration supports versioned keys:

```json { "currentKeyVersion": 2, "keys": { "1": "old-key-still-valid-for-decryption", "2": "new-key-used-for-new-encryptions" } } ```

Each encrypted param stores its `keyVersion`, so old values remain decryptable even after rotation. New encryptions use the current key version. This means you can rotate keys without re-encrypting existing orchestrations.

Why Step-Level, Not Orchestration-Level

Secrets are scoped to the step that needs them, not the entire orchestration. This matters because:

  • Least privilege: the approval step doesn't see the GitHub credentials that only the provisioning step needs
  • One place to update: when credentials rotate, you update one step, not every function that uses them
  • Audit clarity: you can see exactly which step holds which secrets

SDK and MCP Support

Both the SDK and MCP tools support encrypted params:

SDK: ```typescript await client.createOrchestration({ name: 'Provision Repo', steps: [{ type: 'compute', functionId: 'fn-create-repo', encryptedParams: { API_KEY: { value: 'sk_live_...', encrypt: true } } }] }); ```

MCP: The `create_orchestration` and `update_orchestration` tools accept the same format. Existing encrypted params with `encrypted: true` are preserved as-is during updates; omitted params are removed.

Encrypted parameters are available now for all orchestration compute steps.

Building something with Centrali and want to share feedback about this feature?

Email feedback@centrali.io