Audit deliverables
How to Tell a Fake Audit Report From a Real One
A fake audit report is a document that claims a security review which never happened, or a real report that no longer describes the code you are about to use. Both are caught the same way, by checking the report against the two things it should name: the commit that was audited and the deployed address it maps to. This guide gives the procedure, and a measurement of how often published reports carry those anchors at all.

Key facts
- The two anchors
- A commit hash ties the report to a revision of the source. A contract address ties it to the chain. Without one of them a genuine report cannot be checked
- Measured archives
- 19 of the 23 DSA member firms publish a reachable report archive, 17 of them on GitHub (September 3, 2026)
- Measured anchors
- Of 50 published reports sampled from those archives, 31 state a commit, 17 an address, 6 both and 8 neither
- Proxy slot
- ERC-1967 keeps the implementation at slot 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, and Etherscan's proxy detection is a heuristic it sums up as trust, but verify
- Audit drift
- At Nomad only 18.6 percent of the on-chain code in Replica.sol had been audited, in the contract the attack went through
Why a forged report works
A PDF with a logo on it is not evidence. It is a claim about evidence held somewhere else, and the somewhere else is what a reader almost never checks. Forgers know this, which is why the cheapest fraud in this market is a document rather than a contract.
The named cases follow one pattern. In , CertiK stated publicly that it had never audited Keep3rLink and that the report circulating in the project's Telegram was fake. In , PeckShield said the report on DxDy.Finance's website had been forged from its own earlier work for a different protocol. CertiK's own account of brand misuse, published in , names the mining site Wixpool and the Lymex exit scam, which listed CertiK as its auditor after failing the firm's identity checks and cost users around 300,000 dollars.
Forgery is only the crude version. The subtler one is a real report that no longer describes the code in front of you, which is the failure the rest of this guide is built around. Both are caught by the same two anchors.
- The commit
- The exact revision of the source the firm reviewed, which turns "we audited this protocol" into a checkable statement about a specific set of files.
- The address
- The deployed contract the code became, on a named chain, which is the only thing your money actually touches.
A report that states both can be checked by anybody with a block explorer. A report that states neither cannot be checked at all, whether or not it is genuine. As the measurement below shows, the second kind is common.
Step 1: get the report from the firm, not from the project
Never verify a document against the site that handed it to you. The first move is to find the same report where the auditing firm publishes, then compare. CertiK gives the same instruction for its certificates: the authenticity of a badge should be checkable on the issuer's own site, and if the issuer link leads nowhere, the firm may not exist at all.
So we measured what the firms publish. On , for each of the 23 DeFi Security Alliance member firms, we looked for a public report archive in two places: a repository on the firm's GitHub account, ranked by how many report PDFs it holds, and an index page on the firm's own domain, which counted only when it showed at least three PDF links or ten distinct report links. The script and the raw results are kept in the site repository as seo/research/audit-report-anchors-2026-09-03.json.
| Channel | Firms | Notes |
|---|---|---|
| Public archive found | 19 of 23 | Either channel counted |
| GitHub repository | 17 | From 3 PDFs at Blaize to 3,927 at Cyberscope |
| Index on the firm's domain | 9 | Nine firms publish in both places |
| Nothing found | 4 | Expelee, Callisto Network, Solidity Finance and PaladinAI, where the site was unreachable, redirected or listed no reports at the paths we tried |
Two practical points come out of that. A GitHub archive is the better anchor, because a repository carries file history: you can see when a report was added and whether it was changed afterwards, which a PDF emailed to you cannot tell you. And an archive under a firm's own name is not the same as an archive of that firm's work. SlowMist keeps its reports in a repository called Knowledge-Base and SolidProof keeps its in one called Projects, so searching for the word audit finds neither.
If the firm has no public archive, the report is not automatically fake. It does mean the only route left is asking the firm directly, which is the last section of this guide.
Step 2: match the commit and the file list
Open the report's scope section. ChainSecurity's guide to reading audit reports puts the point plainly: the scope names the files inside the audit and excludes those outside it, and reports either give the commit hash of the repository or list the files with their hashes. Sherlock, describing its own process, calls an audit a point-in-time review of a scoped codebase.
Three real reports show what a usable scope looks like. Trail of Bits reviewed NFTX Protocol v2 at commit c8ddc72b4400ad1e12ed03f4369b765371564a00 in , on-chain contracts only. OpenZeppelin's USDT0 review names the repository and commit 01cdf1d74c1bd4d9a664de4755ac5112f2a9988c with two files formally in scope, dated . Code4rena's Sequence report names commit 4c0a98259624b80ac22385e40f83a2145e8e33e2 across 34 contracts in .
Then we checked how common that is. From the 17 GitHub archives we sampled 50 published reports, three per firm, spread across each archive rather than taken from the top, converted them to text and looked for the two anchors. Sampling per firm rather than per report is a deliberate choice with a cost: an archive of three reports weighs as much as one of 3,927, and three of the fifty are reviews of OpenZeppelin's own library, which has no single deployment to name.
| What the report states | Reports | What a reader can do with it |
|---|---|---|
| A commit hash | 31 of 50 | Compare the audited revision against the repository |
| A contract address | 17 of 50 | Compare the report against the chain |
| Both | 6 of 50 | Follow the chain from report to deployment end to end |
| Neither | 8 of 50 | Nothing, even if the report is genuine |
| A repository URL | 26 of 50 | Find the code, though not the revision |
How far to take this depends on what is at stake. Ten minutes before a small swap buys step 1 and the verified-source check in step 3, which together catch outright forgery. A treasury allocation or a listing review has no business skipping the commit match or the proxy read, because those are the two places a genuine report quietly stops describing the deployment.
Six in fifty is the number worth carrying away. The commit is the more common anchor, which fits how firms work: they are engaged against a repository, not against a deployment that may not exist yet. That leaves the reader holding a genuine report with no stated link to the address they are about to send money to, and closing that gap is step 3.
When the scope names files rather than a commit, read the list carefully and count what is missing. A protocol whose report covers the token and the staking contract, but not the treasury or the upgrade admin, has been audited exactly as far as that list goes.
Step 3: match the deployed address, then the implementation
Take the address from the project, not from the report, because the report is what you are testing. Open it on a block explorer and read three things in order.
- Is the source verified. Verification proves that published source compiles to deployed bytecode. It says nothing about whether that code is safe, and nothing about who reviewed it.
- How well it matches. On Etherscan an exact match covers the deployed code together with its constructor arguments, while a similar match happens automatically when the bytecode matches an already verified contract and, in Etherscan's words, does not take into account constructor arguments. Two deployments of the same code with different owners and different fee settings both pass as similar. Sourcify draws the same line: an exact match is byte for byte identical excluding libraries and immutables, and anything less means comments, variable names or source paths may differ from what you are reading.
- Whether it is a proxy. If it is, the code you just read is a forwarder and the logic lives elsewhere.
For the third point, read the implementation slot yourself rather than trusting a label. ERC-1967 fixes it at 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, derived as keccak256('eip1967.proxy.implementation') minus one, with the admin who may replace it at 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103. One command answers it, and the address is the last 20 bytes of a 32 byte word, so the leading zeros are padding rather than part of it.
cast storage <proxy> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url <rpc>
eth_getStorageAt call, so a node URL is enough when Foundry is not installed.Two more patterns need a different lookup. A beacon proxy points at a beacon instead, ERC-1822 keeps the logic address under keccak256("PROXIABLE"), and an ERC-2535 diamond has no single implementation at all, only facets mapped per function selector.
| Pattern | Where the code address lives | What to check |
|---|---|---|
| Transparent or UUPS under ERC-1967 | Storage slot ending d382bbc | Read the slot, then verify the implementation address separately |
| Beacon proxy | Beacon slot ending 5133d50, then the beacon's own implementation | Two hops, and the beacon can be swapped for every proxy at once |
| UUPS under ERC-1822 | keccak256("PROXIABLE") | A different slot from ERC-1967, so a single lookup can miss it |
| Diamond under ERC-2535 | Many facets, one per selector | Enumerate facets through the loupe functions the standard requires |
Etherscan is candid about the limits of its own detection. It looks for bytecode patterns associated with known proxy implementations, notes that contracts using libraries can trigger the same signal without being proxies, and warns that the proxy may not be delegating where it appears to. Its own summary of the situation is "Trust, but verify". For custom proxies it relies on a trace of an earlier delegatecall, so a freshly deployed proxy can show nothing at all.
What an upgrade does to the report
An audit describes a revision. An upgradeable protocol describes whatever the admin last deployed. Between those two facts sits the failure mode that has cost more than any forged PDF.
Nomad is the case to know. Reviewing the bridge after its loss, Zellic compared the on-chain code against the audited code and found that only 18.6 percent of the on-chain code in Replica.sol had been audited, in the contract holding the functions the attack went through. The report was real. The deployment had moved.
Three consequences follow for anybody checking a report today.
- Date the implementation, not the report. An audit dated last year describes last year's code only if nobody upgraded since. Read the proxy's admin events to see when the implementation last changed.
- A verified proxy is not a verified implementation. Etherscan stores the implementation it found when verification last ran, so after an upgrade the process has to be repeated before the explorer reflects reality.
- Ask who can upgrade. The admin slot names the account, and one key behind that account is the difference between an audited system and an audited snapshot of one.
Compiler settings are the honest counterexample to all of this. Sourcify's documentation traces how a single changed byte in the source alters the metadata hash and so the bytecode, which is why a mismatch is a question to ask rather than proof of fraud. Optimization runs and the metadata setting are the usual culprits, and the fix is to ask the team for the exact compiler configuration and rebuild.
Badges, scores and what they actually assert
A badge on a project's website is an image and a link. The image proves nothing, so follow the link and see whether it lands on a page the issuer controls that names this project.
Scores are a different claim again. CertiK's Skynet score combines six categories, the aggregation is proprietary, and it explicitly incorporates third-party audit findings, so a high score is not a statement that CertiK audited anything. Its project pages make the distinction visible: the Celestia page we opened shows a grade of A alongside "CertiK Audit: No" and a separate third-party audit field. Skynet's own methodology says outright that many listed projects lack a comprehensive audit or do not disclose one.
Automated scanners sit lower still. A honeypot check, a holder distribution chart and a bytecode pattern match against known scam templates are useful signals about a token, and none of them asserts that a human reviewed the code. Treat "audit score 92" from a scanner as what it is, which is a heuristic, not an audit. Our analysis of audit companies profiles the firms behind the badges, and the CertiK company analysis covers what that firm's own marks include.
The KYC badge deserves its own sentence, because it is the most misread mark in the market. It says a firm collected identity documents from a team. It does not say the code was reviewed, and CertiK's guidance on spotting forged certificates is aimed at exactly this confusion.
When it does not check out
Rank what you found. A missing commit is a documentation failure, common enough that our sample found it in 19 of 50 reports. An address that does not match the audited code is a live risk. A firm that never audited the project at all is fraud, and the sequence below is worth following in order.
- Ask the firm through a channel you found yourself. Use the contact on the firm's own domain, never a Telegram handle supplied by the project. CertiK's brand-misuse page points readers at Skynet, a report form and its moderated channels, and the firm runs an employee verification tool precisely because scammers impersonate its staff to sell audits.
- Put the question in writing and keep it narrow. Ask whether the firm audited this repository at this commit for this client on this date, and whether the attached PDF is the report it issued. A firm that will not confirm a document bearing its own name has told you something.
- Check the report against its own internal logic. CertiK's guidance on fake certificates lists spelling errors and missing dates as the visible tells. The Bureau Veritas report examined by CryptoInformer in went further: a document dated referenced an audit taking place on , more than two weeks after it was written.
- Say what you found. Publicly, if the project will not answer.
Regulators have started saying the same thing in their own register. In , the SEC's chief accountant warned that non-audit crypto engagements were being marketed as if they were audits, and told firms whose names are misused to disassociate publicly.
Finally, keep the exercise in proportion. Verification tells you the report describes the code. It does not tell you the code is safe, and the largest losses do not come from the class of bug an audit hunts: a four-year study of 23,818 public audit findings against 218 exploit incidents totalling about 7.76 billion dollars found that private key compromise, phishing and social engineering account for roughly 49.6 percent of losses while barely appearing in audit findings at all. What a team can run before paying anybody is in the fuzzing guide. Reading a report's findings properly, and the wider diligence process around it, are the subjects of the next two articles in this series, how to read an audit report and the due diligence walkthrough.
Frequently asked questions
How do I check whether a CertiK audit is real?
Find the project on Skynet rather than trusting the badge on its website. The project page states separately whether CertiK audited the project and whether a third party did, so a high score with CertiK Audit set to No is not a CertiK audit. If the page does not exist or contradicts the PDF you were sent, use the report form and moderated channels on certik.com, and check the sender domain of any mail claiming to come from the firm.
Does this work on a chain without Etherscan?
The three steps do, the tools change. Blockscout verifies through its own interface and through Sourcify, and the Verifier Alliance shares one verified-contract database across Blockscout, Routescan and Sourcify, so an address on a smaller EVM chain usually has somewhere to check. What does not travel is the proxy convenience: read the ERC-1967 slot with a node call rather than looking for a Read as Proxy tab. On a non-EVM chain the storage slot conventions do not apply at all, and the commit match in step 2 becomes the whole of the check.
The project shows a different version of the report than the firm does. Which one counts?
The firm's copy, and the difference is worth reading rather than dismissing. Reports are reissued after fixes, so the same engagement can have an initial version with open findings and a later one where they are resolved, both genuine. Compare the commit each version names: a later commit means a real second pass, the same commit with fewer findings means the severity was renegotiated rather than the code fixed, and only the firm can say which.
What if the firm will not confirm the report?
Treat silence as an answer and act on the risk rather than the document. Ask once, in writing, whether the firm audited this repository at this commit for this client, using a contact you found on the firm's own domain. Firms that learn of forgeries do disclaim them publicly, which is how the Keep3rLink and DxDy.Finance cases entered the record, so a non-answer about a document bearing the firm's name is itself informative.