Slither for Starknet — a production-grade static security analyzer for Cairo smart contracts.
Cairo smart contracts on Starknet manage real assets. A single vulnerability — an unprotected upgrade, a reentrancy path, a missing nonce check — can drain millions. Manual audits are slow, expensive, and don't scale.
Starknet has no Slither. Until now.
Shadowhare scans compiled Sierra artifacts (.sierra.json / .contract_class.json) and runs 71 security detectors using CFG analysis, taint tracking, and dataflow engines — the same techniques behind Slither, but purpose-built for Cairo's Sierra IR.
No source code needed. Point it at compiled artifacts and get results in seconds.
- 71 security detectors across 4 severity tiers (21 High / 26 Medium / 16 Low / 8 Info)
- Deep analysis — CFG construction, dominator trees, taint propagation, call graph analysis
- Zero source required — works directly on compiled
.sierra.jsonand.contract_class.json - CI/CD ready — SARIF 2.1.0 output, baseline diffing, deterministic exit codes
- Scarb integration —
scarb shadowhare detectjust works - Upgrade safety —
detect-diffcompares two contract versions and flags regressions - Parallel execution — detectors run concurrently via Rayon
- External plugins — extend with custom detectors via a simple JSON protocol
- Tested against production contracts — 0 high-severity false positives on Argent, OpenZeppelin, AVNU, and 30+ real mainnet contracts
cargo install shadowharegit clone https://github.com/Shadowhare/shadow-hare.git
cd shadowhare
cargo install --path .Shadowhare analyzes compiled Sierra artifacts, not Cairo source code. You must compile your contracts before scanning.
# If using Scarb (recommended)
scarb build
# Artifacts land in ./target/dev/*.contract_class.json
# If using the Cairo compiler directly
starknet-compile my_contract.cairo > my_contract.sierra.json# Scan a contract
shdr detect ./target/dev/my_contract.contract_class.json
# Scan an entire project directory
shdr detect ./target/dev/
# Include info-level findings
shdr detect ./target/dev/ --min-severity info
# Output SARIF for CI pipelines
shdr detect ./target/dev/ --format sarif
# List all 71 detectors
shdr list-detectors# Build your project first
scarb build
# Run shadowhare as a Scarb subcommand
scarb shadowhare detect
scarb shdr detect$ shdr detect ./satoru/target/dev/ --min-severity medium
shadowhare — satoru_Bank.contract_class.json, satoru_Config.contract_class.json, ...
────────────────────────────────────────────────────────────
[HIGH] Incomplete account interface surface
Detector: account_interface_compliance
Confidence: high
Function: <program>
File: satoru_MockAccount.contract_class.json
Account-like function set detected, but interface compliance check
failed: missing core account methods: __execute__, __validate__,
is_valid_signature, supports_interface.
Fingerprint: f7bb2a7bdc27a760
[MEDIUM] felt252 arithmetic without range check
Detector: felt252_overflow
Confidence: low
Function: satoru::config::config::Config::constructor
File: satoru_Config.contract_class.json
Function 'Config::constructor': felt252_add at stmt 1569 performs
felt252 arithmetic on user-controlled input without a proven range
check. felt252 wraps silently modulo the field prime.
Fingerprint: 623285b52ec8368f
────────────────────────────────────────────────────────────
Summary: 0 critical, 1 high, 3 medium, 0 low, 0 info
$ shdr detect ./piltover/target/dev/ --min-severity medium
shadowhare — piltover.sierra.json, piltover_appchain.contract_class.json, ...
────────────────────────────────────────────────────────────
No findings.
────────────────────────────────────────────────────────────
Summary: 0 critical, 0 high, 0 medium, 0 low, 0 info
Every shdr detect run produces a structured report. This section explains every part of it so you can read, triage, and act on findings with confidence.
A human-format report has four sections, in order:
shadowhare — <artifact names> ← Header
──────────────────────────────────────── ← Divider
[SEVERITY] <title> ← Finding (repeated)
Detector: ...
...
──────────────────────────────────────── ← Divider
Summary: 0 critical, 1 high, ... ← Summary
Compatibility: ... ← Compatibility info
Warnings: ... ← Warnings (if any)
If there are no findings, the report shows No findings. between the dividers.
Every finding contains these fields:
[HIGH] Storage write without caller check ← Severity + Title
Detector: write_without_caller_check ← Detector ID
Confidence: low ← Confidence level
Function: MyContract::__wrapper__set_value ← Function where issue lives
File: my_contract.contract_class.json ← Artifact file
Sierra stmt: 4397 ← Statement index in Sierra IR
Source: src/lib.cairo:42:5 ← Source location (if debug info exists)
Function 'set_value': writes to storage (first ← Detailed description
write at stmt 4397) without get_caller_address... explaining the vulnerability
Fingerprint: 09a859223861e02b ← Unique identifier for this finding
Here is what each field means:
Severity + Title — The colored tag ([HIGH], [MEDIUM], etc.) and a short human-readable summary of the issue. This is the first thing you read.
Detector — The unique ID of the detector that fired (e.g. write_without_caller_check). Use this to look up detailed documentation in docs/RULES.md, or to suppress it in config:
shdr detect ./target/dev/ --exclude write_without_caller_checkConfidence — How likely this is a true positive vs. a false positive. See Confidence levels below.
Function — The fully-qualified Sierra function name where the issue was found. Compiler-generated wrapper functions start with __wrapper__ or __external__ — these are serde wrappers the compiler emits around your actual functions.
File — The artifact path that was analyzed.
Sierra stmt — The index into the Sierra program's flat statement array where the issue occurs. This is the Sierra-level "line number". See Sierra statement index below.
Source — The original Cairo source location (file:line:col), shown only if the artifact contains debug info. Scarb builds include this by default; standalone compilation may not.
Description — A detailed explanation of what the detector found and why it matters. Often includes the specific operation (e.g. felt252_add at stmt 1569) and what is missing (e.g. "without a proven range check").
Fingerprint — A stable hex identifier for this exact finding at this exact location. See Fingerprints below.
Shadowhare assigns one of five severity levels to each detector. The severity is fixed per detector — every firing of reentrancy is always HIGH.
| Level | Color | Meaning | Action |
|---|---|---|---|
| Critical | Red bold | Exploitable vulnerability with direct fund loss | Fix immediately. Block deployment. |
| High | Red | Serious vulnerability that could lead to loss of funds, unauthorized access, or contract takeover | Fix before mainnet. Investigate every instance. |
| Medium | Yellow | Logic flaw, missing validation, or unsafe pattern that could be exploited under specific conditions | Fix before audit. Acceptable to baseline if mitigated elsewhere. |
| Low | Cyan | Best-practice violation, missing event, or code quality issue that doesn't directly threaten funds | Fix when convenient. Good for code hygiene. |
| Info | Dimmed | Informational observation — dead code, magic numbers, complexity metrics | Optional. Use --min-severity low to hide these (default). |
By default, --min-severity low hides Info findings. Use --min-severity info to see everything.
When piping to CI systems via SARIF, severities map to SARIF levels and CVSS-like scores:
| Shadowhare | SARIF level | Security severity score |
|---|---|---|
| Critical | error |
9.8 |
| High | error |
7.5 |
| Medium | warning |
5.0 |
| Low | note |
2.5 |
| Info | note |
0.0 |
Confidence reflects how likely the finding is a true positive. Each detector has a fixed confidence level.
| Level | Meaning | Typical false-positive rate |
|---|---|---|
| High | Strong evidence from multiple analysis passes (CFG + taint + call graph). Very likely a real issue. | < 5% |
| Medium | Good evidence from structural analysis. Usually correct but may fire on safe patterns the analyzer cannot fully resolve. | 5–20% |
| Low | Pattern match or heuristic. Flags suspicious code that may be intentional or mitigated elsewhere (e.g. in a caller function). | 20–50% |
Not all findings deserve equal attention. Use this priority matrix:
| Confidence: High | Confidence: Medium | Confidence: Low | |
|---|---|---|---|
| Critical/High severity | P0 — fix now. Likely exploitable. | P1 — investigate. Read the description, check if mitigated. | P2 — review. Check manually; may be a false positive but the impact if real is severe. |
| Medium severity | P1 — fix before audit. | P2 — review when possible. | P3 — baseline if mitigated. |
| Low/Info severity | P3 — fix for hygiene. | P4 — nice to fix. | P4 — informational. |
A HIGH-severity, LOW-confidence finding (like write_without_caller_check with confidence: low) means: "if this is real, it's dangerous — but check manually because the analyzer isn't certain." This commonly fires on functions that enforce access control through an internal helper call that shadowhare can't trace across function boundaries yet.
Sierra is Cairo's intermediate representation — a flat list of typed statements that the Starknet sequencer JITs into CASM. When shadowhare reports Sierra stmt: 4397, that means statement #4397 in the program's linear instruction array.
What a statement is: Each Sierra statement is either:
- An Invocation — a call to a library function (
libfunc) likefelt252_add,storage_write,call_contract_syscall, etc., with input variables and output branches - A Return — returning variables from the current function
How to use it:
- If debug info is present, shadowhare maps the statement back to source (shown as
Source: src/lib.cairo:42:5) - If no debug info, use
shdr print ir-dump <artifact>to see the raw IR and find statement 4397 manually - The statement index is deterministic for a given compilation — it won't change unless you recompile
Why it matters: The statement index pinpoints exactly where in the compiled code the vulnerable operation occurs. Two findings in the same function at different statements are distinct issues.
Every finding has a fingerprint like 09a859223861e02b. This is the first 8 bytes (16 hex chars) of a SHA-256 hash computed from:
sha256("{detector_id}:{function_name}:{statement_idx}:{file_path}")
Fingerprints serve three purposes:
-
Baseline tracking — When you run
shdr update-baseline, all current fingerprints are saved. On the next run with--baseline, only findings with new fingerprints (not in the baseline) are flagged. This lets CI pass on known issues while catching regressions. -
Suppression — You can suppress a specific finding by its fingerprint in
Scarb.toml:[[tool.shadowhare.suppress]] id = "write_without_caller_check" location_hash = "09a859223861e02b"
-
Diff tracking —
detect-diffuses fingerprints to classify findings as new, resolved, or unchanged between two contract versions.
Fingerprints are stable across runs as long as the artifact doesn't change. Recompiling the contract may shift statement indices, which changes fingerprints.
After the summary, the report shows compatibility information for each artifact:
Compatibility:
- my_contract.contract_class.json: tier=Tier1 source=compiler_version
- old_contract.contract_class.json: tier=Tier3 source=contract_class_version
(degraded: contract_class_version='0.1.0' cannot be mapped to a semver)
What the tiers mean for your results:
| Tier | Cairo version | What it means |
|---|---|---|
| Tier 1 | ~2.16 (current) |
Full support. All 71 detectors run. Results are CI-quality — you can trust them to block deployments. |
| Tier 2 | ~2.15 (previous stable) |
Full detector support. Fully tested. Minor IR differences from Tier 1 are handled. |
| Tier 3 | ~2.14 and older 2.x |
Best-effort. Most detectors run but some may skip if the IR structure is too different. Results are directionally correct but review manually. |
| Unsupported | Cairo 1.x | Rejected entirely. Shadowhare only analyzes Sierra IR from Cairo 2.x+. |
Metadata source tells you how shadowhare determined the tier:
compiler_version— extracted fromdebug_info.compiler_version(most reliable)sierra_version— extracted fromsierra_versionfieldcontract_class_version— extracted fromcontract_class_versionfield (least specific)unavailable— no version metadata found (defaults to Tier 3)
When you see (degraded: ...) — the version string was found but couldn't be parsed into a semver range, so shadowhare fell back to Tier 3 best-effort mode. This is common with older artifacts.
The --strict flag makes shadowhare exit with code 2 if any artifact has degraded compatibility. Use this in CI when you want to guarantee Tier 1/2 analysis quality.
Summary: 0 critical, 1 high, 3 medium, 0 low, 0 info
This counts findings after filtering by --min-severity, --exclude, suppressions, and baseline. It reflects exactly what was shown in the report.
The process exit code is derived from the summary:
- Exit 0 — zero findings (or zero new findings if
--fail-on-new-only) - Exit 1 — one or more findings at or above the severity threshold
- Exit 2 — runtime error (bad file path, parse failure,
--strictwith degraded tier)
Warnings:
⚠ contract_class_version='0.1.0' cannot be mapped to a semver
⚠ detector 'pragma_stale_price' skipped: requires Tier2, artifact is Tier3
Warnings are non-fatal issues that didn't prevent analysis but may affect result quality. Common warnings:
- Compatibility degradation — version couldn't be determined precisely
- Detector skipped — a detector's minimum tier requirement wasn't met
- Plugin failure — an external plugin crashed or returned invalid output
- Missing debug info — a source-aware detector couldn't run without debug info
Shadowhare supports three output formats via --format:
human (default) — Colored terminal output as described above. Best for interactive use.
json — Machine-readable JSON with schema version 1.0.0:
{
"schema_version": "1.0.0",
"generated_at": 1710000000,
"analyzer_version": "1.0.0",
"sources": ["my_contract.contract_class.json"],
"artifacts": [{
"source": "my_contract.contract_class.json",
"compatibility_tier": "Tier1",
"metadata_source": "compiler_version",
"degraded_reason": null
}],
"findings": [{
"detector_id": "reentrancy",
"severity": "high",
"confidence": "high",
"title": "Reentrancy vulnerability",
"description": "Function 'withdraw': storage read at stmt 100...",
"location": {
"file": "my_contract.contract_class.json",
"function": "MyContract::withdraw",
"statement_idx": 100,
"line": 42,
"col": 5
},
"fingerprint": "a1b2c3d4e5f6a7b8"
}],
"warnings": [{"kind": "compatibility", "message": "..."}],
"summary": {
"total": 1, "critical": 0, "high": 1,
"medium": 0, "low": 0, "info": 0
}
}Use JSON when you want to programmatically parse results, feed into dashboards, or integrate with custom tooling.
sarif — SARIF 2.1.0 for GitHub Code Scanning and other SARIF-compatible tools. Upload directly to GitHub:
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifSARIF output includes security-severity scores (CVSS-style) and precision levels so GitHub's security tab can sort and filter findings correctly.
shdr detect-diff compares two contract versions and classifies every finding:
Shadowhare Diff Report
Left sources:
- ./v1/target/dev/my_contract.contract_class.json
Right sources:
- ./v2/target/dev/my_contract.contract_class.json
Summary: new=2 resolved=1 unchanged=5
New by severity: critical=0 high=1 medium=1 low=0 info=0
Resolved by severity: critical=0 high=0 medium=0 low=1 info=0
New findings:
- [HIGH] reentrancy (Reentrancy vulnerability)
at my_contract.contract_class.json :: MyContract::withdraw :: stmt 200
fp=a1b2c3d4e5f6a7b8
Resolved findings:
- [LOW] missing_event_emission (State change without event emission)
at my_contract.contract_class.json :: MyContract::set_owner :: stmt 50
fp=f0e1d2c3b4a59687
Unchanged fingerprints:
- 1122334455667788
- aabbccddeeff0011
...
| Category | Meaning |
|---|---|
| New | Finding exists in v2 but not in v1. This is a regression — the upgrade introduced a new issue. |
| Resolved | Finding existed in v1 but not in v2. The upgrade fixed this issue. |
| Unchanged | Finding exists in both versions (same fingerprint). Pre-existing, not caused by the upgrade. |
Use --fail-on-new-severity high to make CI fail only when the upgrade introduces new HIGH+ findings:
shdr detect-diff --left ./v1/target/dev/ --right ./v2/target/dev/ \
--fail-on-new-severity high
# Exit 0 if no new high/critical findings, exit 1 otherwiseWhen you run with --baseline .shadowhare-baseline.json --fail-on-new-only, the report only shows findings whose fingerprints are not in the baseline file. This means:
- Shown findings = newly introduced since the baseline was created
- Hidden findings = known issues already captured in the baseline
The summary reflects only the shown (new) findings. Exit code 1 triggers only if there are new findings.
To refresh the baseline after you've triaged or fixed findings:
shdr update-baseline ./target/dev/ --baseline .shadowhare-baseline.jsonThe baseline file is simple JSON — an array of fingerprint strings:
{
"schema_version": "1.0.0",
"fingerprints": ["09a859223861e02b", "11862d120c9d7d4d", "..."]
}Beyond security detection, shdr print provides 7 structural analysis printers:
| Printer | Command | What it shows |
|---|---|---|
| summary | shdr print summary <PATH> |
Per-artifact overview: function counts (external, view, L1 handler, constructor, internal), total statements, call graph edge count, compatibility tier |
| callgraph | shdr print callgraph <PATH> |
Function call graph. Use --format dot to generate Graphviz output: shdr print callgraph <PATH> --format dot | dot -Tpng -o callgraph.png |
| attack-surface | shdr print attack-surface <PATH> |
Maps every entrypoint to reachable sinks (storage writes, external calls, library calls, L2→L1 messages, upgrades, deploys). Shows what an attacker can reach from each external function. |
| storage-layout | shdr print storage-layout <PATH> |
Storage slot analysis — what state variables exist and how they're organized |
| data-dependence | shdr print data-dependence <PATH> |
Variable data flow and dependency chains through the program |
| function-signatures | shdr print function-signatures <PATH> |
Function parameter and return types for every function |
| ir-dump | shdr print ir-dump <PATH> |
Raw Sierra IR dump — every statement with index, useful for manually inspecting Sierra stmt references from findings |
All printers support --format human (default) and --format json (schema version 1.0). The callgraph printer additionally supports --format dot for Graphviz.
You can extend shadowhare with custom detectors via the --plugin flag:
shdr detect ./target/dev/ --plugin ./my-custom-detectorProtocol: Shadowhare invokes your plugin as <executable> <artifact_path>. Your plugin must:
- Read the Sierra artifact from the given path
- Run its analysis
- Print JSON to stdout (exit 0 on success, non-zero on failure)
Output format — any of these:
[{"detector_id": "my_check", "severity": "high", "confidence": "medium",
"title": "Issue found", "description": "Details...",
"location": {"file": "contract.json", "function": "fn_name",
"statement_idx": 42, "line": null, "col": null}}]or wrapped: {"findings": [...]}
Plugin findings go through the same filtering pipeline (severity threshold, suppressions, exclude list) as built-in detectors.
┌─────────────────────────────────┐
│ Sierra Artifacts │
│ .sierra.json .contract_class │
└────────────────┬────────────────┘
│
┌───────────────▼───────────────┐
│ Loader │
│ SierraLoader + VersionNeg. │
│ Contract class enrichment │
└───────────────┬───────────────┘
│
┌───────────────▼───────────────┐
│ Internal IR │
│ ProgramIR · TypeRegistry │
│ FunctionClassifier · OZ │
│ component detection │
└───────────────┬───────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌─────────▼─────────┐ ┌─────────▼─────────┐ ┌─────────▼─────────┐
│ CFG Engine │ │ Taint Engine │ │ Call Graph │
│ 2-phase build │ │ RPO worklist │ │ Function │
│ dominator tree │ │ source→sink │ │ summaries │
│ natural loops │ │ sanitizer-aware │ │ reachability │
└─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
┌───────────────▼───────────────┐
│ 71 Detectors (parallel) │
│ 21 High · 26 Med · 16 Low │
│ 8 Info · deterministic order │
└───────────────┬───────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌─────────▼───────┐ ┌─────────▼───────┐ ┌─────────▼───────┐
│ Human │ │ JSON │ │ SARIF │
│ terminal report│ │ versioned API │ │ 2.1.0 for CI │
└─────────────────┘ └─────────────────┘ └─────────────────┘
| Detector | Description |
|---|---|
reentrancy |
Storage read → external call → storage write pattern |
unprotected_upgrade |
replace_class_syscall without owner check |
unchecked_l1_handler |
L1 handler missing from_address validation |
controlled_library_call |
Library call with user-controlled class hash |
signature_replay |
Signature verification without nonce check |
arbitrary_token_transfer |
Token transfer with attacker-controlled parameters |
write_without_caller_check |
Storage write in external function without caller validation |
oracle_price_manipulation |
External call result stored without validation |
deploy_syscall_tainted_class_hash |
Deploy with user-controlled class hash |
initializer_replay_or_missing_guard |
Initializer without one-time guard |
missing_nonce_validation |
__execute__ without nonce increment |
account_interface_compliance |
Incomplete SRC6 account interface |
account_validate_forbidden_syscalls |
Side-effectful syscalls in validation |
account_execute_missing_v0_block |
Missing tx-version guard in __execute__ |
unchecked_ecrecover |
EC recovery without validation |
rtlo |
Right-to-left override character injection |
u256_underflow |
Unchecked u256 subtraction |
l1_handler_payload_to_storage |
L1 payload written to storage without sanitization |
l1_handler_unchecked_selector |
L1 handler without selector validation |
l2_to_l1_tainted_destination |
L2→L1 message with tainted destination |
l2_to_l1_unverified_amount |
L2→L1 message with unverified amount |
| Detector | Description |
|---|---|
felt252_overflow |
felt252 arithmetic without range check |
unchecked_integer_overflow |
Integer arithmetic with silent overflow discard |
integer_truncation |
u256→felt252 without high-word bounds check |
unchecked_address_cast |
Fallible address cast with unhandled failure |
unchecked_array_access |
Array access without bounds checking |
tx_origin_auth |
Authentication using transaction origin |
divide_before_multiply |
Division before multiplication (precision loss) |
weak_prng |
Weak pseudo-random number generation |
hardcoded_address |
Hardcoded contract/wallet addresses |
block_timestamp_dependence |
Block timestamp used for critical logic |
unchecked_transfer |
Token transfer without return value check |
tainted_storage_key |
User-controlled storage key |
gas_griefing |
Unbounded loop callable by external users |
view_state_modification |
View function modifying state |
uninitialized_storage_read |
Reading storage before initialization |
multiple_external_calls |
Multiple external calls in single function |
tautological_compare |
Always-true/false comparison |
tautology |
Tautological expression |
l1_handler_unchecked_amount |
L1 handler with unchecked amount |
l2_to_l1_double_send |
Duplicate L2→L1 messages |
pyth_* (3) |
Pyth oracle misuse patterns |
pragma_* (3) |
Pragma oracle misuse patterns |
| Detector | Description |
|---|---|
missing_event_emission |
State-modifying function emits no event |
missing_events_access_control |
Privileged state mutation with no event |
missing_events_arithmetic |
Arithmetic-driven storage update with no event |
missing_pausable |
No pause mechanism in state-modifying contract |
missing_zero_address_check |
Address input used without zero-address validation |
incorrect_erc20_interface |
Incomplete or inconsistent ERC20 interface |
incorrect_erc721_interface |
Incomplete or inconsistent ERC721 interface |
event_before_state_change |
Event emitted before the state change it describes |
calls_loop |
External/library call inside a loop body |
write_after_write |
Redundant storage writes without intervening read |
reentrancy_events |
Event emitted between external call and storage write |
unused_return |
Function return value silently discarded |
unchecked_l1_message |
L1 message sent without caller verification |
shadowing_builtin |
Entrypoint name collides with Cairo builtin |
shadowing_local |
Adjacent symbol segments suggest local name shadowing |
shadowing_state |
Function name collides with state/type name |
| Detector | Description |
|---|---|
dead_code |
Function never called and not an entry point |
magic_numbers |
Large numeric literal without a named constant |
costly_loop |
Storage access inside a loop |
cache_array_length |
Array length repeatedly queried inside a loop |
boolean_equality |
Boolean compared to literal true/false |
unindexed_event |
Event uses empty keys/index set |
unused_state |
Storage value loaded but never used |
excessive_function_complexity |
High cyclomatic complexity — hard to test and review |
Full detector documentation with examples: docs/RULES.md
# Core commands
shdr detect <PATH...> [options] # Run security analysis
shdr detect-diff --left <V1> --right <V2> # Compare two versions
shdr print <PRINTER> <PATH...> # Structural analysis
shdr update-baseline <PATH...> # Snapshot current findings
shdr list-detectors # Show all detectors| Flag | Description |
|---|---|
-v, --verbose |
Enable debug-level logging (RUST_LOG=shadowhare=debug) |
-q, --quiet |
Suppress all output except errors (RUST_LOG=shadowhare=error) |
--no-color |
Disable colored output (also respects NO_COLOR env var) |
-V, --version |
Print shadowhare version and exit |
| Flag | Description | Default |
|---|---|---|
--format <human|json|sarif> |
Output format | human |
--min-severity <info|low|medium|high> |
Severity threshold | low |
--detectors <id1,id2,...> |
Run only these detectors | all |
--exclude <id1,id2,...> |
Skip these detectors | none |
--baseline <path> |
Baseline file for diffing | none |
--fail-on-new-only |
Exit 1 only for new findings | off |
--strict |
Fail on degraded analysis | off |
--manifest <Scarb.toml> |
Read project config | auto-discover |
--plugin <exe> |
External detector plugin (repeatable for multiple plugins) | none |
| Code | Meaning |
|---|---|
0 |
No findings (or no new findings with --fail-on-new-only) |
1 |
Findings detected |
2 |
Runtime error |
| Flag | Description | Default |
|---|---|---|
--left <PATH...> |
Left-side (old version) artifacts | required |
--right <PATH...> |
Right-side (new version) artifacts | required |
--format <human|json> |
Output format | human |
--min-severity <info|low|medium|high> |
Severity threshold | low |
--detectors <id1,id2,...> |
Run only these detectors | all |
--exclude <id1,id2,...> |
Skip these detectors | none |
--fail-on-new-severity <level> |
Exit 1 only if new findings reach this severity | any new finding |
--strict |
Fail on degraded analysis | off |
--manifest <Scarb.toml> |
Read project config | auto-discover |
--plugin <exe> |
External detector plugin | none |
shdr print <PRINTER> <PATH...> [--format <human|json|dot>]| Printer | What it shows |
|---|---|
summary |
Per-artifact overview: function counts, statement count, call graph edges, compatibility tier |
callgraph |
Function call graph. Supports --format dot for Graphviz output |
attack-surface |
Entrypoint-to-sink reachability map (storage writes, external calls, library calls, L2→L1 messages, upgrades, deploys) |
storage-layout |
Storage slot analysis — state variables and their organization |
data-dependence |
Variable data flow and dependency chains |
function-signatures |
Parameter and return types for every function |
ir-dump |
Raw Sierra IR — every statement with its index, useful for tracing Sierra stmt references |
| Flag | Description | Default |
|---|---|---|
--format <human|json|dot> |
Output format (dot only for callgraph) |
human |
shdr update-baseline <PATH...> [--baseline <FILE>]Runs all detectors on the given artifacts and saves every finding's fingerprint to the baseline file. On subsequent detect runs with --baseline, only findings not in this file trigger exit code 1.
| Flag | Description | Default |
|---|---|---|
--baseline <path> |
Path to baseline file to create/overwrite | .shadowhare-baseline.json |
shdr list-detectorsPrints a table of all 71 built-in detectors with four columns:
| Column | Description |
|---|---|
ID |
Unique detector identifier (use with --detectors or --exclude) |
SEVERITY |
Fixed severity level: critical, high, medium, low, or info |
CONFIDENCE |
Fixed confidence level: high, medium, or low |
Description |
One-line explanation of what the detector checks |
- name: Build Cairo contracts
run: scarb build
- name: Security scan
run: |
cargo install shadowhare
shdr detect ./target/dev/ --format sarif --min-severity medium \
> results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif# Create baseline from current state
shdr update-baseline ./target/dev/ --baseline .shadowhare-baseline.json
# In CI: only fail on NEW findings
shdr detect ./target/dev/ --baseline .shadowhare-baseline.json --fail-on-new-only# Compare v1 vs v2 — fail if new high-severity findings appear
shdr detect-diff --left ./v1/target/dev/ --right ./v2/target/dev/ \
--fail-on-new-severity high[tool.shadowhare]
detectors = ["all"]
exclude = ["dead_code"]
severity_threshold = "medium"
baseline = ".shadowhare-baseline.json"
strict = false
plugins = ["./target/debug/my-plugin"]
[[tool.shadowhare.suppress]]
id = "reentrancy"
location_hash = "a1b2c3d4" # optional: omit to suppress all from this detectorCLI flags always override Scarb.toml values.
| Cairo Compiler | Support Tier | Detector coverage | CI-safe? |
|---|---|---|---|
~2.16 |
Tier 1 (full support) | All 71 detectors | Yes |
~2.15 |
Tier 2 (supported) | All 71 detectors | Yes |
~2.14 and older 2.x |
Tier 3 (best-effort) | Most detectors; some skip if IR differs too much | Review manually |
| Cairo 1.x | Unsupported | None — rejected at load time | N/A |
Artifacts without version metadata are analyzed in Tier 3 best-effort mode with a warning. Use --strict to reject degraded artifacts in CI.
Prerequisites: Rust 1.75+
git clone https://github.com/Shadowhare/shadow-hare.git
cd shadowhare
cargo build --release
# Run tests
cargo test
# Run clippy
cargo clippy --all-targets -- -D warnings
Licensed under either of:
at your option.