Skip to content

Commit dac4330

Browse files
committed
Merge origin/develop into tejaswi/relay-gateway-dumb-bundle
Resolve the aggregator_test.go modify/delete conflict by keeping #22807's deletion: #22516 (now on develop) added validEnclaveConfig to that file, but the dumb-bundler replaces the aggregator entirely and that helper is unused elsewhere. Relay packages build and tests pass with #22516's config-verify changes merged in.
2 parents f54d361 + e5fd2dc commit dac4330

31 files changed

Lines changed: 1144 additions & 379 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink": patch
3+
---
4+
5+
#internal Confidential workflows: stop setting the deprecated outside-envelope `ConfidentialWorkflowRequest.binary_url`. `binary_url` stays in the hashed `WorkflowExecution` (PublicData); the enclave reads it there.

.github/actions/setup-wasmd/action.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

.github/workflows/build-publish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,10 @@ jobs:
114114
docker-cache-behaviour: "disable"
115115
docker-manifest-sign: true
116116
docker-registry-url-override: public.ecr.aws/chainlink
117+
github-runner-amd64: ${{ github.repository != 'smartcontractkit/chainlink' && 'ubuntu24.04-4cores-16GB' || 'ubuntu-24.04' }}
117118
github-runner-arm64: ${{ github.repository != 'smartcontractkit/chainlink' && 'ubuntu-24.04-4cores-16GB-ARM' || 'ubuntu-24.04-arm' }}
118119
docker-image-tag-strip-prefix: v # strip out the "v" prefix from the git tag for the image tag.
120+
free-disk-space: "true"
119121
git-sha: ${{ github.sha }}
120122
github-event-name: ${{ github.event_name }}
121123
github-ref-name: ${{ github.ref_name }}
@@ -155,8 +157,10 @@ jobs:
155157
docker-cache-behaviour: "disable"
156158
docker-manifest-sign: true
157159
docker-registry-url-override: public.ecr.aws/chainlink
160+
github-runner-amd64: ${{ github.repository != 'smartcontractkit/chainlink' && 'ubuntu24.04-4cores-16GB' || 'ubuntu-24.04' }}
158161
github-runner-arm64: ${{ github.repository != 'smartcontractkit/chainlink' && 'ubuntu-24.04-4cores-16GB-ARM' || 'ubuntu-24.04-arm' }}
159162
docker-image-tag-override: ${{ needs.checks.outputs.ccip-image-tag }}
163+
free-disk-space: "true"
160164
git-sha: ${{ github.sha }}
161165
github-event-name: ${{ github.event_name }}
162166
github-ref-name: ${{ github.ref_name }}

.github/workflows/ci-core.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,6 @@ jobs:
355355
github-token: ${{ secrets.GITHUB_TOKEN }}
356356
version: mainnet-v1.72.2
357357

358-
- name: Setup wasmd
359-
if: ${{ matrix.type.should-run == 'true' }}
360-
uses: ./.github/actions/setup-wasmd
361-
362358
- name: Setup Postgres
363359
if: ${{ matrix.type.should-run == 'true' }}
364360
uses: smartcontractkit/.github/actions/setup-postgres@setup-postgres/v1

.github/workflows/ci-deployments.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ jobs:
168168
with:
169169
github-token: ${{ secrets.GITHUB_TOKEN }}
170170
version: mainnet-v1.72.2
171-
- name: Setup wasmd
172-
uses: ./.github/actions/setup-wasmd
173171
- name: Setup Postgres
174172
uses: smartcontractkit/.github/actions/setup-postgres@setup-postgres/v1
175173
with:

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ linters:
7070
desc: Cosmos only runs as a LOOPP
7171
- pkg: github.com/smartcontractkit/chainlink-integrations/evm
7272
desc: Use github.com/smartcontractkit/chainlink-evm instead
73+
- pkg: github.com/smartcontractkit/chainlink-solana
74+
desc: Solana only runs as a LOOPP
7375
- pkg: github.com/smartcontractkit/chainlink-starknet/relayer
7476
desc: Starknet only runs as a LOOPP
7577
# - pkg: github.com/smartcontractkit/chainlink-tron

core/capabilities/confidentialrelay/handler.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package confidentialrelay
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/base64"
67
"encoding/hex"
78
"encoding/json"
89
"errors"
910
"fmt"
11+
"sort"
1012
"time"
1113

1214
"github.com/ethereum/go-ethereum/common"
@@ -220,6 +222,20 @@ func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *j
220222
if err := h.verifyAttestationHash(ctx, att, params, confidentialrelaytypes.DomainSecretsGet); err != nil {
221223
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err)
222224
}
225+
// Fetch the local node once: it provides both the WorkflowDON snapshot for
226+
// the enclave-config check below and the DON metadata on the vault request.
227+
localNode, err := h.capRegistry.LocalNode(ctx)
228+
if err != nil {
229+
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to get local node: %w", err))
230+
}
231+
// Verify the enclave's reported config matches the onchain DON state
232+
// before treating the attested request as trusted: the Nitro attestation
233+
// binds the request hash, but a malicious host can produce a
234+
// genuinely-attested request over a forged enclave config unless we
235+
// compare the config value against the DON reference.
236+
if err = h.verifyEnclaveConfigMatchesDON(localNode, params.EnclaveConfig); err != nil {
237+
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err)
238+
}
223239

224240
vaultCap, err := h.capRegistry.GetExecutable(ctx, vault.CapabilityID)
225241
if err != nil {
@@ -255,11 +271,6 @@ func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *j
255271
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to wrap vault request: %w", err))
256272
}
257273

258-
localNode, err := h.capRegistry.LocalNode(ctx)
259-
if err != nil {
260-
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to get local node: %w", err))
261-
}
262-
263274
metadata := capabilities.RequestMetadata{
264275
WorkflowID: params.WorkflowID,
265276
WorkflowOwner: params.Owner,
@@ -389,6 +400,13 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string,
389400
if err := h.verifyAttestationHash(ctx, att, params, confidentialrelaytypes.DomainCapabilityExec); err != nil {
390401
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err)
391402
}
403+
localNode, err := h.capRegistry.LocalNode(ctx)
404+
if err != nil {
405+
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to get local node: %w", err))
406+
}
407+
if err = h.verifyEnclaveConfigMatchesDON(localNode, params.EnclaveConfig); err != nil {
408+
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err)
409+
}
392410

393411
capability, err := h.capRegistry.GetExecutable(ctx, params.CapabilityID)
394412
if err != nil {
@@ -461,6 +479,51 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string,
461479
return h.jsonResponse(req, signedResult)
462480
}
463481

482+
// verifyEnclaveConfigMatchesDON compares the enclave's reported EnclaveConfig
483+
// against the local node's WorkflowDON membership and fault tolerance. The
484+
// relay DON runs on the same nodes as the workflow DON, so
485+
// localNode.WorkflowDON.Members is the right comparison target.
486+
//
487+
// PRIV-458: the Nitro attestation binds the request hash but does not on its
488+
// own prove the config matches the DON, so a malicious host could produce a
489+
// genuinely-attested request over a forged config. Comparing the attested
490+
// config against onchain DON state closes that gap.
491+
//
492+
// localNode is passed in so each request fetches it once (it feeds request
493+
// metadata too); the caller's lookup is an O(1) in-memory read populated by
494+
// the registry syncer, so this stays off the RPC hot path.
495+
//
496+
// cfg is optional: a nil EnclaveConfig (sender on an older protocol that does
497+
// not include it) is accepted and skips the check. The config is verified
498+
// only when present.
499+
func (h *Handler) verifyEnclaveConfigMatchesDON(localNode capabilities.Node, cfg *confidentialrelaytypes.EnclaveConfig) error {
500+
if cfg == nil {
501+
return nil
502+
}
503+
expectedF := uint32(localNode.WorkflowDON.F)
504+
if cfg.F != expectedF {
505+
return fmt.Errorf("enclave config F mismatch: enclave reports %d, expected %d", cfg.F, expectedF)
506+
}
507+
if len(cfg.Signers) != len(localNode.WorkflowDON.Members) {
508+
return fmt.Errorf("enclave config signers count mismatch: enclave reports %d, expected %d",
509+
len(cfg.Signers), len(localNode.WorkflowDON.Members))
510+
}
511+
expected := make([][]byte, len(localNode.WorkflowDON.Members))
512+
for i := range localNode.WorkflowDON.Members {
513+
expected[i] = localNode.WorkflowDON.Members[i][:]
514+
}
515+
actual := append([][]byte(nil), cfg.Signers...)
516+
sort.Slice(actual, func(i, j int) bool { return bytes.Compare(actual[i], actual[j]) < 0 })
517+
sort.Slice(expected, func(i, j int) bool { return bytes.Compare(expected[i], expected[j]) < 0 })
518+
for i := range actual {
519+
if !bytes.Equal(actual[i], expected[i]) {
520+
return fmt.Errorf("enclave config signer mismatch at sorted index %d: enclave reports %x, expected %x",
521+
i, actual[i], expected[i])
522+
}
523+
}
524+
return nil
525+
}
526+
464527
// getEnclaveAttestationConfig reads the enclave pool configuration from the
465528
// capabilities registry and returns trusted measurement sets and CA roots
466529
// for attestation validation. Called per-request so the config stays fresh

0 commit comments

Comments
 (0)