Blockchain security auditing is a structured examination of a system's code, architecture, cryptographic assumptions, and operational controls against an explicit threat model. It is not a single scanner run or a one-time certificate; it is a repeatable process of scoping, modeling threats, testing, reporting, remediating, and re-verifying. This article gives you a methodology you can apply to smart contracts, protocol clients, and the node/RPC infrastructure your application depends on, including runnable diagnostics and a results table you populate with your own measurements. It separates documented audit practice from the measurements only your team can produce.
What a blockchain security audit actually covers
A blockchain security audit is a systematic review of an on-chain or blockchain-adjacent system against a defined threat model and a set of standards. In practice the scope usually spans four layers: the smart contracts or chaincode, the protocol or client software, the network and node infrastructure, and the operational and key-management controls around deployment. Each layer has different failure modes, so a credible audit states which layers are in scope and which are explicitly excluded.
The most common mistake is treating an audit as a binary badge. A smart contract audit that reviews Solidity logic does not automatically cover your RPC provider's availability, your deployer key handling, or your upgrade governance. When you read an audit report, check the scope section first: it tells you what was tested, what assumptions were made, and what residual risk remains. This page focuses on how to run that process yourself or how to evaluate a third-party audit critically.
- Code layer: contract logic, access control, arithmetic, upgradeability, external calls.
- Protocol layer: consensus assumptions, message handling, peer-to-peer exposure, client configuration.
- Infrastructure layer: node hardening, RPC exposure, key storage, monitoring, and failover.
- Operational layer: deployment procedures, incident response, and change management.
Prerequisites and assumptions before you start
Before any testing, assemble the artifacts an auditor needs: the exact commit hash of the code under review, build and deployment scripts, dependency lockfiles, architecture diagrams, and a written description of intended behavior. If the system is already deployed, record the deployed addresses and the block range you are auditing. Without a pinned commit and a pinned block, findings cannot be reproduced and remediation cannot be verified.
You also need an environment. For contract review, a local fork of the target chain is usually sufficient; for infrastructure review, a staging node that mirrors production configuration is required. Assume you will need read access to historical logs and the ability to replay transactions. If you rely on a hosted RPC endpoint for testing, note that rate limits and archive depth can change results, so record the provider and method used for every measurement. For endpoint selection guidance rather than audit methodology, the RPC Assistant covers concrete provider choices; this article stays on process.
Building the threat model that drives the audit
A threat model converts a vague goal ("be secure") into testable questions. Start with the assets: funds held by contracts, privileged roles, user keys, and the integrity of state transitions. Then enumerate adversaries: external attackers, malicious or compromised insiders, and faulty infrastructure. Map each asset to the trust boundary that protects it, and list the assumptions that boundary depends on.
Classical models help structure this. STRIDE (spoofing, tampering, repudiation, information disclosure, denial of service, elevation of privilege) is useful for enumerating attack surfaces in off-chain services and node APIs. For on-chain logic, focus on economic invariants: what must always be true about balances, supply, and access? Write those invariants down as assertions you can test. A finding is only meaningful relative to an invariant it violates.
- List privileged roles and who can assume them, including upgrade and pause authority.
- State every external dependency: oracles, bridges, other contracts, and RPC providers.
- Define invariants such as total supply conservation or collateralization ratios.
- Rank threats by impact and likelihood so remediation effort follows risk.
Static review and automated scanning of contract code
Automated tools find known patterns quickly but miss business-logic flaws. Run static analyzers and linters first to clear low-value noise, then spend human review time on state transitions, access control, and external call ordering. Record the tool version and configuration so results are reproducible. Treat every automated finding as a lead, not a conclusion: confirm exploitability manually before reporting it.
The following example runs a static analyzer against a pinned contract directory and writes machine-readable output. Replace the tool invocation with whichever analyzer your stack uses; the point is the pinned input and saved output.
- Always audit a commit hash, never a moving branch.
- Save raw tool output; reviewers will need to diff it after remediation.
- Triage findings into confirmed, false positive, and needs-manual-review.
# Pin the code under review
cd contracts && git checkout <AUDIT_COMMIT_SHA>
# Run a static analyzer and save reproducible output
slither . --json audit-output/slither-$(date +%Y%m%d).json
# Record tool and dependency versions alongside the report
slither --version > audit-output/tool-versions.txt
forge --version >> audit-output/tool-versions.txtDynamic testing with a forked chain
Dynamic testing exercises the deployed state, not just the source. Fork the chain at the block you are auditing, impersonate privileged accounts in the test environment, and attempt the attacks your threat model predicts. This is where reentrancy, oracle manipulation, and access-control bypasses become concrete. Keep each test as a script so a reviewer can rerun it after a fix.
The snippet below forks a chain and impersonates an account to test a privileged path. Use placeholders for your own RPC URL and addresses; do not hardcode credentials.
- Pin the fork block so results are stable across runs.
- Test both the happy path and the adversarial path for each privileged function.
- Capture gas usage and revert reasons; they often reveal logic errors.
// test/privileged-path.test.js (conceptual example)
const { ethers } = require("ethers");
async function main() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const blockNumber = Number(process.env.AUDIT_BLOCK);
// Impersonate a privileged account on the fork
await provider.send("anvil_impersonateAccount", [process.env.PRIVILEGED_ADDRESS]);
const signer = await provider.getSigner(process.env.PRIVILEGED_ADDRESS);
const target = new ethers.Contract(process.env.TARGET_ADDRESS, ABI, signer);
const tx = await target.sensitiveFunction();
const receipt = await tx.wait();
console.log("status", receipt.status, "block", blockNumber);
}
main().catch((e) => { console.error(e); process.exit(1); });Auditing node, RPC, and infrastructure exposure
Infrastructure findings are frequently the highest-impact and least-reviewed. An RPC endpoint that exposes admin namespaces, an unlocked account, or an unrestricted debug API is a direct path to fund loss. Review the node's enabled modules, bind addresses, firewall rules, and authentication. Then verify behavior from outside the host, not just from the config file.
The diagnostic below checks which JSON-RPC methods a node exposes and whether it leaks account information. Run it against your own staging node, not a third party's endpoint. Interpret the output against your intended exposure: a public read-only endpoint should not answer admin or personal methods.
- Expected: public endpoints expose only read methods; admin and personal namespaces are disabled.
- eth_accounts should return an empty array on a node that does not hold keys.
- Peer count and sync status confirm the node is healthy, not just reachable.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}'
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"eth_accounts","params":[]}'
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"net_peerCount","params":[]}'Recording measurements in a results table
Audit findings become defensible when they are reproducible. For infrastructure and performance-related checks, record each measurement with its method, environment, and timestamp. Do not publish numbers you did not measure; the table below is a template you fill in during your own audit.
| Check | Method | Environment | Observed | Expected | Status |
|---|---|---|---|---|---|
| Exposed RPC modules | rpc_modules | staging node | (record) | read-only set | (pass/fail) |
| Key material on node | eth_accounts | staging node | (record) | empty array | (pass/fail) |
| Peer connectivity | net_peerCount | staging node | (record) | > 0 | (pass/fail) |
| Contract invariant | fork test | fork at block N | (record) | invariant holds | (pass/fail) |
| Access control | impersonation test | fork at block N | (record) | revert for unauthorized | (pass/fail) |
Keep the raw command output next to the table. When a fix is applied, rerun the identical command against the same environment and add a second row rather than overwriting the first. That history is what turns an audit into evidence.
Troubleshooting common audit blockers
Audits stall for predictable reasons. If findings cannot be reproduced, the usual cause is an unpinned commit, a different fork block, or a different RPC provider returning inconsistent state. If dynamic tests pass locally but fail in staging, compare node configuration and chain height before assuming a code bug. If an analyzer floods you with findings, tighten its configuration rather than ignoring output wholesale.
Infrastructure checks often fail for environmental reasons: a firewall rule blocks the probe, the node is still syncing, or the endpoint is rate-limited. Confirm sync status and rate limits before concluding a method is disabled. When you depend on a hosted endpoint, document its limits in the audit scope so reviewers understand which results are provider-dependent.
- Mismatched results: verify commit hash, fork block, and provider.
- Silent RPC failures: check HTTP status and JSON-RPC error objects, not just exit codes.
- Missing archive data: confirm the endpoint supports historical state for your block range.
- Flaky tests: isolate state per test run instead of sharing a mutable fork.
Limitations, tradeoffs, and what an audit cannot prove
No audit proves a system is secure. It samples a defined scope at a point in time against a threat model that may itself be incomplete. Code changes after the audit invalidate its conclusions unless the delta is re-reviewed. Economic attacks, governance capture, and key-compromise scenarios often sit outside code review entirely and require operational controls.
There is also a cost tradeoff. Deeper review of every dependency is expensive, so scoping should follow risk: privileged functions, fund-holding logic, and externally reachable infrastructure deserve the most attention. Be explicit about residual risk in the report rather than implying completeness. A short, honest scope statement is more useful than a long report with hidden assumptions.
From audit to continuous assurance
The final step is turning one-time findings into ongoing controls. Convert confirmed findings into regression tests, add invariant checks to CI, and monitor the infrastructure checks on a schedule. Re-audit after material changes such as upgrades, new external dependencies, or changes to key management. Track remediation status in the same table you used during the audit so nothing is lost between rounds.
If your application depends on hosted RPC infrastructure, include provider behavior in your assurance plan: availability, method coverage, and archive depth all affect what your monitoring can detect. For concrete endpoint and provider decisions, use RPC Assistant; for the surrounding methodology, keep the audit scope, threat model, and results table current. To go deeper on the infrastructure side, see the guide to blockchain node hosting, compare RPC service options, review network coverage, and evaluate dedicated node and RPC pricing against your audit's availability requirements.
- Add invariant and access-control tests to CI so regressions fail fast.
- Schedule infrastructure probes and alert on unexpected method exposure.
- Re-audit after upgrades, dependency changes, and key-management changes.
- Keep the results table versioned alongside the code it describes.