Methodology
The published scoring rubric — deterministic first, AI second, dissent shown.
Sigil's scorer is pure, versioned, and public. The same input always produces the same verdict. This page is the rubric that ships in @sigil/core — RUBRIC_VERSION = "1.0.0".
Bands
Risk score = sum of triggered rule weights, capped at 100.
| band | range |
|---|---|
| PASS | score < 30 |
| WARN | 30 ≤ score < 70 |
| BLOCK | score ≥ 70 |
Any hard finding forces BLOCK and sets riskScore = max(rawSum, 90), capped at 100. The ambiguous band is [35, 65] — only there (or in explicit mode: "deep") may the AI panel convene.
The rule table (verbatim from the shipped engine)
| rule id | weight | hard |
|---|---|---|
COUNTERPARTY_SANCTIONED | 100 | hard |
COUNTERPARTY_FLAGGED_MALICIOUS | 100 | hard |
APPROVAL_ALL_TO_EOA | 100 | hard |
TOKEN_HONEYPOT | 100 | hard |
APPROVAL_UNLIMITED_UNVERIFIED | 45 | soft |
TOKEN_SELL_TAX_EXTREME (≥ 2500 bps) | 40 | soft |
RECIPIENT_FRESH_WALLET | 30 | soft |
APPROVAL_UNLIMITED (verified spender) | 25 | soft |
COUNTERPARTY_MIXER | 25 | soft |
SIM_REVERTED | 20 | soft |
TOKEN_SELL_TAX_HIGH (≥ 1000 bps) | 20 | soft |
TOKEN_MINTABLE_ACTIVE_OWNER | 15 | soft |
TOKEN_BLACKLIST_FUNCTION | 15 | soft |
TOKEN_HOLDER_CONCENTRATION (≥ 30%) | 15 | soft |
TOKEN_LOW_LIQUIDITY (< $10k) | 15 | soft |
COUNTERPARTY_UNVERIFIED_CONTRACT | 10 | soft |
TOKEN_UPGRADEABLE | 5 | soft |
Findings are sorted by severity (critical, high, medium, low, info) in every verdict, each carrying evidence strings naming the sources that produced it.
Why deterministic-first
~95% of calls never touch an LLM. That is a trust feature, not a cost hack:
- Reproducibility — anyone can re-run the rubric on the same inputs and get the same verdict. An LLM cannot promise that.
- Auditability — every weight above is public; a verdict is explainable as a sum, not a vibe.
- Adversarial resistance — prompt injection in calldata or token metadata cannot argue a deterministic rule out of firing.
The panel: escalate-only, dissent shown
Inside the ambiguous band, three differentiated adjudicators — a conservative auditor, a pragmatic operator, and an adversarial red-teamer — vote PASS/WARN/BLOCK using only the evidence the engine already produced (inventing facts is forbidden). Majority may escalate the verdict; it can never loosen one, and hard BLOCKs are untouchable.
Every vote — including disagreeing ones — ships verbatim in verdict.panel.votes / verdict.panel.dissents. Single-model tools hide uncertainty behind one confident answer; Sigil shows you the disagreement. If the panel times out, loses quorum, or hits its daily cost cap, the verdict degrades to the deterministic result and coverage.unavailable records panel.
Bypass-class defenses
Naive firewalls are beaten in known ways; Sigil closes them explicitly:
- Real simulation, honestly labelled. With an X Layer RPC configured, Sigil runs the actual transaction (
eth_call+ a native-balance state override) so a honeypot/paused/blacklisted token that reverts on transfer is caught — the verdict'ssimulation.methodreads"simulated". Without an RPC it degrades to"derived"(read from calldata) and says so; static output is never labelled as executed. - A failed sim is never a silent PASS. A transaction that actually reverted in simulation can never be reported PASS — it is floored to at least WARN. Silence is not safety.
- Casing normalization. Every hex/address is normalized (a
0X…prefix can't slip past astartsWith("0x")check) and all address comparisons are case-insensitive. - Off-chain signatures.
eth_signTypedDatanever appears as calldata, yet a malicious EIP-2612Permitor Permit2PermitSingleis how modern drainers take an allowance. PasstypedDataand Sigil screens the signing request through the identical scorer before you sign.
Honest coverage
A failed data source never fakes an answer and never aborts a verdict. It is recorded in coverage.unavailable (and gap-tagged inside sources), so the caller always knows what was actually checked. Degradation semantics are part of the contract — see the Reference.