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.