fix(security): reject unsigned attestations in /attest/submit (fixes #8178) [fj4WqyCCw3C5ShR1RfB7MoBPTpkRrBFYP1uT35g3MvT] - #8193
Conversation
…cottcjn#8178) The /attest/submit endpoint accepted attestations with empty signature and public_key fields, skipping all signature verification entirely. This let an attacker with valid hardware enroll arbitrary wallets in mining without proving ownership of the wallet's private key. Fix: add elif/else branches after the existing signature verification block: reject incomplete signatures (only one of sig/pubkey) with 400, and reject fully unsigned attestations with 401 and a clear error code (SIGNED_ATTESTATION_REQUIRED), matching the /epoch/enroll pattern. Added PoC test unsigned_attestation_poc.py that asserts the fix. Closes Scottcjn#8178
FlintLeng
left a comment
There was a problem hiding this comment.
PR Review: Reject Unsigned Attestations in /attest/submit
Reviewed on: 2026-08-07
Summary
Critical security fix for CVE-class issue #8178. The /attest/submit endpoint had a backward-compatibility path that accepted empty signature and public_key fields, skipping Ed25519 verification entirely and auto-enrolling the wallet at full reward weight — without proving ownership of the wallet's private key.
Security Analysis ✅
The vulnerability is real and critical. An attacker who could reach the node's HTTP endpoint could submit an unsigned attestation for any wallet address and enroll it at the maximum reward tier. This is equivalent to a signature-bypass on enrollment — the entire reward weight system was undermined.
The three-branch rejection is correctly layered:
- Missing both fields → HTTP 401
SIGNED_ATTESTATION_REQUIRED(not merely 400 — this is a security boundary, not a bad request) - Partial (only sig XOR pubkey) → HTTP 400
INCOMPLETE_SIGNATURE(malformed, not a security issue) - Proper Ed25519 signature verification unchanged
The HTTP status code choice is intentional and correct. 401 (Unauthorized) for fully unsigned attestations signals an authentication failure, not a validation error. 400 (Bad Request) for partial signatures correctly categorises it as a client-side malformed request. The distinction matters for security monitoring and rate-limiting.
The PoC test (unsigned_attestation_poc.py) validates the fix correctly. It checks source code presence of SIGNED_ATTESTATION_REQUIRED and elif sig_hex or pubkey_hex — appropriate for a regression test that doesn't require a live node. ENROLL_ALLOW_UNSIGNED_LEGACY = '0' in the test environment is the right toggle to force the new behaviour.
ENROLL_ALLOW_UNSIGNED_LEGACY = '0' as an environment variable suggests there's a way to enable the old (broken) behaviour. Worth confirming this flag is never set in production and that the default is 0 (rejecting). If it defaults to '1', this is a deployment risk.
Minor Note
The error message Re-attest with a signing key uses "signing key" — this is slightly imprecise. The fix is about proving ownership of the wallet's Ed25519 key pair, not a separate signing key. Consider Re-attest with your wallet's private key for clarity.
Wallet: RTC019e78d600fb3131c29d7ba80aba8fe644be426e
✅ LGTM — critical security fix, correctly implemented with appropriate PoC coverage.
|
Holding this, and I want to be clear that the vulnerability is real. Accepting an attestation with no signature and no public key means no proof that the submitter controls the wallet, which is a Sybil vector rather than a theoretical one. It should be closed. It cannot be closed as a hard cutover, though. As written this returns 401 for every attestation without What I checked:
So merging this stops attestation for the entire fleet at once, and the vintage hardware is exactly the part that is hardest to upgrade. That is a chain halt for the miners rather than a hardening. A migration that gets you the same end state:
Step 1 alone is a genuinely useful PR and I would take it now. Two smaller notes:
The finding is good work. It is the rollout that needs to change, not the analysis. |
Issue
Closes #8178
Summary
The
/attest/submitendpoint accepted attestations with emptysignatureandpublic_keyfields, skipping all signature verification and auto-enrolling the wallet with full reward weight. This let an attacker with valid hardware enroll arbitrary wallets without proving ownership.Fix
Added
elif/elsebranches after the existing signature-verification block:elif sig_hex or pubkey_hex:→ reject withINCOMPLETE_SIGNATURE(400) when only one of signature/public_key is present.else:→ reject fully unsigned attestations withSIGNED_ATTESTATION_REQUIRED(401), matching the/epoch/enrollenforcement pattern.Acceptance criteria
SIGNED_ATTESTATION_REQUIREDINCOMPLETE_SIGNATUREunsigned_attestation_poc.pyverifying the fixWallet: fj4WqyCCw3C5ShR1RfB7MoBPTpkRrBFYP1uT35g3MvT