Back to Hub
CREATED BY CHAINLINK LABS

Hello Confidential Workflows

Quickstart confidential workflow that registers a TEE handler, securely fetches a secret inside the enclave, executes a capability call from within the enclave, and returns to the DON for any operations requiring decentralized consensus.

Get the template
cre init --template=hello-confidential-workflows-ts
View on GitHub

This template shows the smallest useful shape of a confidential CRE workflow: register a handler that runs in a TEE, pull a secret inside the enclave, make a capability call from inside the enclave, then hand only the non-sensitive result back to the Workflow DON for consensus-backed operations.

Quick navigation:


What This Template Does

By default, a CRE workflow callback runs on Workflow DON nodes, where node operators can inspect what it is computing. That is acceptable for many workflows, but not for when sensitive or proprietary data is required for workflows.

A Confidential Workflow executes the logic requiring confidential inputs in a hardware-isolated enclave. This template demonstrates the minimal end-to-end shape in four steps:

StepWhat it demonstratesAPI
1Register a handler that runs inside a TEEcre.handlerInTee(trigger, fn, tees)
2Fetch a secret inside the enclaveruntime.getSecret({ id }) / runtime.GetSecret()
3Make a capability call from inside the enclaveHTTPClient.sendRequest(runtime, req)
4Cross back to the DON for anything needing consensusruntime.usingTheDons()

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CronTrigger โ”‚  fires on schedule (runs on the Workflow DON)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚  DON hands the triggered request to an enclave
       v
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ ENCLAVE                                                              โ•‘
โ•‘                                                                      โ•‘
โ•‘ Step 2: runtime.getSecret({ id: 'API_TOKEN' })                       โ•‘
โ•‘           โ–ฒ                                                          โ•‘
โ•‘           โ””โ”€โ”€ released by Vault DON, decrypted in-enclave            โ•‘
โ•‘                                                                      โ•‘
โ•‘ Step 3: HTTPClient.sendRequest(runtime, { ... })                     โ•‘
โ•‘           Authorization: Bearer <secret>                             โ•‘
โ•‘           โ–ฒ trust from enclave attestation, not consensus            โ•‘
โ•‘                                                                      โ•‘
โ•‘ Logic with confidential data: score(response) vs. scoreThreshold     โ•‘
โ•‘           -> verdict = APPROVE | REJECT                              โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
                           โ”‚  Step 4: runtime.usingTheDons()
                           โ”‚  ONLY the verdict + score cross out
                           v
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WORKFLOW DON - donRuntime.report({ ... })                            โ”‚
โ”‚ BFT consensus verifies the enclave attestation, then signs           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

How It Works

my-workflow/workflow.ts and my-workflow/workflow.go follow the same confidential pattern:

  1. Register the cron handler with cre.handlerInTee, constrained to [{ tee: 'nitro', regions: ['us-west-2'] }]
  2. Fetch API_TOKEN inside the enclave - the Vault DON releases it only into an attested enclave
  3. Call the configured URL from inside the enclave, sending the secret in the Authorization header
  4. Score the response against scoreThreshold - this represents the private policy you want to keep hidden
  5. Cross back with usingTheDons() and report only the verdict and score - never the secret or raw response

TypeScript Implementation

Getting Started with TypeScript

1 Install dependencies
cd my-workflow && bun install && cd ..
2 Configure secrets
cp .env.example .env

Set SECRET_API_TOKEN in .env. secrets.yaml maps the workflow-facing secret ID API_TOKEN to that environment variable:

secretsNames:
  API_TOKEN:
    - SECRET_API_TOKEN

With the default echo endpoint, any non-empty value works.

3 Run tests
cd my-workflow && bun test
4 Simulate
cre workflow simulate my-workflow --target staging-settings --non-interactive --trigger-index 0

Expected output:

[SIMULATION] Running trigger trigger=cron-trigger@1.0.0
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Trigger requested TEE Execution your trigger will run in one of the following Tees:                โ”‚
โ”‚     - AWS Nitro in us-west-2                                                                       โ”‚
โ”‚ The simulator is not a real TEE, and is meant to debug.                                            โ”‚
โ”‚ Do not use it for sensitive information.                                                           โ”‚
โ”‚ During real execution, user logs for this trigger will not be visible, and will not leave the TEE. โ”‚
โ”‚ They are presented in the simulator for debugging only.                                            โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

[USER LOG] Enclave computation complete. verdict=REJECT

โœ“ Workflow Simulation Result:
"REJECT (score: 371, secret reached API: true)"

Three things to notice:

  • The simulator confirms the TEE constraint it resolved (AWS Nitro in us-west-2) and warns that it is not a real enclave. In real execution those logs never leave the TEE.
  • secret reached API: true means the Vault DON secret was fetched inside the enclave and arrived in the outbound request's Authorization header.
  • The verdict flips between APPROVE and REJECT from run to run. That is expected: the score is derived from the live response body, and the echo endpoint includes a per-request trace ID.

Go Implementation

Getting Started with Go

1 Install dependencies
cd my-workflow && go mod tidy && cd ..
2 Configure secrets
cp .env.example .env

Set SECRET_API_TOKEN in .env. secrets.yaml maps the workflow-facing secret ID API_TOKEN to that environment variable:

secretsNames:
  API_TOKEN:
    - SECRET_API_TOKEN

With the default echo endpoint, any non-empty value works.

3 Run tests
cd my-workflow && go test ./...
4 Simulate
cre workflow simulate my-workflow --target staging-settings --non-interactive --trigger-index 0

Expected output:

[SIMULATION] Running trigger trigger=cron-trigger@1.0.0
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Trigger requested TEE Execution your trigger will run in one of the following Tees:                โ”‚
โ”‚     - AWS Nitro in us-west-2                                                                       โ”‚
โ”‚ The simulator is not a real TEE, and is meant to debug.                                            โ”‚
โ”‚ Do not use it for sensitive information.                                                           โ”‚
โ”‚ During real execution, user logs for this trigger will not be visible, and will not leave the TEE. โ”‚
โ”‚ They are presented in the simulator for debugging only.                                            โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

[USER LOG] Enclave computation complete. verdict=REJECT

โœ“ Workflow Simulation Result:
"REJECT (score: 371, secret reached API: true)"

The Go and TypeScript versions share the same confidential workflow shape. The main differences are the language tooling and the SDK surface: Go uses runtime.GetSecret() and the Go HTTPClient implementation, while TypeScript uses runtime.getSecret() and the TypeScript SDK.

Configuration

my-workflow/config.staging.json:

FieldDescription
scheduleCron expression (6 fields, seconds first)
urlEndpoint called from inside the enclave
secretIdSecret ID fetched with runtime.getSecret() / runtime.GetSecret(); must match secrets.yaml
scoreThresholdThreshold the confidential scoring compares against

TEE Constraints

The third argument to handlerInTee declares which enclaves the handler accepts:

{
} // any registered TEE, any region
{
  regions: ["us-west-2"]
} // any TEE, restricted to a region
;[{ tee: "nitro", regions: ["us-west-2"] }] // specific TEE types and regions

AWS Nitro in us-west-2 is currently the only registered TEE type and region.

Confidentiality Boundary

Understanding what is and is not protected matters more here than in a regular workflow.

Protected by defaultNot automatically protected
Secrets the Vault DON releases into the enclaveTriggers, chain reads, and chain writes - these always run on Workflow DON nodes
Sensitive inputs and intermediate values you do not share outside the enclaveYour workflow's source code and deployed binary
Capability calls made from inside the enclaveCapability calls not routed through the enclave
Enclave execution memory, while your computation runsReports, calldata, and any output you deliver outside the enclave

Consequences worth internalizing:

  • Your handlerโ€™s source code and compiled binary are not confidential just because part of its logic runs inside an enclave. Confidential Workflows protect only the confidential data processed during execution inside the enclave, including Vault DON secrets (such as API keys), HTTP response, and intermediate values that are not explicitly shared outside the enclave. Support for confidential logic or workflow is planned as a future enhancement, not part of the current beta.
  • usingTheDons() is a one-way door. Anything you pass into a capability call on that runtime executes on Workflow DON nodes like any non-confidential call. Cross over only what does not need to stay hidden.
  • Do not log from inside the enclave in production. Logs leave the confidentiality boundary. This template logs only the verdict, and the comment marks it for removal before deploying.
  • Keep enclave logic deterministic. The enclave result is attested and verified by DON consensus.
  • Multiple confidential workflows may execute within the same enclave. Workflows are isolated from one another by the wasmtime. Dedicated per workflow enclave isolation is planned as a future enhancement.

Which Secrets Belong in an Enclave?

Not every secret needs enclave-level protection.

Higher value - consider enclave execution: wallet and CA private keys; exchange, custody, payment-processor, banking, or LLM-provider credentials; OAuth client secrets, JWT signing keys, KMS keys; payment data, health data, other PII.

Lower value - regular DON execution is usually fine: API keys for publicly available data (weather, explorers, public price feeds, public RPCs); public wallet addresses.

The common thread: a secret belongs in the enclave if disclosure would expose more than the workflow needs.

Customization

  • Put your real logic in the enclave - replace scoreResponse in workflow.ts or workflow.go with the policy, threshold, or model you need to keep private
  • Deliver the report onchain - pass the report from Step 4 to evmClient.writeReport(donRuntime, report); the RPCs in project.yaml are already set up for Sepolia. See the Keeper Bot or Event Reactor templates for the full write path
  • Change the trigger - handlerInTee accepts any CRE trigger, same as handler; swap cron for a log trigger to react to onchain events confidentially
  • Fetch more secrets - call runtime.getSecret() / runtime.GetSecret() once per secret

Security

  • Never commit .env files or secrets - .gitignore covers *.env
  • Remove or gate every runtime.log() inside the TEE handler before deploying
  • Audit what crosses usingTheDons(); that data is no longer confidential

Further Reading

Get the latest Chainlink content straight to your inbox.