Skip to main content
Glama
Baneado98

solidity-sentinel

by Baneado98
README.md
# solidity-sentinel πŸ›‘οΈ

**Static security audit for Solidity smart contracts β€” for AI agents and developers.**

`solidity-sentinel` is an [MCP](https://modelcontextprotocol.io) server **and** a pay-per-call [x402](https://x402.org) HTTP API. Paste a contract's `source` (or the raw **deployed bytecode** of an unverified contract) and get a **CRITICAL / HIGH-RISK / REVIEW / LOW-RISK / CLEAN** verdict with a **0–100 risk score** and, in the deep tier, a full finding list β€” each with its exact line, the **sourceβ†’sink dataflow**, a code-evidence snippet, the SWC + CWE id, a confidence score, an exploit explanation, and a concrete fix.

[![MCP](https://img.shields.io/badge/MCP-server-blue)](https://modelcontextprotocol.io) [![x402](https://img.shields.io/badge/x402-pay--per--call-ff7a9c)](https://x402.org) [![license](https://img.shields.io/badge/license-MIT-green)](./LICENSE)

> ⚠️ **Heuristic static screen, not a formal audit.** It pattern-matches source structure and dataflow; it cannot prove a contract safe and can miss novel bugs. Absence of a finding is **not** a guarantee. Always pair with tests, fuzzing and a manual review before risking funds.

## Why it exists (the moat)

An AI agent asked to "check this contract for bugs" makes **one pass** and produces a few regex-grade observations. A real review is the *accumulated depth* of an auditor working through the whole surface. solidity-sentinel encodes that depth:

- **A flow-sensitive taint/dataflow engine (CFG-based).** It tracks attacker-controlled values (function params, `msg.sender`, external-call returns) into dangerous sinks (delegatecall/call targets, `selfdestruct`, transfer amounts, array indices, `sstore` slots). It does **not** walk statements linearly β€” it builds a **control-flow graph** and runs a forward dataflow with a **JOIN at branch confluences** (a value tainted on *any* path is tainted after the merge), **loop fixpoints** (taint a loop body creates reaches code after the loop), and **revert/early-return guards** (`if (msg.sender != owner) revert();` makes the fall-through path trusted). A guard on *one* branch does **not** sanitise the merge. This catches `if (g) { t = userInput; } else { t = safe; } … t.delegatecall(d)` and stays silent on owner-guarded / immutable-target code β€” killing both false negatives and false positives.
- **Multi-level interprocedural + cross-contract.** Taint follows internal call chains **several hops deep** (`entry β†’ mid β†’ inner β†’ sink`) and **across contracts in the same project** (`router.run(tainted)` into another contract's delegatecall), recording the full call/contract chain in the evidence.
- **High-value DeFi families** where the money is: flash-loan-manipulable accounting, **ERC-4626 first-depositor / share inflation**, **read-only reentrancy** (stale view price during a transfer), **callback-based reentrancy** (ERC-777/1363 + flash-loan callbacks), **MEV / sandwich** exposure (no slippage bound), **TWAP-window manipulation**, unsafe liquidation, governance/timelock bypass, EIP-2612 permit replay, spot-price oracles.
- **Deployed-bytecode screening** for **unverified** contracts (no source): it disassembles runtime bytecode and flags `DELEGATECALL`/`SELFDESTRUCT` capabilities and **calldata-controlled delegatecall** takeover at the EVM level β€” ground truth a misleading source comment can't hide.
- The classics, done right: a tokenizer that isn't fooled by strings/comments, a parser that knows each function's visibility/mutability/modifiers, **CEI ordering dataflow**, **interprocedural** reentrancy, and **version-aware** arithmetic.

The engine ships with **227 automated tests**, including a **153-case calibration corpus** modelled on real incidents (TheDAO, Parity, bZx, Cream, Euler, Beanstalk, Nomad, Lendf.me, **Wormhole, Ronin, Multichain, Mango, Rari, Visor, Qubit, BadgerDAO, KyberSwap, Sentiment, Penpie, Sushi RouteProcessor**…) and audited-clean contracts. Measured on that corpus: **100% recall, 0 false positives** β€” every must-flag fires, and safe CEI code, guarded reentrancy, immutable-target / beacon-proxy delegatecall, revert-guarded sinks, bounded indices, complete EIP-712 permits, slippage-bounded swaps, windowed TWAPs and `.call` text inside comments/strings are *not* flagged. A **coverage drift-guard test** asserts every detector id the engine fires is documented in the SWC/severity/confidence map β€” the docs can't silently fall out of sync with the code.

## What it analyses (taint engine + 12 detector families)

| Family | Examples it catches |
|---|---|
| 🌊 **Dataflow / taint (flow-sensitive)** | attacker-controlled value β†’ **delegatecall/call target**, `selfdestruct` beneficiary, transfer amount, array index, `sstore` slot β€” **CFG-based with branch JOINs, loop fixpoints, revert-guards, multi-level interprocedural and cross-contract** propagation, with guard/sanitiser suppression |
| πŸ” **Reentrancy** | classic ETH reentrancy (CEI violated), **cross-function** reentrancy on shared state, **read-only reentrancy** (stale view price), **callback-based** (ERC-777/1363 + flash-loan callbacks), **ERC-777/721/1155 receiver-hook** reentrancy (no raw `.call` needed), guard-aware suppression |
| πŸ” **Access control** | unprotected `selfdestruct`, unprotected/attacker-targeted `delegatecall` (immutable/beacon-target aware), privileged mutators (`mint`/`withdraw`/`upgrade`/`setFee`…) and **infinite-mint / arbitrary-credit** shapes with no `onlyOwner`/role check, **unprotected initializer** (proxy takeover) |
| 🏦 **DeFi families** | **flash-loan-manipulable accounting**, **ERC-4626 first-depositor inflation**, **MEV / sandwich** (no slippage bound), **TWAP-window manipulation**, unsafe **liquidation** (no health check), **governance without timelock**, EIP-2612 **permit** missing deadline/nonce |
| βž• **Arithmetic** | pre-0.8 overflow/underflow without SafeMath, `unchecked{}` blocks, **division-before-multiply** precision loss, unsafe downcasts, strict balance equality |
| ☎️ **Low-level calls** | `tx.origin` auth (phishable), **unchecked** call/send return, `.send()` 2300-gas trap, **arbitrary external call** with caller-controlled target |
| 🎲 **Randomness** | weak randomness from `block.timestamp`/`prevrandao`/`blockhash`, timestamp-dependent logic |
| πŸ“‰ **Oracle** | **spot-price** (getReserves/`balanceOf`) used as a price feed (flash-loan manipulable), Chainlink usage without **staleness** checks, deprecated `latestAnswer()` |
| ✍️ **Signatures** | `ecrecover` without `address(0)` check, **signature replay** (no nonce / no chainId), malleability, `abi.encodePacked` hash collision |
| ⬆️ **Proxy / upgradeability** | constructor-init in upgradeable contracts, **UUPS without `_authorizeUpgrade`**, risky inline assembly |
| 🧱 **DoS** | unbounded loops, **push-payment** loops a single revert can brick, O(n) array shifting |
| βœ… **ERC-compliance & gas** | `approve` race, raw ERC-20 transfer (no SafeERC20), missing zero-address checks, floating/outdated pragma, `public`β†’`external` gas, deprecated constructs |
| 🧬 **Deployed bytecode** | disassembles runtime bytecode of **unverified** contracts; flags `DELEGATECALL`/`SELFDESTRUCT` and **calldata-controlled delegatecall** takeover |

Each finding is mapped to its [SWC](https://swcregistry.io/) and CWE id. **SWC coverage** (documented in `detectors/coverage.ts` and asserted in sync by `coverage.test.ts`): SWC-100, 101, 102, 103, 104, 105, 106, 107, 112, 114, 115, 116, 117, 118, 120, 121, 128, 132, 133 β€” **plus the DeFi/dataflow families that have no SWC number** (oracle manipulation, flash-loan accounting, ERC-4626 inflation, read-only reentrancy, MEV/sandwich, TWAP-window, governance timelock). Severity and confidence are calibrated per detector and further adjusted at runtime by path guards, interprocedural depth and cross-contract distance.

## Tiers

| | Free | Deep (paid) |
|---|---|---|
| Detectors | 4 high-signal | **flow-sensitive taint engine + all 16 families + bytecode mode** |
| Output | verdict + risk score + finding counts + top issue titles | every finding: line, the **source→sink flow**, evidence, SWC/CWE, confidence, exploit reasoning, **fix**, + per-contract surface inventory |
| Runs | locally in the installed package | **server-side, behind payment** |
| Cost | free (rate-limited) | $0.05 USDC per call via x402, **or** a prepaid API key (card) |

The deep engine **never runs in the locally-installed package** β€” `deep=true` is forwarded to the hosted `/pro/run` endpoint, gated by x402 or a prepaid key. The premium engine (the taint/dataflow CFG, the DeFi families, the full detector suite, the calibration corpus) is **not shipped in the npm package at all**: the `files` allowlist publishes only the free client, and the deep path is a lazy `import()` of modules that exist solely on the server. `npm pack` contains zero premium source β€” the moat cannot be extracted from the installed package.

## Use as an MCP server

```jsonc
{
  "mcpServers": {
    "solidity-sentinel": {
      "command": "npx",
      "args": ["-y", "solidity-sentinel-mcp"],
      // optional β€” unlocks the deep audit with a prepaid key:
      "env": { "SENTINEL_KEY": "<your-key>" }
    }
  }
}
```

Tool: **`audit_contract`** β€” `{ source: string, deep?: boolean }`.

- `deep` omitted/false β†’ free verdict (runs locally).
- `deep: true` with a `SENTINEL_KEY` β†’ full deep audit (server-side).
- `deep: true` without a key β†’ you still get the free verdict plus how to unlock the deep tier.

## Use as an HTTP API

```bash
# Free verdict
curl -s -X POST https://solidity-sentinel.vercel.app/run \
  -H 'content-type: application/json' \
  -d '{"source":"pragma solidity ^0.8.0; contract C { ... }"}'

# Deep audit β€” pay per call with x402 (USDC on Base), or a prepaid key:
curl -s -X POST https://solidity-sentinel.vercel.app/pro/run \
  -H 'authorization: Bearer <your-key>' \
  -H 'content-type: application/json' \
  -d '{"source":"..."}'
```

A request to `/pro/run` without payment returns **402** with both payment lanes (x402 challenge + a Stripe checkout link). Buy a prepaid card key at `/pro/checkout`.

## Develop

```bash
npm install
npm run build        # tsc + sync landing
npm test             # 227 tests: engine + 153-case CVE/hack calibration corpus + taint/CFG/bytecode units + coverage drift-guard
npm run dev:http     # local server (FORCE_LISTEN); /run, /pro/run, /mcp
```

## License

MIT β€” see [LICENSE](./LICENSE).

TDQS

A4.6/5.0

Scored across 1 tool

Disambiguation5/5

Only one tool exists, so there is no risk of confusion. The tool's purpose is clearly distinct from any other potential tool.

Naming Consistency5/5

The single tool name follows a clear verb_noun pattern (audit_contract), which is consistent and descriptive. No inconsistencies to evaluate.

Tool Count3/5

With only one tool, the server is on the thin side. However, the tool is comprehensive, encapsulating both free and premium audits via parameters, which somewhat justifies the low count.

Completeness4/5

The tool covers a wide range of security detectors and audit modes (source code, bytecode, deep analysis). Minor gaps like gas analysis or integration checks are absent but not critical for the core audit purpose.

Maintenance

ActivityMaintained
ResponsivenessSyncing