Verify enclave config against onchain DON in confidentialrelay handler (PRIV-458)#22516
Conversation
…r (PRIV-458) The handler now compares the attested EnclaveConfig in every incoming SecretsRequestParams and CapabilityRequestParams against the local node's WorkflowDON membership and fault tolerance, after Nitro attestation validation succeeds. Closes Sigma Prime CL112-01 on the relay-DON path. The relay DON runs on the same nodes as the workflow DON, so localNode.WorkflowDON.Members is the right comparison target. LocalNode is an O(1) in-memory map lookup populated by the registry syncer on a ~12s tick, so the check stays off the RPC hot path. Up to ~12s staleness applies during DON membership rotations and is acceptable given how rare those events are. Tests cover match-accepts, F mismatch, signers-count mismatch, signer value mismatch, order-independent comparison, and the secrets-get path. Existing tests updated to fill EnclaveConfig in fixtures with a matching WorkflowDON.Members in the mock registry. Bumps chainlink-common to v0.11.2-0.20260518112011-40a8e4cedaa8 to pick up the EnclaveConfig field on confidentialrelay request params (smartcontractkit/chainlink-common#2063). Companion PRs: - smartcontractkit/confidential-compute#329 (pool.go-side check). - smartcontractkit/chainlink-common#2063 (field on params). - smartcontractkit/confidential-compute#330 (enclave fills field).
|
👋 nadahalli, thanks for creating this pull request! To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team. Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks! |
|
I see you updated files related to
|
|
✅ No conflicts with other open PRs targeting |
There was a problem hiding this comment.
Pull request overview
Risk Rating: MEDIUM — changes add new validation in the confidential relay request handling path (security hardening) and bump a shared dependency (chainlink-common).
This PR hardens the confidential relay handler by verifying that the attested EnclaveConfig in incoming requests matches the node’s locally-synced onchain WorkflowDON membership and fault tolerance, addressing Sigma Prime CL112-01 (PRIV-458).
Changes:
- Add relay-side EnclaveConfig vs WorkflowDON verification for both SecretsGet and CapabilityExec flows.
- Bump
github.com/smartcontractkit/chainlink-commonto pick up the newEnclaveConfigfield on request params. - Update handler tests/fixtures and add dedicated tests for EnclaveConfig verification (match, mismatches, order-independence, secrets-get coverage).
Targeted areas requiring scrupulous human review:
verifyEnclaveConfigMatchesDON: confirm the chosen onchain reference (localNode.WorkflowDON) is correct for all deployments/rotations and that the comparison logic matches the intended security model (especially around signer identity representation).- Request-path behavior during DON membership rotations / registry staleness: ensure the new rejection behavior is operationally acceptable for the gateway/enclave interaction.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| go.mod | Bumps chainlink-common to a newer pseudo-version to include EnclaveConfig in request params. |
| go.sum | Updates module sums for the chainlink-common version bump. |
| core/capabilities/confidentialrelay/handler.go | Adds EnclaveConfig verification against locally synced WorkflowDON members/F; hooks it into SecretsGet and CapabilityExec after attestation hash verification. |
| core/capabilities/confidentialrelay/handler_test.go | Updates fixtures to include EnclaveConfig and adds tests covering accept/reject cases and order-independence. |
Comments suppressed due to low confidence (1)
core/capabilities/confidentialrelay/handler_test.go:475
- This struct literal has formatting/indentation issues (EnclaveConfig and the closing brace are misaligned). Please run gofmt (or adjust indentation) to keep formatting consistent and avoid gofmt-check failures.
params := confidentialrelaytypes.CapabilityRequestParams{
WorkflowID: "wf-1",
Owner: testOwner,
ExecutionID: "32c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1",
ReferenceID: "17",
CapabilityID: "fail-cap@1.0.0",
Payload: base64.StdEncoding.EncodeToString(mustMarshalProto(t, &sdkpb.CapabilityRequest{Id: "fail-cap@1.0.0", Method: "Execute"})),
EnclaveConfig: testEnclaveConfig(),
}
| // the Nitro attestation binds the request hash, but a malicious host | ||
| // can produce a genuinely-attested request over a forged enclave config | ||
| // unless we compare the config value against an onchain reference. | ||
| if err := h.verifyEnclaveConfigMatchesDON(ctx, params.EnclaveConfig); err != nil { |
| params := confidentialrelaytypes.CapabilityRequestParams{ | ||
| WorkflowID: "wf-1", | ||
| Owner: testOwner, | ||
| ExecutionID: "32c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1", | ||
| ReferenceID: "17", | ||
| CapabilityID: "my-cap@1.0.0", | ||
| Payload: makeCapabilityPayload(t, map[string]any{"key": "val"}), | ||
| EnclaveConfig: testEnclaveConfig(), | ||
| } |
…elay-config-verify # Conflicts: # go.mod # go.sum
…elay-config-verify # Conflicts: # core/capabilities/confidentialrelay/handler_test.go # go.mod # go.sum
GolangCI flagged three issues introduced by this PR's test fixtures: - gosec G115: uint32->uint8 narrowing in WorkflowDON.F assignment. Replaced with an untyped testEnclaveF const used by both EnclaveConfig.F (uint32) and WorkflowDON.F (uint8), so no conversion. - gocritic assignOp x2: badCfg.F = badCfg.F + 5 and params.EnclaveConfig.F = ... + 5 rewritten with +=.
Root go.mod was bumped to chainlink-common b1205469 (post-#2063, adds EnclaveConfig). The deployment, integration-tests, integration-tests/load, system-tests/lib, system-tests/tests, and core/scripts modules replace chainlink => ../ and so build against the root's confidentialrelay code, but still pinned the pre-#2063 common (883689d). That skew broke "make generate" (deployment error: exit status 1) because EnclaveConfig was undefined when building root packages from those modules. Bump all six to b1205469 and tidy.
CORA - Analysis SkippedReason: The number of code owners (1) is less than the minimum required (5) and/or the number of CODEOWNERS entries with changed files (2) is less than the minimum required (2). |
The chainlink-common bump in this PR makes confidentialrelay.Validate require a non-empty EnclaveConfig (Signers, F, MasterPublicKey). The gateway aggregator test fixtures (validCapParams/validSecretsParams) didn't set it, so Hash() errored, no signed responses formed, and TestConfidentialRelayHandler_QuorumWithRealAggregator blocked on quorum until the 15m test timeout. Add a valid EnclaveConfig to both fixtures. Gateway production code is unaffected (nodes and gateway share one build, so hashing stays consistent); only the stale test fixtures needed the field.
- verifyEnclaveConfigMatchesDON now takes a localNode snapshot instead of fetching it itself. handleSecretsGet fetched LocalNode twice (once for the config check, once for request metadata); now it fetches once and passes the snapshot, avoiding the redundant lookup and any inconsistency if registry state changes between calls. handleCapabilityExecute fetches once and passes it too. - gofmt the handler test: the EnclaveConfig struct literals added earlier were mis-indented. - Drop the audit tag from the doc comment, matching the chainlink-common cleanup (keep PRIV-458).
The LocalNode-once refactor introduced `localNode, err :=` then kept `if err := verifyEnclaveConfigMatchesDON(...)`, shadowing err (govet). Use `=` so it assigns the existing err.
chainlink-common #2111 made EnclaveConfig optional (*EnclaveConfig). Bump common to 208ae6dd across the root and the dependent submodules that replace chainlink => ../, and adapt the relay handler: - verifyEnclaveConfigMatchesDON takes *EnclaveConfig and skips the check when nil (sender on an older protocol that omits it), verifying only when present. - Node-handler and gateway-aggregator test fixtures use the pointer form; add a node-handler subtest covering nil-config acceptance.
…elay-config-verify
…elay-config-verify # Conflicts: # core/scripts/go.mod # core/scripts/go.sum # deployment/go.mod # deployment/go.sum # go.mod # go.sum # integration-tests/go.mod # integration-tests/go.sum # integration-tests/load/go.mod # integration-tests/load/go.sum # system-tests/lib/go.mod # system-tests/lib/go.sum # system-tests/tests/go.mod # system-tests/tests/go.sum
|
…ntractkit#22762) * Add privacy as CODEOWNER for core/capabilities/confidentialrelay confidentialrelay holds the relay-DON authorization logic (PRIV-433, PRIV-458), which is privacy-owned security code. It currently inherits /core/capabilities/ (keystone + capabilities-team) with no privacy entry. Add privacy alongside the existing owners so privacy can review and approve changes there, e.g. smartcontractkit#22516 and smartcontractkit#22703. * Make confidentialrelay CODEOWNERS privacy-only (capabilities + gateway) Both confidentialrelay paths are purely privacy's confidential-relay code: the capability handler (core/capabilities/confidentialrelay) and the gateway handler (core/services/gateway/handlers/confidentialrelay). Assign both to @smartcontractkit/privacy outright, mirroring confidentialhttp in chainlink-common. * Add system-tests confidentialrelay CODEOWNERS for privacy
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.
#22516 (verifyEnclaveConfigMatchesDON) landed on develop and overlaps the PRIV-433 verifier in handler_test.go. Unify the fixtures so the Workflow DON members are real ed25519 public keys that double as EnclaveConfig.Signers: this satisfies both verifyEnclaveConfigMatchesDON (byte-compares Signers to Members) and verifyWorkflowAuthorization (verifies F+1 signatures against Members). handleSecretsGet fetches LocalNode once for both checks. Sign 2*F+1 (=3) compute requests to meet quorum.




Verifies the enclave's reported EnclaveConfig against onchain DON state on
the relay-DON path. Relay-DON portion of PRIV-458.
After Nitro attestation validation, the confidentialrelay node handler
compares the attested EnclaveConfig (Signers and F) in each
SecretsRequestParams and CapabilityRequestParams against the local node's
WorkflowDON membership and fault tolerance. The attestation binds the
request hash but does not on its own prove the config matches the DON, so
without this check a malicious host could produce a genuinely-attested
request over a forged enclave config.
The relay DON runs on the same nodes as the workflow DON, so
localNode.WorkflowDON.Members is the comparison target. LocalNode is an
O(1) in-memory lookup populated by the registry syncer on a ~12s tick, so
the check stays off the RPC hot path; up to ~12s staleness during DON
membership rotations is acceptable.
Bumps chainlink-common to v0.11.2-0.20260528150532-b12054695b25 for the
EnclaveConfig field on confidentialrelay request params
(chainlink-common#2063, merged).
This is additive at compile time (a new struct field plus a runtime
Validate requirement), so it does not break chainlink compilation. It is
a breaking wire change at runtime: the matching enclave side (sends and
hash-binds EnclaveConfig) must deploy alongside relays. That is acceptable
because confidential workflows is not released yet. The enclave side
(confidential-compute#330) is reverted for now (confidential-compute#351)
and re-merges once this lands.
Tests: match-accepts, F mismatch, signers-count mismatch, signer-value
mismatch, order-independent comparison, and the secrets-get path. Node
handler and gateway aggregator test fixtures set EnclaveConfig now that
confidentialrelay.Validate requires it.
Jira: https://smartcontract-it.atlassian.net/browse/PRIV-458