Conversation
📝 WalkthroughWalkthroughA cloud KMS signer backend implementation enabling operators to manage Ed25519 signing keys in AWS KMS and GCP Cloud KMS instead of on-disk encrypted files. The Changes
Sequence Diagram(s)sequenceDiagram
participant App as Application Init
participant Ctx as Context
participant Factory as Factory<br/>(pkg/signer)
participant Config as Config<br/>Validator
participant KMS as KMS Client<br/>(AWS/GCP)
participant Signer as Signer<br/>Implementation
App->>Ctx: cmd.Context()
App->>Factory: NewSignerForInit(ctx, config, passphrase)
Factory->>Config: Validate signer type & KMS provider
Config-->>Factory: OK or error
alt Validation Fails
Factory-->>App: error
else File Signer
Factory->>Signer: CreateFileSystemSigner(...)
Signer-->>Factory: *FileSystemSigner
else AWS KMS
Factory->>KMS: aws.NewKmsSigner(ctx, region, profile, keyID, opts)
KMS->>KMS: Load AWS credentials
KMS->>KMS: Create KMS client
KMS->>KMS: GetPublicKey(ctx)
KMS->>KMS: Cache & parse public key
KMS-->>Factory: *KmsSigner
else GCP KMS
Factory->>KMS: gcp.NewKmsSigner(ctx, keyName, opts)
KMS->>KMS: Load GCP credentials
KMS->>KMS: Create KMS client
KMS->>KMS: GetPublicKey(ctx)
KMS->>KMS: Parse & cache public key
KMS-->>Factory: *KmsSigner
end
Factory->>Signer: GetPublic() & GetAddress()
Signer-->>Factory: pubKey, address
Factory-->>App: Signer, nil
sequenceDiagram
participant Executor as Block Executor
participant Signer as Signer<br/>(File/AWS/GCP)
participant KMS as KMS Service<br/>(if AWS/GCP)
participant Cache as Public Key<br/>Cache
Executor->>Executor: ProduceBlock(ctx)
Executor->>Executor: signHeader(ctx, header)
Executor->>Signer: Sign(ctx, headerBytes)
alt File Signer
Signer->>Signer: ed25519.Sign(privKey, msg)
Signer-->>Executor: signature
else AWS/GCP KMS
Signer->>Signer: ctx.Err() check
alt Context Canceled
Signer-->>Executor: context error
else Normal Flow
Signer->>KMS: Sign(ctx, message)
KMS->>KMS: Apply per-call timeout
alt Retryable Error
KMS->>KMS: Exponential backoff
KMS->>KMS: Retry (up to MaxRetries+1)
else Non-Retryable Error
KMS-->>Signer: error
end
KMS-->>Signer: signature
Signer->>Cache: Return cached pubKey
Signer-->>Executor: signature
end
end
Executor->>Executor: Continue block production with signature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).
|
|
Claude finished @alpe's task in 2m 58s —— View job Code Review: PR #3171 — AWS KMS and GCP KMS Signer Backends
SummaryThis PR adds AWS KMS and GCP KMS remote signer backends for the sequencer, plus a factory that selects a signer implementation at startup. The architecture is sound — a clean Critical
This is a real bug. The function constructs // BUG: signs signedHeader.Header, not newSignedHeader.Header
signature, err := GetSignature(ctx, signedHeader.Header, signer)Should be: signature, err := GetSignature(ctx, newSignedHeader.Header, signer)This function is used in tests and test helpers throughout the codebase. Any test that verifies the chain of signed headers calling this function will silently carry an invalid signature. While the immediate impact may be confined to tests, fixing it is required because it breaks the invariant that a Major
The struct comment says // aws/signer.go:87 — should be 10s to match doc and GCP
o := Options{Timeout: 1 * time.Second, MaxRetries: 3}The README example also shows
Both // Correct fix: return a copy
addr := make([]byte, len(s.address))
copy(addr, s.address)
return addr, nil
When if config.Signer.SignerPath == "" {
return nil, fmt.Errorf("signer_path must be set when using file signer")
}
// Wrong:
option.WithAuthCredentialsFile(option.ServiceAccount, opts.CredentialsFile)
// Correct:
option.WithCredentialsFile(opts.CredentialsFile)
The Minor
When
The AWS signer computes
The thin
Treating every
Nit
Testing Assessment
Checklist for Merge
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3171 +/- ##
==========================================
+ Coverage 61.15% 61.48% +0.32%
==========================================
Files 117 120 +3
Lines 12082 12421 +339
==========================================
+ Hits 7389 7637 +248
- Misses 3867 3933 +66
- Partials 826 851 +25
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
types/utils_test.go (1)
82-82: Consider usingt.Context()for consistency.While
context.Background()works correctly here, other test files in this PR uset.Context()which is more idiomatic for tests. It automatically cancels when the test completes, providing better cleanup semantics.🔧 Suggested change
- firstSignedHeader, err := types.GetFirstSignedHeader(context.Background(), noopSigner, tc.chainID) + firstSignedHeader, err := types.GetFirstSignedHeader(t.Context(), noopSigner, tc.chainID)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/utils_test.go` at line 82, Replace the call to context.Background() with the test's cancellable context by using t.Context() when invoking types.GetFirstSignedHeader; update the specific invocation firstSignedHeader, err := types.GetFirstSignedHeader(context.Background(), noopSigner, tc.chainID) to pass t.Context() instead so the test uses the per-test context and cancels automatically when the test finishes.pkg/cmd/init_test.go (1)
49-50: Line 49 now overstates the scenario.This subtest is specifically exercising an unknown signer type (
remote), not non-file signers in general. Renaming the case description will keep the test intent aligned with what it actually covers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/cmd/init_test.go` around lines 49 - 50, Update the test case description to accurately reflect that this subtest exercises an unknown "remote" signer type: change the comment and/or header text that currently reads "Case 3: Non-File signer, Aggregator -> Error (unknown signer type)" to something like "Case 3: Remote signer, Aggregator -> Error (unknown signer type)" so the t.Run("RemoteSigner_Aggregator", ...) intent matches the comment.pkg/cmd/run_node.go (1)
140-142: Consider enhancing AWS KMS log with key ID for observability.Including the key ID (or a truncated version) in the log message would help operators verify which KMS key is in use during startup.
🔧 Optional enhancement
if nodeConfig.Signer.SignerType == "awskms" { - logger.Info().Msg("initialized AWS KMS signer via factory") + logger.Info(). + Str("kms_key_id", nodeConfig.Signer.KmsKeyID). + Msg("initialized AWS KMS signer via factory") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/cmd/run_node.go` around lines 140 - 142, The AWS KMS startup log only notes the signer type; update the block that checks nodeConfig.Signer.SignerType == "awskms" to also read the KMS key identifier from the signer config (e.g., nodeConfig.Signer.KeyID or nodeConfig.Signer.AwsKmsKeyID), truncate it for safety (for example to the first 8 characters) and include that truncated key id in the logger.Info() message so operators can see which KMS key was used while avoiding full secret exposure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pkg/config/config.go`:
- Around line 305-306: The SignerType struct tag comment currently lists "(file,
awskms)" but the CLI flag description includes "(file, grpc, awskms)"; make them
consistent by either adding "grpc" to the SignerType field comment (SignerType
string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote
signer to use (file, grpc, awskms)"`) or by removing "grpc" from the CLI flag
help text if grpc is not supported—update the comment associated with SignerType
(and SignerPath documentation if needed) or the flag description in the CLI
registration so both list the same valid signer types.
In `@pkg/signer/aws/README.md`:
- Around line 3-15: Update the README for the awskms signer to add explicit IAM
permissions and KMS key configuration required: state that the IAM principal
needs kms:GetPublicKey and kms:Sign on the configured key, and that the KMS key
must be created with KeyUsage=SIGN_VERIFY and KeySpec=ECC_NIST_EDWARDS25519;
mention that the implementation of signer.Signer calls GetPublicKey eagerly
during initialization (and fails fast on misconfiguration) and uses Sign for
signing operations so those permissions/settings are mandatory for successful
initialization and runtime signing.
In `@pkg/signer/aws/signer.go`:
- Around line 31-37: The documented default Timeout in the Options struct (10s)
is inconsistent with the fallback used in kmsSignerFromClient (1s); update the
implementation to use a single default (e.g., defaultTimeout = 10 * time.Second)
and ensure kmsSignerFromClient and any other fallback logic use that constant
when opts is nil or opts.Timeout <= 0; modify the fallback for MaxRetries
similarly if needed and verify all uses (notably kmsSignerFromClient and the
related sign/constructor paths) reference the same default constants instead of
hardcoded values.
- Around line 207-216: GetAddress currently returns the backing slice s.address
from KmsSigner allowing callers to mutate cached state after the RLock is
released; while holding the read lock (in KmsSigner.GetAddress) allocate a new
byte slice, copy the contents of s.address into it, and return that copy instead
of the original slice (keep the existing nil check and error path, but ensure
the copy is created before releasing s.mu.RUnlock so callers cannot corrupt the
signer's cached identity).
In `@pkg/signer/factory.go`:
- Around line 33-35: Reject empty signer_path in the signer factory before
calling filepath.Abs: retrieve the raw value used (config.Signer.SignerPath or
the trimmed value from strings.TrimSuffix(config.Signer.SignerPath,
"signer.json")), check if it is empty (after trimming) and return a clear error
instead of calling filepath.Abs; only call filepath.Abs when the trimmed signer
path is non-empty and then continue with existing resolution logic that assigns
to signerPath. Ensure the validation happens in the same initialization area
where signerPath is computed (the code using filepath.Abs and signerPath) so
misconfiguration cannot fall back to the process CWD.
In `@types/utils.go`:
- Around line 192-198: GetRandomNextSignedHeader is building
newSignedHeader.Header via GetRandomNextHeader but then calls GetSignature with
the old signedHeader.Header, producing a signature that won't verify; change the
call to GetSignature(ctx, newSignedHeader.Header, signer), assign the returned
signature to newSignedHeader.Signature (or the appropriate field on
SignedHeader), and preserve the existing error check/return so the function
returns the newly built SignedHeader with a signature over its own Header.
---
Nitpick comments:
In `@pkg/cmd/init_test.go`:
- Around line 49-50: Update the test case description to accurately reflect that
this subtest exercises an unknown "remote" signer type: change the comment
and/or header text that currently reads "Case 3: Non-File signer, Aggregator ->
Error (unknown signer type)" to something like "Case 3: Remote signer,
Aggregator -> Error (unknown signer type)" so the
t.Run("RemoteSigner_Aggregator", ...) intent matches the comment.
In `@pkg/cmd/run_node.go`:
- Around line 140-142: The AWS KMS startup log only notes the signer type;
update the block that checks nodeConfig.Signer.SignerType == "awskms" to also
read the KMS key identifier from the signer config (e.g.,
nodeConfig.Signer.KeyID or nodeConfig.Signer.AwsKmsKeyID), truncate it for
safety (for example to the first 8 characters) and include that truncated key id
in the logger.Info() message so operators can see which KMS key was used while
avoiding full secret exposure.
In `@types/utils_test.go`:
- Line 82: Replace the call to context.Background() with the test's cancellable
context by using t.Context() when invoking types.GetFirstSignedHeader; update
the specific invocation firstSignedHeader, err :=
types.GetFirstSignedHeader(context.Background(), noopSigner, tc.chainID) to pass
t.Context() instead so the test uses the per-test context and cancels
automatically when the test finishes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a074a34e-fd54-4bb5-acf1-846e6f9e5871
⛔ Files ignored due to path filters (6)
apps/evm/go.sumis excluded by!**/*.sumapps/grpc/go.sumis excluded by!**/*.sumapps/testapp/go.sumis excluded by!**/*.sumexecution/evm/test/go.sumis excluded by!**/*.sumgo.sumis excluded by!**/*.sumtest/e2e/go.sumis excluded by!**/*.sum
📒 Files selected for processing (45)
CHANGELOG.mdapps/evm/cmd/init.goapps/evm/go.modapps/grpc/cmd/init.goapps/grpc/go.modapps/testapp/cmd/init.goapps/testapp/go.modblock/internal/executing/executor.goblock/internal/submitting/da_submitter.goblock/internal/submitting/da_submitter_integration_test.goblock/internal/submitting/da_submitter_test.goblock/internal/submitting/submitter_test.goblock/internal/syncing/da_retriever_strict_test.goblock/internal/syncing/da_retriever_test.goblock/internal/syncing/p2p_handler_test.goblock/internal/syncing/syncer_test.godocs/.vitepress/config.tsdocs/guides/operations/aws-kms-signer.mdgo.modpkg/cmd/init.gopkg/cmd/init_test.gopkg/cmd/run_node.gopkg/config/config.gopkg/config/config_test.gopkg/config/defaults.gopkg/signer/aws/README.mdpkg/signer/aws/signer.gopkg/signer/aws/signer_test.gopkg/signer/factory.gopkg/signer/factory_test.gopkg/signer/file/README.mdpkg/signer/file/doc.gopkg/signer/file/example_test.gopkg/signer/file/file_signer_test.gopkg/signer/file/local.gopkg/signer/noop/signer.gopkg/signer/noop/signer_test.gopkg/signer/signer.gopkg/store/store_adapter_test.gopkg/sync/sync_service_test.gotest/e2e/evm_aws_kms_e2e_test.gotest/e2e/go.modtypes/signed_header_test.gotypes/utils.gotypes/utils_test.go
| This package implements `signer.Signer` using AWS KMS. | ||
|
|
||
| It uses KMS for `Sign` operations and caches the public key/address in memory after initialization. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - AWS credentials must be available via the standard AWS SDK credential chain. | ||
| - The configured KMS key must be an asymmetric **Ed25519** key. | ||
|
|
||
| ## Configuration | ||
|
|
||
| Set `evnode.signer.signer_type` to `awskms` and provide at least `kms_key_id`. | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd pkg/signer/aws && find . -type f -name "*.md" -o -name "*.go" | head -20Repository: evstack/ev-node
Length of output: 99
🏁 Script executed:
cat -n pkg/signer/aws/README.mdRepository: evstack/ev-node
Length of output: 1086
🏁 Script executed:
cd pkg/signer/aws && ls -laRepository: evstack/ev-node
Length of output: 352
🏁 Script executed:
cd pkg/signer/aws && wc -l *.go | tail -1Repository: evstack/ev-node
Length of output: 70
🏁 Script executed:
cat -n pkg/signer/aws/signer.goRepository: evstack/ev-node
Length of output: 8302
🏁 Script executed:
cat -n pkg/signer/aws/signer_test.go | head -100Repository: evstack/ev-node
Length of output: 3771
🌐 Web query:
AWS KMS Ed25519 key creation KeyUsage SIGN_VERIFY KeySpec ECC_NIST_EDWARDS25519
💡 Result:
Yes, AWS KMS supports creating an asymmetric key with KeySpec 'ECC_NIST_EDWARDS25519' (Ed25519 on Edwards25519 curve) and KeyUsage 'SIGN_VERIFY'. This feature was announced in November 2025 and is available as of 2026-03-19. To create such a key, use the CreateKey API or AWS CLI command: aws kms create-key \ --key-spec ECC_NIST_EDWARDS25519 \ --key-usage SIGN_VERIFY This creates a customer-managed KMS key for signing and verification using EdDSA (Ed25519). The key supports signing algorithms like ED25519_SHA_512 (requires MessageType: RAW) and ED25519_PH_SHA_512 (requires MessageType: DIGEST). It cannot be used for encryption/decryption or key agreement. KeySpec 'ECC_NIST_EDWARDS25519' is listed in official AWS KMS documentation for asymmetric NIST-standard elliptic curve keys, valid only for SIGN_VERIFY usage.
Citations:
- 1: https://aws.amazon.com/about-aws/whats-new/2025/11/aws-kms-edwards-curve-digital-signature-algorithm/#:~:text=AWS%20Key%20Management%20Service%20(KMS,the%20Edwards25519%20curve%20(Ed25519).
- 2: https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html
- 3: https://docs.aws.amazon.com/kms/latest/developerguide/symm-asymm-choose-key-spec.html
- 4: https://docs.aws.amazon.com/kms/latest/APIReference/API_KeyMetadata.html
- 5: https://sdk.amazonaws.com/kotlin/api/latest/kms/aws.sdk.kotlin.services.kms.model/-create-key-request/-builder/key-spec.html
- 6: https://docs.aws.amazon.com/cli/latest/reference/kms/create-key.html
- 7: https://docs.aws.amazon.com/kms/latest/developerguide/example_kms_CreateKey_section.html
Add KMS permission and key configuration requirements to the README.
The README should explicitly document the IAM permissions and KMS key settings required for this signer to function. Without these details, users following the setup instructions will encounter failures during signer initialization.
Add to the Requirements section:
- IAM permissions:
kms:GetPublicKeyandkms:Signon the specified KMS key - KMS key configuration: created with
KeyUsage=SIGN_VERIFYandKeySpec=ECC_NIST_EDWARDS25519
The code calls GetPublicKey eagerly during initialization (failing fast on misconfiguration) and uses Sign for all signing operations, both of which require these permissions and key settings to succeed. Users without these configured will see initialization errors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/signer/aws/README.md` around lines 3 - 15, Update the README for the
awskms signer to add explicit IAM permissions and KMS key configuration
required: state that the IAM principal needs kms:GetPublicKey and kms:Sign on
the configured key, and that the KMS key must be created with
KeyUsage=SIGN_VERIFY and KeySpec=ECC_NIST_EDWARDS25519; mention that the
implementation of signer.Signer calls GetPublicKey eagerly during initialization
(and fails fast on misconfiguration) and uses Sign for signing operations so
those permissions/settings are mandatory for successful initialization and
runtime signing.
| // Options configures optional KmsSigner behaviour. | ||
| type Options struct { | ||
| // Timeout for individual KMS Sign API calls. Default: 10s. | ||
| Timeout time.Duration | ||
| // MaxRetries for transient KMS failures during Sign. Default: 3. | ||
| MaxRetries int | ||
| } |
There was a problem hiding this comment.
Align the implementation with the documented default timeout.
Options says the default sign timeout is 10s, but kmsSignerFromClient falls back to 1s when opts is nil or Timeout <= 0. That makes the exported constructor much more aggressive than advertised.
Proposed fix
- o := Options{Timeout: 1 * time.Second, MaxRetries: 3}
+ o := Options{Timeout: 10 * time.Second, MaxRetries: 3}Also applies to: 87-95
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/signer/aws/signer.go` around lines 31 - 37, The documented default
Timeout in the Options struct (10s) is inconsistent with the fallback used in
kmsSignerFromClient (1s); update the implementation to use a single default
(e.g., defaultTimeout = 10 * time.Second) and ensure kmsSignerFromClient and any
other fallback logic use that constant when opts is nil or opts.Timeout <= 0;
modify the fallback for MaxRetries similarly if needed and verify all uses
(notably kmsSignerFromClient and the related sign/constructor paths) reference
the same default constants instead of hardcoded values.
| // GetAddress returns the cached address derived from the public key. | ||
| func (s *KmsSigner) GetAddress() ([]byte, error) { | ||
| s.mu.RLock() | ||
| defer s.mu.RUnlock() | ||
|
|
||
| if s.address == nil { | ||
| return nil, fmt.Errorf("address not loaded") | ||
| } | ||
|
|
||
| return s.address, nil |
There was a problem hiding this comment.
Return a copy of the cached address.
GetAddress currently exposes the backing slice stored on the signer. Any caller can mutate that slice after the lock is released and corrupt the signer's cached identity.
Proposed fix
func (s *KmsSigner) GetAddress() ([]byte, error) {
s.mu.RLock()
- defer s.mu.RUnlock()
-
if s.address == nil {
+ s.mu.RUnlock()
return nil, fmt.Errorf("address not loaded")
}
- return s.address, nil
+ address := append([]byte(nil), s.address...)
+ s.mu.RUnlock()
+ return address, nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // GetAddress returns the cached address derived from the public key. | |
| func (s *KmsSigner) GetAddress() ([]byte, error) { | |
| s.mu.RLock() | |
| defer s.mu.RUnlock() | |
| if s.address == nil { | |
| return nil, fmt.Errorf("address not loaded") | |
| } | |
| return s.address, nil | |
| // GetAddress returns the cached address derived from the public key. | |
| func (s *KmsSigner) GetAddress() ([]byte, error) { | |
| s.mu.RLock() | |
| if s.address == nil { | |
| s.mu.RUnlock() | |
| return nil, fmt.Errorf("address not loaded") | |
| } | |
| address := append([]byte(nil), s.address...) | |
| s.mu.RUnlock() | |
| return address, nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/signer/aws/signer.go` around lines 207 - 216, GetAddress currently
returns the backing slice s.address from KmsSigner allowing callers to mutate
cached state after the RLock is released; while holding the read lock (in
KmsSigner.GetAddress) allocate a new byte slice, copy the contents of s.address
into it, and return that copy instead of the original slice (keep the existing
nil check and error path, but ensure the copy is created before releasing
s.mu.RUnlock so callers cannot corrupt the signer's cached identity).
| // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails | ||
| signerPath, err := filepath.Abs(strings.TrimSuffix(config.Signer.SignerPath, "signer.json")) | ||
| if err != nil { |
There was a problem hiding this comment.
Reject an empty signer_path before calling filepath.Abs.
filepath.Abs("") resolves to the process working directory, so a misconfigured file signer can silently create/load ./signer.json from wherever the node was started. That's a risky fallback for key material.
Proposed fix
- signerPath, err := filepath.Abs(strings.TrimSuffix(config.Signer.SignerPath, "signer.json"))
+ signerPath := strings.TrimSpace(config.Signer.SignerPath)
+ if signerPath == "" {
+ return nil, fmt.Errorf("signer_path is required when using local file signer")
+ }
+ signerPath = strings.TrimSuffix(signerPath, "signer.json")
+ if signerPath == "" {
+ signerPath = "."
+ }
+ signerPath, err := filepath.Abs(filepath.Clean(signerPath))
if err != nil {
return nil, err
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails | |
| signerPath, err := filepath.Abs(strings.TrimSuffix(config.Signer.SignerPath, "signer.json")) | |
| if err != nil { | |
| // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails | |
| signerPath := strings.TrimSpace(config.Signer.SignerPath) | |
| if signerPath == "" { | |
| return nil, fmt.Errorf("signer_path is required when using local file signer") | |
| } | |
| signerPath = strings.TrimSuffix(signerPath, "signer.json") | |
| if signerPath == "" { | |
| signerPath = "." | |
| } | |
| signerPath, err := filepath.Abs(filepath.Clean(signerPath)) | |
| if err != nil { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/signer/factory.go` around lines 33 - 35, Reject empty signer_path in the
signer factory before calling filepath.Abs: retrieve the raw value used
(config.Signer.SignerPath or the trimmed value from
strings.TrimSuffix(config.Signer.SignerPath, "signer.json")), check if it is
empty (after trimming) and return a clear error instead of calling filepath.Abs;
only call filepath.Abs when the trimmed signer path is non-empty and then
continue with existing resolution logic that assigns to signerPath. Ensure the
validation happens in the same initialization area where signerPath is computed
(the code using filepath.Abs and signerPath) so misconfiguration cannot fall
back to the process CWD.
| func GetRandomNextSignedHeader(ctx context.Context, signedHeader *SignedHeader, signer signer.Signer, chainID string) (*SignedHeader, error) { | ||
| newSignedHeader := &SignedHeader{ | ||
| Header: GetRandomNextHeader(signedHeader.Header, chainID), | ||
| Signer: signedHeader.Signer, | ||
| } | ||
|
|
||
| signature, err := GetSignature(signedHeader.Header, signer) | ||
| signature, err := GetSignature(ctx, signedHeader.Header, signer) |
There was a problem hiding this comment.
Sign the newly built header here.
GetRandomNextSignedHeader creates newSignedHeader.Header but still signs signedHeader.Header. The returned SignedHeader therefore carries a signature for different bytes and won't verify.
Proposed fix
- signature, err := GetSignature(ctx, signedHeader.Header, signer)
+ signature, err := GetSignature(ctx, newSignedHeader.Header, signer)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func GetRandomNextSignedHeader(ctx context.Context, signedHeader *SignedHeader, signer signer.Signer, chainID string) (*SignedHeader, error) { | |
| newSignedHeader := &SignedHeader{ | |
| Header: GetRandomNextHeader(signedHeader.Header, chainID), | |
| Signer: signedHeader.Signer, | |
| } | |
| signature, err := GetSignature(signedHeader.Header, signer) | |
| signature, err := GetSignature(ctx, signedHeader.Header, signer) | |
| func GetRandomNextSignedHeader(ctx context.Context, signedHeader *SignedHeader, signer signer.Signer, chainID string) (*SignedHeader, error) { | |
| newSignedHeader := &SignedHeader{ | |
| Header: GetRandomNextHeader(signedHeader.Header, chainID), | |
| Signer: signedHeader.Signer, | |
| } | |
| signature, err := GetSignature(ctx, newSignedHeader.Header, signer) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@types/utils.go` around lines 192 - 198, GetRandomNextSignedHeader is building
newSignedHeader.Header via GetRandomNextHeader but then calls GetSignature with
the old signedHeader.Header, producing a signature that won't verify; change the
call to GetSignature(ctx, newSignedHeader.Header, signer), assign the returned
signature to newSignedHeader.Signature (or the appropriate field on
SignedHeader), and preserve the existing error check/return so the function
returns the newly built SignedHeader with a signature over its own Header.
* Add remote signer GCO KMS * Review feedback * Minor updates
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
docs/guides/operations/aws-kms-signer.md (1)
12-33: Add an explicit least-privilege warning for the example IAM policy.The example policy is broad (
Resource: "*") and includes key-management actions; please label it as bootstrap/admin-only and recommend scoped runtime permissions (already partially covered in Line 35–38) to reduce accidental over-permissioning in production.Also applies to: 35-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/guides/operations/aws-kms-signer.md` around lines 12 - 33, Add a clear least-privilege warning above the example IAM policy JSON that currently uses "Resource": "*" and includes key-management actions (e.g., "kms:CreateKey", "kms:PutKeyPolicy", "kms:EnableKey", "kms:Sign"); label this policy as bootstrap/admin-only and explicitly state it must not be used in production, then reference and recommend the scoped runtime permissions described later (the smaller set in the docs) as the safe alternative and suggest replacing wildcard Resource with specific KMS key ARNs for production.test/e2e/evm_kms_e2e_test.go (1)
32-32: Callingflag.Parse()multiple times can cause issues
flag.Parse()is called in bothTestEvmSequencerWithAWSKMSSignerE2E(line 32) andTestEvmSequencerWithGCPKMSSignerE2E(line 72). When both tests run in the same test binary execution, this can cause problems as flags are already parsed. The Go testing framework already handles flag parsing before tests run.♻️ Remove redundant flag.Parse() calls
func TestEvmSequencerWithAWSKMSSignerE2E(t *testing.T) { if testing.Short() { t.Skip("skip e2e in short mode") } - flag.Parse() kmsKeyID := os.Getenv("EVNODE_E2E_AWS_KMS_KEY_ID")func TestEvmSequencerWithGCPKMSSignerE2E(t *testing.T) { if testing.Short() { t.Skip("skip e2e in short mode") } - flag.Parse() kmsKeyName := os.Getenv("EVNODE_E2E_GCP_KMS_KEY_NAME")Also applies to: 72-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/evm_kms_e2e_test.go` at line 32, The tests call flag.Parse() redundantly in TestEvmSequencerWithAWSKMSSignerE2E and TestEvmSequencerWithGCPKMSSignerE2E which can break when the test binary parses flags once; remove the flag.Parse() calls from both test functions (search for TestEvmSequencerWithAWSKMSSignerE2E and TestEvmSequencerWithGCPKMSSignerE2E) so the standard Go test harness handles flag parsing, and run go test to confirm no other tests rely on manual flag parsing.pkg/signer/gcp/signer.go (2)
262-272: Inconsistent mutex pattern between GetPublic and GetAddress
GetPublicmanually unlocks before the nil check whileGetAddressusesdefer s.mu.RUnlock(). For consistency and safety, consider usingdeferin both methods.♻️ Use defer for consistency
func (s *KmsSigner) GetPublic() (crypto.PubKey, error) { s.mu.RLock() - pubKey := s.pubKey - s.mu.RUnlock() + defer s.mu.RUnlock() + pubKey := s.pubKey if pubKey == nil { return nil, fmt.Errorf("public key not loaded") } return pubKey, nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/signer/gcp/signer.go` around lines 262 - 272, GetPublic currently acquires a read lock and manually unlocks before checking pubKey; change it to mirror GetAddress by calling s.mu.RLock() then immediately deferring s.mu.RUnlock(), assign pubKey := s.pubKey, and perform the nil check after the defer so the lock-unlock pattern is consistent and safe (update the GetPublic method accordingly to use defer s.mu.RUnlock()).
194-201: Consider usingdefer cancel()for the per-call timeout contextThe
cancel()function is called immediately after the API call, but if there's a panic between context creation and the cancel call, the context could leak. Usingdeferis the idiomatic pattern.♻️ Use defer for cancel()
callCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() dataCRC32C := int64(crc32.Checksum(message, castagnoliTable)) out, err := s.client.AsymmetricSign(callCtx, &kmspb.AsymmetricSignRequest{ Name: s.keyName, Data: message, DataCrc32C: wrapperspb.Int64(dataCRC32C), }) - cancel()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/signer/gcp/signer.go` around lines 194 - 201, The context cancel function created by callCtx, cancel := context.WithTimeout(ctx, timeout) is invoked immediately with cancel() which can leak the context on panic; after creating callCtx and cancel, replace the immediate cancel() call with defer cancel() so the context is always cancelled when the surrounding function returns (keep the call to s.client.AsymmetricSign using callCtx and the DataCrc32C wrapper as-is).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/evm/go.mod`:
- Line 239: Update the grpc dependency entry to google.golang.org/grpc v1.79.3
in the go.mod (replace the existing google.golang.org/grpc v1.79.2 // indirect
line), then run go mod tidy (or go get google.golang.org/grpc@v1.79.3) to update
the lockfiles and verify module downloads; this addresses the CVE by ensuring
the project uses the patched grpc version.
In `@docs/guides/operations/aws-kms-signer.md`:
- Around line 72-75: Remove the misleading expected startup log "initialized AWS
KMS signer via factory" from the docs and either delete that sentence or replace
it with the actual log emitted by the AWS signer initialization path; update the
guide text that references the signer factory so it reflects the real behavior
of the AWS signer initialization path (see aws signer implementation in
pkg/signer/aws/signer.go and the factory behavior in pkg/signer/factory.go) so
operators are not told to expect a nonexistent log line.
In `@go.mod`:
- Line 48: The go.mod currently pins the vulnerable module
"google.golang.org/grpc v1.79.2"; update that dependency to a patched gRPC
release that fixes GHSA-p77j-4mvh-x3m3 (e.g., upgrade to a patched version >=
v1.79.3 or the latest patched release), run "go get
google.golang.org/grpc@<patched-version>" and "go mod tidy", run tests, and
ensure any other references (such as the duplicate pin in apps/evm/go.mod) are
likewise updated and no replace directives or transitive dependencies re-pin the
vulnerable v1.79.2.
---
Nitpick comments:
In `@docs/guides/operations/aws-kms-signer.md`:
- Around line 12-33: Add a clear least-privilege warning above the example IAM
policy JSON that currently uses "Resource": "*" and includes key-management
actions (e.g., "kms:CreateKey", "kms:PutKeyPolicy", "kms:EnableKey",
"kms:Sign"); label this policy as bootstrap/admin-only and explicitly state it
must not be used in production, then reference and recommend the scoped runtime
permissions described later (the smaller set in the docs) as the safe
alternative and suggest replacing wildcard Resource with specific KMS key ARNs
for production.
In `@pkg/signer/gcp/signer.go`:
- Around line 262-272: GetPublic currently acquires a read lock and manually
unlocks before checking pubKey; change it to mirror GetAddress by calling
s.mu.RLock() then immediately deferring s.mu.RUnlock(), assign pubKey :=
s.pubKey, and perform the nil check after the defer so the lock-unlock pattern
is consistent and safe (update the GetPublic method accordingly to use defer
s.mu.RUnlock()).
- Around line 194-201: The context cancel function created by callCtx, cancel :=
context.WithTimeout(ctx, timeout) is invoked immediately with cancel() which can
leak the context on panic; after creating callCtx and cancel, replace the
immediate cancel() call with defer cancel() so the context is always cancelled
when the surrounding function returns (keep the call to s.client.AsymmetricSign
using callCtx and the DataCrc32C wrapper as-is).
In `@test/e2e/evm_kms_e2e_test.go`:
- Line 32: The tests call flag.Parse() redundantly in
TestEvmSequencerWithAWSKMSSignerE2E and TestEvmSequencerWithGCPKMSSignerE2E
which can break when the test binary parses flags once; remove the flag.Parse()
calls from both test functions (search for TestEvmSequencerWithAWSKMSSignerE2E
and TestEvmSequencerWithGCPKMSSignerE2E) so the standard Go test harness handles
flag parsing, and run go test to confirm no other tests rely on manual flag
parsing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d986867e-d2c9-40c3-968d-ac865e15dbe4
⛔ Files ignored due to path filters (6)
apps/evm/go.sumis excluded by!**/*.sumapps/grpc/go.sumis excluded by!**/*.sumapps/testapp/go.sumis excluded by!**/*.sumexecution/evm/test/go.sumis excluded by!**/*.sumgo.sumis excluded by!**/*.sumtest/e2e/go.sumis excluded by!**/*.sum
📒 Files selected for processing (19)
apps/evm/go.modapps/grpc/go.modapps/testapp/go.moddocs/guides/operations/aws-kms-signer.mddocs/guides/operations/gcp-kms-signer.mdexecution/evm/test/go.modgo.modpkg/cmd/run_node.gopkg/config/config.gopkg/config/config_test.gopkg/config/defaults.gopkg/signer/aws/README.mdpkg/signer/factory.gopkg/signer/factory_test.gopkg/signer/gcp/README.mdpkg/signer/gcp/signer.gopkg/signer/gcp/signer_test.gotest/e2e/evm_kms_e2e_test.gotest/e2e/go.mod
✅ Files skipped from review due to trivial changes (5)
- execution/evm/test/go.mod
- docs/guides/operations/gcp-kms-signer.md
- pkg/signer/gcp/README.md
- pkg/signer/aws/README.md
- pkg/signer/factory_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
- pkg/config/config_test.go
- pkg/config/defaults.go
- apps/grpc/go.mod
- test/e2e/go.mod
- apps/testapp/go.mod
- pkg/signer/factory.go
- pkg/cmd/run_node.go
- pkg/config/config.go
| google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d // indirect | ||
| google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect | ||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect | ||
| google.golang.org/grpc v1.79.2 // indirect |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest version of google.golang.org/grpc and does it fix GHSA-p77j-4mvh-x3m3?
💡 Result:
The latest version of google.golang.org/grpc is v1.79.3, released on 2026-03-17. Yes, it fixes GHSA-p77j-4mvh-x3m3. The release notes explicitly state under Security: "server: fix an authorization bypass where malformed :path headers (missing the leading slash) could bypass path-based restricted 'deny' rules in interceptors like grpc/authz. Any request with a non-canonical path is now immediately rejected with an Unimplemented error. (#8981)". This matches the vulnerability description in advisories (CVE-2026-33186), which involves authorization bypass via missing leading slash in :path.
Citations:
- 1: https://github.com/grpc/grpc-go/releases
- 2: https://pkg.go.dev/google.golang.org/grpc?tab=versions
- 3: https://pkg.go.dev/google.golang.org/grpc
- 4: https://github.com/grpc/grpc-go
- 5: https://github.com/grpc/grpc-go/releases/tag/v1.79.3
Upgrade google.golang.org/grpc to v1.79.3 to fix critical authorization bypass vulnerability
google.golang.org/grpc v1.79.2 contains GHSA-p77j-4mvh-x3m3 (CVE-2026-33186), an authorization bypass where malformed :path headers missing the leading slash can bypass path-based access controls in gRPC interceptors. Since the KMS signer uses gRPC to communicate with cloud KMS services, this vulnerability affects the security of signing operations. Upgrade to v1.79.3, which fixes this issue by rejecting non-canonical paths.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/evm/go.mod` at line 239, Update the grpc dependency entry to
google.golang.org/grpc v1.79.3 in the go.mod (replace the existing
google.golang.org/grpc v1.79.2 // indirect line), then run go mod tidy (or go
get google.golang.org/grpc@v1.79.3) to update the lockfiles and verify module
downloads; this addresses the CVE by ensuring the project uses the patched grpc
version.
| You should see a startup log line: | ||
|
|
||
| `initialized AWS KMS signer via factory` | ||
|
|
There was a problem hiding this comment.
Remove or replace the expected startup log line; it does not exist in current code paths.
Line 72–75 tells operators to expect initialized AWS KMS signer via factory, but the AWS signer initialization path and signer factory path do not emit that log (pkg/signer/aws/signer.go:52-110, pkg/signer/factory.go:50-77). This creates a false negative during bring-up.
Proposed doc fix
-You should see a startup log line:
-
-`initialized AWS KMS signer via factory`
+Confirm startup succeeded by checking that `evnode` starts without signer initialization errors.
+If startup fails, use the exact errors in the troubleshooting section below.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| You should see a startup log line: | |
| `initialized AWS KMS signer via factory` | |
| Confirm startup succeeded by checking that `evnode` starts without signer initialization errors. | |
| If startup fails, use the exact errors in the troubleshooting section below. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/guides/operations/aws-kms-signer.md` around lines 72 - 75, Remove the
misleading expected startup log "initialized AWS KMS signer via factory" from
the docs and either delete that sentence or replace it with the actual log
emitted by the AWS signer initialization path; update the guide text that
references the signer factory so it reflects the real behavior of the AWS signer
initialization path (see aws signer implementation in pkg/signer/aws/signer.go
and the factory behavior in pkg/signer/factory.go) so operators are not told to
expect a nonexistent log line.
| golang.org/x/net v0.52.0 | ||
| golang.org/x/sync v0.20.0 | ||
| google.golang.org/api v0.272.0 | ||
| google.golang.org/grpc v1.79.2 |
There was a problem hiding this comment.
Critical: Same gRPC authorization bypass vulnerability in root module
This is the same GHSA-p77j-4mvh-x3m3 vulnerability flagged in apps/evm/go.mod. The google.golang.org/grpc v1.79.2 dependency is used directly for KMS communication. Upgrading to a patched version should be prioritized before merging this PR.
🧰 Tools
🪛 OSV Scanner (2.3.3)
[CRITICAL] 48-48: google.golang.org/grpc 1.79.2: gRPC-Go has an authorization bypass via missing leading slash in :path
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@go.mod` at line 48, The go.mod currently pins the vulnerable module
"google.golang.org/grpc v1.79.2"; update that dependency to a patched gRPC
release that fixes GHSA-p77j-4mvh-x3m3 (e.g., upgrade to a patched version >=
v1.79.3 or the latest patched release), run "go get
google.golang.org/grpc@<patched-version>" and "go mod tidy", run tests, and
ensure any other references (such as the duplicate pin in apps/evm/go.mod) are
likewise updated and no replace directives or transitive dependencies re-pin the
vulnerable v1.79.2.
Resolves #3163
Overview
Includes AWS and GCP remote signers
Manual test:
See:
Example
Setup AWS permission:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowKeyCreation", "Effect": "Allow", "Action": [ "kms:CreateKey", "kms:TagResource", "kms:EnableKey", "kms:PutKeyPolicy", "kms:GetPublicKey", "kms:Sign", "kms:ListKeys", "kms:ListAliases" ], "Resource": "*" } ] }Create KMS key:
Copy
KeyIdfrom response.Summary by CodeRabbit
New Features
Documentation