Ask the cluster what is actually running at the address your program declares.
A linter reads the manifest and has no idea whether anything was ever deployed. A block explorer knows what is running and has never read the manifest that built it. This release joins the two, behind a flag you have to ask for.
On chain
--mainnetcollects everydeclare_id!in the tree and asks Solana what is at each address. The report gains a section above the findings: whether an account exists, whether it is a program, and — under the upgradeable loader — who holds the upgrade authority and at which slot the code was last replaced.--rpc-url <URL>points the lookup somewhere else and implies--mainnet. Without the flag VaultLint makes no network calls at all, which stays the default.- Findings in code that is actually running are marked
live on mainnet at <address>. A finding is marked when the crate it sits in is compiled into the program deployed at that address — the path-dependency closure below that program's crate, followed transitively, because a shared library declares no id of its own and its arithmetic runs on chain regardless. - Severity is unchanged by any of this, on purpose. The exit code has to be a function of your source alone, or the same commit passes CI today and fails tomorrow because an endpoint was slow. An unmarked finding means not shown to be live, never not live, and a question the cluster could not answer is never reported as an answer.
Rules
- VL003 reports a missing
overflow-checkswithout waiting for arithmetic to point at. The project-level finding used to require at least one Low arithmetic site alongside it. Measured against fifteen unaudited Solana repositories, six are built without overflow checks and only one of the six writes arithmetic in the shape the rule recognises — so the old gate silenced a true finding in five cases out of six, and the setting is wrong in all six whether or not a wrapping expression happens to be written yet. Two narrowings replace it: the tree must contain on-chain code, and the manifest must lie inside the directory you scanned.
This is a minor release rather than a patch because it adds output and because the VL003 change can report a finding your last build did not. That is the line the versioning promise draws: a patch only ever makes VaultLint quieter.
Fewer false positives, and a scan that survives whatever you point it at.
Nothing here reports anything new. Every rule change in this release makes VaultLint quieter, on findings the first release should not have raised.
Rules
- VL002 and VL005 now read
#[access_control(...)]. Anchor runs that function — and aborts the instruction on its error — before the handler body, and programs put owner and program-id checks there. A check written in a guard now silences the finding it covers. The call is resolved by qualified name, so one struct'saccountsmethod cannot stand in for another's, and only within the same file: an imported checker stays invisible, which leaves the finding in place rather than inventing a silence. - VL004 no longer reports a
create_program_addresscall whose bump seed is read from account data. The trigger was unconditional, so it fired on the very fix it recommends — store the canonical bump at init, then derive with&[stored.bump]. A rule that reports its own remediation teaches people to ignore it. A caller-supplied bump is still reported. - VL002 reports a bare
AccountInfoparameter at Medium instead of High. When the account arrives as a helper's argument, whatever the caller proved about it is in another function, and VL002 reads one function at a time. The finding stays — a helper that trusts its caller is a shape Metaplex has historically been exploited through — but the rule no longer fails a default--fail-on highbuild on a question it admits it cannot answer.
Output
- The human report prints each finding's documentation address. It was already in the JSON and SARIF output and nowhere in the one people actually read. That line also carries the rule id, which a
// vaultlint:allow VL002comment needs and the human report otherwise never spelled out. - The skipped-files half of the JSON envelope now deserialises into VaultLint's own type, so a tool reading a scan back no longer has to declare its own struct for it.
Robustness
- A file nested thousands of blocks deep no longer aborts the scan. Rust's parser descends recursively, so past a few thousand braces it exhausts the stack — an abort the process cannot catch, which took the whole run down and reported nothing, not even the files already scanned. Such a file is now skipped with a reason, like any other file that cannot be read. The limit is eight times deeper than rustc's own recursion limit; no real program comes close.
First public release.
Five rules, three output formats, and an exit code you can gate CI on. Everything runs offline.
Rules
- VL001 — unproven authority on initialization (Medium)
- VL002 — missing owner check (High)
- VL003 —
overflow-checksis not enabled (Medium) - VL004 — non-canonical PDA bump (Medium)
- VL005 — unchecked CPI to unknown program (Medium)
Output and CI
--format human(default),json, andsarif. The SARIF output targets GitHub code scanning, so findings show up as annotations on a pull request.--fail-on high|medium|low|never, defaulting tohigh, controls the exit code — this is what makes VaultLint usable as a gate rather than a report.- Every finding carries the address of its rule page. In SARIF that becomes
helpUri, so the "learn more" link on a GitHub Security alert resolves.
Suppression and project detection
- Inline
// vaultlint:allow VL001, honoured on the finding's own line or in the block of comments and attributes above it. A barevaultlint:allowwith no rule id is deliberately not honoured — silencing every rule at once should have to be explicit. - VaultLint finds the workspace manifest Cargo reads
[profile.release]from and takes the Anchor version and theoverflow-checkssetting from it, so VL003 stays quiet on projects that already enable overflow checks.
Worth knowing
- No network calls, no telemetry. Your source never leaves your machine.
- Every rule was measured against open-source production programs before it shipped, and narrowed until the false-positive rate was acceptable.
- This is a linter, not a prover. A clean run means these five shapes were not found — it is not a statement about your program's security.
That's the whole history so far.
0.1.0 was the first public release, so there is nothing older to list. Releases are announced on @vaultlint, and the full history also lives in CHANGELOG.md in the repository.