Install
$ agentstack add skill-omermaksutii-rugproof-signature-malleability ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Signature malleability detection
When this applies
Trigger on any of:
- Direct
ecrecover(hash, v, r, s)calls (not via OpenZeppelinECDSA.recover) - Manual
(r, s, v)decoding from abytessignature with assembly - Permit / meta-tx / order-book / claim flows that verify a signed digest
- Signatures used as nonces or dedup keys (e.g.
usedSig[sig] = true) - EIP-712 typed-data verification, or its absence where one is needed
Detection patterns
Missing low-s enforcement / malleable sig (HIGH if sig is a key)
address signer = ecrecover(hash, v, r, s); // no s-range check
require(signer == expected);
usedSignature[keccak256(abi.encode(r,s,v))] = true; // ← dedup keyed on sig bytes
Signal: for any valid (r,s,v) the "flipped" signature (r, n - s, v ^ 1) recovers the same signer (the classic Bitcoin/Ethereum transaction-malleability class). If the signature itself is the replay key, an attacker submits the twin and bypasses dedup. Enforce s <= secp256k1n/2 (EIP-2).
Unchecked ecrecover returning address(0) (HIGH)
address signer = ecrecover(hash, v, r, s);
require(signer == owner); // if owner could ever be address(0)... or no check at all
Signal: malformed inputs make ecrecover return address(0). Any code path where the compared-against value can be address(0) (uninitialized mapping slot, default) authenticates an attacker with garbage. Always require(signer != address(0)).
Unconstrained v (MEDIUM)
Accepting arbitrary v (not 27/28) or trusting a caller-supplied v for EIP-2098 compact sigs, mixing the 64-byte and 65-byte formats. EIP-2098 packs yParity into the high bit of s; decoding it as a raw 65-byte sig corrupts recovery.
Cross-domain / cross-chain replay (HIGH)
bytes32 digest = keccak256(abi.encode(ORDER_TYPEHASH, maker, amount, nonce));
Signal: no EIP-712 domain separator binding chainId and verifyingContract. A signature is replayable on another chain (post-fork) or a sibling deployment. Build the digest with _hashTypedDataV4 (\x19\x01 + domainSeparator + structHash) and recompute the domain separator if block.chainid changes.
Signature reuse / no nonce (HIGH)
Same digest accepted twice because there's no per-signer nonce or no marking of consumed digests — meta-tx and claim flows drain repeatedly.
Severity rubric
| Pattern | Severity | Notes | |---|---|---| | Malleable sig used as replay/dedup key | High | Twin sig bypasses guard | | ecrecover==address(0) authenticates attacker | High | Auth bypass | | Missing EIP-712 domain → cross-chain replay | High | Signature reuse across deployments | | No nonce → straight replay | High | Repeated execution | | Unconstrained v / EIP-2098 confusion | Medium | Format-dependent | | Low-s missing but sig never used as a key | Low | Defense-in-depth |
Remediation patterns
- Use OpenZeppelin
ECDSA.recover— it enforcess <= secp256k1n/2, rejects badv, and reverts onaddress(0)(it also handles EIP-2098). - Bind digests with EIP-712 (
EIP712base +_hashTypedDataV4), includingchainIdandverifyingContract; recompute the domain separator on chainid change. - Use per-signer nonces (incrementing) rather than signature-bytes dedup; consume by
(signer, nonce)not(r,s,v). - Explicit
require(signer != address(0))even when using libraries, for any hand-rolled path. - Prefer
SignatureCheckerwhen signers may be ERC-1271 contract wallets.
False-positive notes
- Code already routing through OpenZeppelin
ECDSA/SignatureCheckerwith EIP-712 and nonces is safe — don't re-flag the underlyingecrecover. - A sig verified against a non-zero, always-set signer where the digest is never reused is malleability-tolerant; note, don't escalate.
Related
- [[signature-replay]] — nonce/chainId replay is the sibling concern
- [[erc1271-contract-signatures]] — smart-wallet signers need
isValidSignature - [[permit2-patterns]] — Permit2 / EIP-2612 digests and nonces
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: omermaksutii
- Source: omermaksutii/RugProof
- License: MIT
- Homepage: https://omermaksutii.github.io/RugProof
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.