A single missed reentrancy check drained 124.5 million tokens from Hemi’s Genesis Drop event earlier this year, a $255,000 loss that a five-minute static analysis pass would likely have caught before deployment. Across the whole industry, CertiK’s Hack3d report puts on-chain losses at more than $1.3 billion across 344 incidents in just the first half of 2026, while Immunefi separately tracked $680.3 million lost to DeFi-specific exploits over the same stretch. Most of that money didn’t disappear because of some exotic zero-day. It disappeared because contracts shipped with bugs that tools already know how to find.

This tutorial walks through setting up Slither, the open-source static analyzer built by Trail of Bits, and using it to catch the vulnerability classes that keep showing up in postmortems: reentrancy, unchecked external calls, access control gaps, and arithmetic mistakes. By the end you’ll have a working Foundry project wired into a Slither scan, a GitHub Actions job that blocks merges on new findings, and a checklist for triaging what the tool reports. This is a hands-on build, not a lecture on smart contract security theory.

Why static analysis still matters in 2026

Audits from firms like Trail of Bits, OpenZeppelin, and Consensys Diligence remain the gold standard for high-value contracts, but they’re expensive and slow, and most teams can’t afford a six-week audit cycle every time they ship an update. Static analysis fills the gap between “wrote the code” and “paid an auditor to look at it.” It runs in seconds, costs nothing, and catches the same categories of bugs that show up over and over in exploit reports.

DeFiLlama’s own 2026 exploit tracking tells a similar story from a different angle: more than $1 billion stolen across upwards of 140 separate exploits year-to-date, spread across chains and protocol types rather than concentrated in one obscure corner of the ecosystem. When three independent trackers (CertiK, Immunefi, and DeFiLlama) all land in the same billion-dollar-plus range for the same period, using different methodologies and different incident sets, that convergence is itself the signal worth paying attention to. It isn’t a handful of unlucky teams. It’s a pattern across the industry, and a meaningful share of it traces back to bug classes a five-second scan flags by default.

Slither describes itself plainly: “Slither is a Solidity & Vyper static analysis framework written in Python3,” according to the project’s own GitHub README. The same document adds that it “runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses.” That last part matters, because out of the box Slither ships with dozens of detectors, and teams that outgrow the defaults can write their own.

Ethereum’s own developer documentation lists Slither in its guide to smart contract security tools, describing it under a comparison table as a CLI-based static analyzer that runs in seconds with moderate setup effort and low false-positive noise relative to some competing tools, per ethereum.org’s developer tutorials. That combination of speed and low friction is exactly why it fits into a CI pipeline instead of sitting on a shelf next to the audit report nobody rereads.

None of this replaces a manual audit before a mainnet launch that will hold real user funds. Static analysis catches patterns. It doesn’t understand your protocol’s economic assumptions or catch a flawed liquidation formula. Treat it as the first filter, not the last one.

What Solidity’s built-in overflow checks do and don’t cover

One reason teams get overconfident is Solidity 0.8’s automatic overflow and underflow reverts, which closed off a bug class that used to dominate exploit writeups. That protection only applies to normal arithmetic, though. Code wrapped in an unchecked block, added specifically so gas-conscious developers could skip the runtime check when they’re certain an overflow can’t happen, brings the old risk right back if that certainty turns out to be wrong. Slither’s arithmetic detectors specifically watch for unchecked blocks that touch user-controlled values, since that’s exactly the spot where a developer’s assumption and the actual input space can quietly diverge.

This matters more than it sounds like it should, because unchecked blocks show up constantly in gas-optimized code, including in widely used libraries. A detector flagging one isn’t automatically wrong just because the surrounding library is well-known and audited. It’s worth five minutes to actually confirm the bound that makes the unchecked math safe, rather than assuming popularity equals correctness.

Prerequisites and versions

Before starting, confirm you have the following installed. Version drift between Slither, Solidity, and your compiler is the single most common source of confusing scan output, so pin these explicitly rather than trusting whatever your package manager grabs.

  • Python 3.9 or newer (Slither is distributed as a Python package)
  • pip or pipx for installing Python packages
  • Slither 0.11.6, the current release as of late September 2026
  • Solidity compiler 0.8.37, the current stable release
  • Foundry v1.8.3 or newer, for the local test project and CI integration
  • Node.js 20+ only if you’re auditing a Hardhat-based repo instead of Foundry
  • Git and a GitHub (or GitLab) account if you want the CI section to work end to end

You don’t need a testnet wallet or gas for any of this. Slither operates entirely on source code and compiler output, and nothing gets deployed or broadcast during a scan.

Step 1: Install Slither

The cleanest install path avoids polluting your system Python with a big dependency tree. Use pipx, which installs Slither into its own isolated environment while still making the command globally available.

python3 -m pip install --user pipx
pipx ensurepath
pipx install slither-analyzer==0.11.6

slither --version

If pipx isn’t available on your system, a plain virtual environment works too:

python3 -m venv .slither-venv
source .slither-venv/bin/activate
pip install slither-analyzer==0.11.6
slither --version

Either path should print 0.11.6 back to you. If it doesn’t, check that your shell is actually picking up the pipx-managed binary directory and not an old install shadowing it in your PATH.

Step 2: Install Foundry and the Solidity compiler

Foundry gives you forge for building and testing contracts and anvil for a local chain, both of which Slither can lean on for more accurate analysis than parsing raw source alone.

curl -L https://foundry.paradigm.xyz | bash
foundryup

forge --version
# expect: forge 1.8.3 or newer

Foundry’s v1.8.3 release added expanded fork execution, scripting, and invariant fuzzing support, according to the release notes on the project’s official Foundry Book documentation site. Invariant fuzzing in particular pairs well with Slither: static analysis flags what could go wrong, fuzzing tries to prove it actually does.

Solidity itself ships as part of Foundry’s toolchain, but you can pin an exact compiler version in your project config, which you’ll do in the next step.

Step 3: Scaffold a Foundry project with a deliberately vulnerable contract

To make the rest of this tutorial concrete, build a small vault contract with three classic bugs baked in: a reentrancy hole, a missing access control check, and an unchecked low-level call. You’ll fix all three by the end.

forge init slither-audit-demo
cd slither-audit-demo

Edit foundry.toml to pin the compiler version:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.37"
optimizer = true
optimizer_runs = 200

A quick note on why these three specific bugs: reentrancy accounts for a large share of historical DeFi losses because it lets an attacker’s contract “call back into” the vulnerable function mid-execution, before the first call has finished updating state. Missing access control is even simpler to exploit and doesn’t require any clever call sequencing at all, just a transaction from any address. The unchecked call is the quietest of the three since the contract keeps running normally even when the underlying transfer silently fails, which is exactly why it tends to surface in production rather than getting caught in testing.

Now create src/VulnerableVault.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.37;

contract VulnerableVault {
    mapping(address => uint256) public balances;
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    // Bug 1: reentrancy — balance is zeroed AFTER the external call
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "insufficient balance");
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "transfer failed");
        balances[msg.sender] -= amount;
    }

    // Bug 2: missing access control on a privileged function
    function sweepFees(address payable to, uint256 amount) external {
        to.transfer(amount);
    }

    // Bug 3: unchecked return value on a low-level call
    function forwardTo(address target, bytes calldata data) external {
        target.call(data);
    }
}

Build it once to confirm the compiler is happy:

forge build

Step 4: Run your first Slither scan

From the project root, point Slither at the whole repo:

slither .

Slither will detect that this is a Foundry project, invoke forge build internally to get accurate compilation artifacts, and then run its detector suite against the resulting AST. Expect output that looks roughly like this:

VulnerableVault.withdraw(uint256) (src/VulnerableVault.sol#15-20) sends eth to arbitrary user
        Dangerous calls:
        - (success,None) = msg.sender.call{value: amount}() (src/VulnerableVault.sol#17)
Reentrancy in VulnerableVault.withdraw(uint256) (src/VulnerableVault.sol#15-20):
        External calls:
        - (success,None) = msg.sender.call{value: amount}() (src/VulnerableVault.sol#17)
        State variables written after the call(s):
        - balances[msg.sender] -= amount (src/VulnerableVault.sol#19)
VulnerableVault.forwardTo(address,bytes) (src/VulnerableVault.sol#28-30) ignores return value by target.call(data) (src/VulnerableVault.sol#29)
VulnerableVault.sweepFees(address,uint256) (src/VulnerableVault.sol#23-25) sends eth to arbitrary user with no access control
. analyzed (2 contracts with 100 detectors), 4 result(s) found

That single scan surfaced all three planted bugs plus a general warning about sending ETH to an arbitrary address. This is roughly the same class of finding that would have flagged the pattern behind Hemi’s Genesis Drop reentrancy loss, where state was updated after external value moved out of the contract rather than before.

Step 5: Read and triage the output

Slither’s default output is dense. Each finding names a detector, a severity implied by its category, and a file-and-line reference. New users often make one of two mistakes here: dismissing everything as noise, or trying to fix every single line the tool prints, including the low-severity informational ones.

A more useful workflow is to sort findings by detector impact first. Slither exposes this with a filter flag:

# Only show High and Medium impact findings
slither . --exclude-low --exclude-informational

# List every detector Slither knows about, with its impact rating
slither --list-detectors

The reentrancy and unprotected-ether-withdrawal findings above are High impact. The unchecked low-level call is typically Medium. Triage High first, Medium second, and treat Low/Informational findings as backlog items you review during your next refactor, not blockers for a release.

Each detector actually carries two separate ratings, not one: impact and confidence. Impact describes how bad the bug is if it’s real. Confidence describes how sure Slither’s analysis is that it actually applies to your code, as opposed to a pattern that merely resembles the vulnerable shape. A High-impact, High-confidence finding deserves immediate attention. A High-impact, Low-confidence finding is still worth five minutes of manual review, since the cost of a missed reentrancy bug dwarfs the cost of a quick double-check, but it’s reasonable to not treat it as an automatic release blocker the way you would a High-confidence match. Reading both fields together, instead of just skimming the finding text, is what separates efficient triage from either chasing ghosts or missing real bugs.

Step 6: Fix the reentrancy bug with checks-effects-interactions

The standard fix is to reorder the function so state changes happen before the external call, following the checks-effects-interactions pattern:

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "insufficient balance");
    balances[msg.sender] -= amount; // effect before interaction
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "transfer failed");
}

Re-run slither . and the reentrancy warning for this function disappears. For extra defense in depth on contracts handling significant value, add OpenZeppelin’s ReentrancyGuard modifier as a second layer rather than relying on ordering alone.

Step 7: Add access control to the privileged function

The sweepFees function let anyone drain the contract’s fee balance to any address. Lock it down:

modifier onlyOwner() {
    require(msg.sender == owner, "not owner");
    _;
}

function sweepFees(address payable to, uint256 amount) external onlyOwner {
    to.transfer(amount);
}

For anything beyond a single-owner toy contract, swap this hand-rolled modifier for OpenZeppelin’s AccessControl or Ownable2Step, which handle ownership transfer edge cases (like transferring to a dead address by mistake) that a bare onlyOwner modifier doesn’t.

Step 8: Handle the unchecked call return value

The forwardTo function ignored whether the downstream call actually succeeded, meaning a failed call would silently look like it worked. Capture and check the return value:

function forwardTo(address target, bytes calldata data) external onlyOwner {
    (bool success, bytes memory returnData) = target.call(data);
    require(success, string(returnData));
}

Notice this also picked up the onlyOwner modifier, since an arbitrary-call forwarder with no access control is its own separate hazard, independent of the return-value bug. Re-run Slither once more. A clean pass on this contract should now report zero High or Medium findings.

Step 9: Generate a human-readable report

For sharing results with a team or attaching to a pull request, export findings as JSON or Markdown instead of reading raw terminal output:

# Machine-readable, useful for CI gating logic
slither . --json slither-report.json

# Markdown, useful for pasting into a PR description
slither . --checklist > slither-checklist.md

The checklist output groups findings by detector and includes checkboxes, which makes it easy to track which ones a reviewer has confirmed as real versus dismissed as a false positive during manual review.

If your team already uses GitHub’s code scanning dashboard for other languages, Slither can output SARIF format too, which slots directly into that same interface instead of living in a separate artifact nobody checks:

slither . --sarif slither-results.sarif

Upload that file with the github/codeql-action/upload-sarif action in your workflow, and findings appear as annotations directly on the relevant lines in GitHub’s Security tab, next to whatever other scanners (dependency checks, secret scanning) your org already has running there.

Step 10: Suppress confirmed false positives properly

Not every finding is a real bug. Sometimes Slither flags a pattern that’s intentional and safe in context. Resist the urge to just delete the line from your output and move on. Instead, mark it explicitly so the suppression is visible in code review and doesn’t silently hide a real regression later.

// slither-disable-next-line reentrancy-eth
(bool success, ) = trustedRecipient.call{value: amount}("");

You can also exclude specific detectors project-wide in a config file if a detector consistently produces noise for your codebase’s patterns:

{
  "detectors_to_exclude": "naming-convention,solc-version",
  "filter_paths": "lib/,test/"
}

Save that as slither.config.json in your project root, and Slither picks it up automatically on every run. Excluding test/ and lib/ (your dependencies) is standard practice, since you generally don’t need findings against vendored code you didn’t write and can’t easily change.

Step 11: Wire Slither into GitHub Actions

A scan that only runs on your laptop gets skipped the first time someone’s in a hurry. Put it in CI so every pull request gets checked automatically. Create .github/workflows/slither.yml:

name: Slither Static Analysis

on:
  pull_request:
  push:
    branches: [main]

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1

      - name: Run Slither
        uses: crytic/[email protected]
        id: slither
        with:
          node-version: 20
          fail-on: high
          slither-args: --exclude-dependencies

The fail-on: high setting means the job fails (and blocks merging, if you’ve set branch protection to require it) only when a High-severity finding shows up, keeping Medium and Low findings visible in logs without gating every PR on cosmetic issues.

Step 12: Cross-check with a second tool

No single static analyzer catches everything, and detector overlap between tools is intentionally imperfect. A common pairing is Slither for fast, broad static analysis plus Foundry’s built-in fuzzing and invariant testing for the logic bugs that pattern-matching alone can’t reach, like a flawed reward-calculation formula or a rounding error that only shows up after thousands of interactions.

# Fuzz test the vault to check the invariant:
# "sum of all balances never exceeds the contract's ETH balance"
forge test --match-contract VaultInvariantTest -vv

Write the invariant test itself in test/VaultInvariant.t.sol, asserting that the contract’s own ETH balance always covers the sum of tracked user balances. If Slither’s static pass and Foundry’s fuzz-based invariant testing both come back clean, you’ve covered two very different failure modes with two different techniques, which is a meaningfully stronger signal than either one alone.

Common Slither detectors and what they actually mean

The full detector list runs past 90 entries once you count style and gas-optimization checks alongside the security-relevant ones, which is more than any team needs to memorize. In practice, a handful of detectors account for most of the findings that matter in a typical review, and it’s worth knowing these by name before you ever run the tool, so the output reads as familiar rather than as a wall of unfamiliar jargon.

Detector nameImpactWhat it flagsTypical fix
reentrancy-ethHighState written after an external call that sends ETHChecks-effects-interactions ordering, ReentrancyGuard
unprotected-upgradeHighUpgradeable proxy contract missing access control on the upgrade functionAdd onlyOwner/onlyAdmin to upgrade logic
arbitrary-send-ethHighFunction sends ETH to an address controlled by caller input, with no restrictionAccess control modifier, allowlist recipients
unchecked-lowlevelMediumReturn value of a low-level call() not checkedRequire the success bool, revert with returndata
tx-originMediumUsing tx.origin instead of msg.sender for authorizationReplace with msg.sender-based checks
timestampLowLogic depends on block.timestamp in a way miners/validators can nudgeWiden tolerance windows, avoid tight equality checks
naming-conventionInformationalVariable or function names don’t follow Solidity style guideCosmetic; fix during normal refactors

Slither versus other tools in the same space

Teams frequently ask whether Slither replaces symbolic execution tools like Mythril, or fuzzers like Echidna and Foundry’s own invariant testing. It doesn’t, and each approach catches a different shape of bug, and mature audit workflows run more than one.

The practical takeaway for a small team without a dedicated security engineer: start with Slither because the setup cost is close to zero and the payoff is immediate, add Foundry invariant tests once you have core protocol logic worth protecting, and budget for a manual audit before any deployment that will hold funds beyond what your team is comfortable losing outright. Skipping straight to “we’ll get audited eventually” without the cheap static analysis step in between is how known, catchable bugs make it into an audit scope that a firm is billing by the hour to review.

ToolTechniqueSpeedBest for
SlitherStatic analysis (AST pattern matching)SecondsKnown vulnerability patterns, CI gating
MythrilSymbolic executionMinutes to hoursDeep path exploration, complex conditionals
EchidnaProperty-based fuzzingMinutesBusiness logic invariants, economic assumptions
Foundry invariant testsStateful fuzzingMinutesMulti-call sequences breaking protocol invariants
Manual auditHuman reviewDays to weeksArchitecture flaws, economic exploits, novel attack paths

Five pitfalls that trip up first-time Slither users

  • Running Slither before pinning a compiler version. Mismatched solc versions between your foundry.toml and what’s actually installed cause confusing compile errors that look like Slither bugs but aren’t.
  • Treating every finding as equal severity. A naming-convention warning and an unprotected-ether-withdrawal warning are not the same problem. Sort by impact before doing anything else.
  • Scanning node_modules or lib/ dependencies by accident. Third-party libraries you didn’t write generate a wall of irrelevant noise. Use filter_paths in your config to exclude them.
  • Silently deleting suppressed findings instead of annotating them. A future contributor (including future you) needs to see why a finding was dismissed, not just that it vanished.
  • Assuming a clean Slither run means the contract is safe to deploy with real funds. It means known patterns weren’t detected. It says nothing about your economic model, oracle assumptions, or upgrade governance.
  • Ignoring compiler warnings while only watching Slither’s output. Some issues, like unused return values or shadowed state variables, show up as plain solc warnings during forge build well before Slither ever runs. Reading both outputs catches more than reading either alone.

Troubleshooting

  • “Slither: command not found” after install. Your pipx or pip user bin directory isn’t on PATH. Run pipx ensurepath, then open a new shell session.
  • “Error: Foundry.toml not found” or Slither can’t detect the framework. Run Slither from the project root, not a subdirectory, and confirm forge build succeeds on its own first.
  • Compiler version mismatch errors. Confirm the solc_version in foundry.toml matches the pragma statement in your contracts, and that Foundry has actually downloaded that solc build (forge build --force to retrigger).
  • Slither hangs on a large codebase. Use --filter-paths to scope the scan to src/ only, excluding test files and dependencies, which cuts analysis time significantly on bigger repos.
  • Too many findings to review manually. Start with --exclude-low --exclude-informational to see only what actually matters first, then work backward.
  • False positive on an intentional pattern. Use inline slither-disable-next-line comments rather than editing detector config globally, so the suppression stays scoped to that one line.
  • GitHub Action fails with no clear error. Check that the workflow installed Foundry before running the slither-action step; Slither needs forge available to compile Foundry-based repos.
  • Detector flags code from an imported OpenZeppelin contract you didn’t write. Add lib/ to filter_paths in slither.config.json; you generally shouldn’t be patching vendored, audited libraries yourself.

Advanced tip: writing a custom detector

Once the default detector suite feels routine, Slither’s Python API lets you write project-specific checks, useful for enforcing an internal convention like “every external-facing function that moves value must emit an event.” A minimal custom detector looks like this:

from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification

class MissingEventOnTransfer(AbstractDetector):
    ARGUMENT = "missing-event-on-transfer"
    HELP = "External functions moving value should emit an event"
    IMPACT = DetectorClassification.MEDIUM
    CONFIDENCE = DetectorClassification.MEDIUM

    def _detect(self):
        results = []
        for contract in self.contracts:
            for function in contract.functions:
                if function.visibility == "external" and function.can_send_eth():
                    if not function.emit_events():
                        results.append(self.generate_result(
                            [f"{function.name} sends ETH without emitting an event"]
                        ))
        return results

Register it as a plugin, and it runs alongside the built-in detector suite on every scan. This is overkill for a single small contract, but for a team maintaining dozens of contracts across multiple repos, a shared custom detector enforces conventions that code review alone tends to miss under deadline pressure.

Complete working project structure

By the end of this tutorial, your repository should look like this:

slither-audit-demo/
├── foundry.toml
├── slither.config.json
├── .github/
│   └── workflows/
│       └── slither.yml
├── src/
│   └── VulnerableVault.sol      (now fixed)
├── test/
│   └── VaultInvariant.t.sol
└── slither-checklist.md

One detail worth pinning down before you consider this “done”: make sure the Slither and solc versions your CI runner installs match what’s in this tutorial (or whatever you’ve since upgraded to) exactly, not just “latest.” A detector suite update between versions can change which findings show up, and a version drift between a developer’s laptop and the CI runner is a common source of “it passed locally but failed in the Action” confusion. Pin versions in both foundry.toml and the CI workflow file, and bump them deliberately in their own pull request rather than letting a `latest` tag update from underneath you.

Run the full sequence one more time to confirm everything is wired together correctly:

forge build
slither . --exclude-low --exclude-informational
forge test
git add .
git commit -m "Add Slither CI gate and fix reentrancy, access control, unchecked call"
git push

Pushing that commit should trigger the GitHub Action from Step 11, and the check should pass with zero High-severity findings against the fixed contract.

What real 2026 incidents tell you about where to focus

Static analysis is easiest to justify when you can point at what it would have caught. Hemi’s Genesis Drop event lost 124.5 million tokens, roughly $255,000, to a reentrancy bug that let an attacker re-enter a claim function before internal state updated, the exact pattern Slither’s reentrancy-eth detector is built to flag. Separately, the Nomic bridge incident froze 36% of Osmosis’s allBTC supply after a validation gap in cross-chain message handling, a category closer to logic and access-control review than a single detector, which is why pairing static analysis with invariant fuzzing (Step 12) matters for bridge-style contracts specifically.

OpenZeppelin’s research team has kept publishing on exactly these categories throughout the year, including an entry in its ongoing bug-digest series covering timestamp manipulation, reentrancy failures, and vault-style fund lockups, published in late September 2026 on the OpenZeppelin news blog. The pattern across these writeups and the CertiK and Immunefi loss data is consistent: the bug classes that dominate real losses are the same handful of classes static analyzers have targeted for years. The gap isn’t tooling awareness, it’s teams skipping the scan under deadline pressure.

Where the Smart Contract Weakness Classification registry fits in

The SWC Registry catalogs smart contract weakness classes with an ID scheme modeled on the CWE system used in traditional software security. It’s worth keeping open in a second tab while triaging Slither output, since mapping a detector name like reentrancy-eth or tx-origin to its corresponding SWC entry gives you a stable reference to link in audit reports, internal wikis, or pull request descriptions instead of relying on tool-specific naming that might change between versions.

Slither detectorSWC IDCommon name
reentrancy-eth / reentrancy-no-ethSWC-107Reentrancy
tx-originSWC-115Authorization through tx.origin
unchecked-lowlevelSWC-104Unchecked call return value
arbitrary-send-ethSWC-105Unprotected ether withdrawal
unprotected-upgradeSWC-112 / SWC-124Unprotected proxy upgrade
timestampSWC-116Block timestamp manipulation

Keep in mind that the registry documents weakness categories rather than tracking live tool versions, so treat the ID as a stable label for the bug class, not a guarantee that Slither’s specific detector logic matches every edge case in the SWC write-up word for word.

Building this into a team workflow

A scan a developer runs manually before pushing gets skipped under deadline pressure, which is exactly why Step 11’s CI job matters more than the local install. Beyond gating pull requests, larger teams typically add three more habits: a weekly full-repo scan across all contracts (not just the diff) to catch drift as dependencies update, a shared slither.config.json committed to the repo so nobody’s local excludes silently diverge, and a rule that any suppressed finding needs a one-line justification comment referencing either an SWC ID or a linked issue. None of these add meaningful overhead once they’re set up, and together they turn a one-off tutorial exercise into a durable part of how a team ships contracts.

It also helps to decide upfront who owns triage. On a small team, that’s usually whoever’s merging the PR. On a larger one, it’s worth designating a rotating “security reviewer” role so findings don’t get rubber-stamped by whoever’s fastest to hit approve. Pair that with a short runbook, maybe half a page, describing how to read a Slither report, which detectors your team has agreed to always block on, and where the suppression log lives. New hires ramp up faster with that document than they do by reverse-engineering tribal knowledge from old pull request comments, and it keeps the bar consistent even as the team grows past the point where everyone remembers every past incident.

Frequently asked questions

Is Slither free to use?
Yes. Slither is open source under an AGPL-3.0 license and free for both personal and commercial use, maintained by Trail of Bits.

Does Slither support Vyper as well as Solidity?
Yes, per the project’s own description, Slither analyzes both Solidity and Vyper contracts, though the detector suite is more mature for Solidity given its larger install base.

Can Slither replace a professional audit before mainnet deployment?
No. Static analysis catches known code patterns quickly and cheaply, but it doesn’t evaluate economic design, oracle dependencies, or governance risk the way a manual audit from a firm like Trail of Bits, OpenZeppelin, or Consensys Diligence does. Use it as a pre-audit filter, not a substitute.

How long does a typical Slither scan take?
For a small to mid-sized project like the demo vault in this tutorial, scans complete in a few seconds. Larger codebases with dozens of contracts and heavy inheritance can take longer, though still typically under a minute.

What’s the difference between Slither and Mythril?
Slither uses static AST-based pattern matching and runs in seconds. Mythril uses symbolic execution to explore possible execution paths and can take minutes to hours on complex contracts, but it can sometimes catch conditional logic bugs static analysis misses.

Why did my Slither scan report zero findings even though the code has a bug?
Static analyzers only catch patterns their detectors are built to recognize. A logic bug specific to your protocol’s math or a flawed access-control design that doesn’t match a known pattern can slip through. That’s why Step 12 pairs static analysis with fuzz-based invariant testing.

Should I run Slither against test files too?
Generally no. Excluding test/ in your filter_paths config keeps output focused on production code, since test helpers often intentionally use unsafe patterns that don’t matter in a testing context.

Does Slither work with Hardhat projects, or only Foundry?
Both. Slither auto-detects the framework in use. The workflow in this tutorial uses Foundry, but the same slither . command and detector suite work identically against a Hardhat-based repository.

Do I need to fix every finding before merging code?
No. Treat High-impact findings as blocking, review Medium findings case by case, and let Low and Informational findings queue up as normal refactor work. Trying to zero out every category usually just trains a team to suppress findings reflexively instead of reading them.

Can Slither analyze contracts that inherit from OpenZeppelin libraries?
Yes, and it will by default, since inherited code is part of the compiled contract. Most teams exclude the lib/ path from findings specifically to avoid re-flagging issues in dependencies they can’t directly patch, while still analyzing how their own contract uses those inherited functions.