DEV Community

Cover image for Per-file scanners said PASS. A contract graph said FAIL.
Alex
Alex

Posted on Originally published at alexar76.github.io

Per-file scanners said PASS. A contract graph said FAIL.

Dark touchstone desk with PASS REVIEW FAIL stamps beside a signed assurance pack

We kept calling different things an audit.

A tier-1 firm PDF. Slither in CI. A red team probe. An on-chain stake with a score. Hub admission. They are not interchangeable — and collapsing them is how you ship a critical with a clean conscience.

We just shipped BASANOS (βάσανος — the Lydian touchstone): a signed Assurance Pack for ecosystem Solidity at a pinned commit. One capability, one question:

Is this contract tree sound enough to ship — at this digest?

Not insurance. Not runtime red team. Not catalogue admission. Technical assurance with Ed25519 receipts and honest limits on every pack.


Stop collapsing the layers

Table of security layers — THEMIS admission, MOMUS runtime, BASANOS assurance, AgentAuditPool insurance

Node Question Layer
THEMIS Let this agent into the Hub catalogue? Procurement admission
MOMUS Is the live federation holey? Runtime red team
BASANOS Is this Solidity sound at commit X? Technical assurance pack
AgentAuditPool Who staked USDC and published a scoreBps? On-chain insurance

Assurance packs inform coverage decisions. AgentAuditPool enforces economic consequences.

BASANOS never emits scoreBps and never calls coverListing. If someone tells you assurance is insurance, they are selling confusion.


The measurement that hurt

Split view — per-file PASS with floating pragma warnings vs cross-contract graph FAIL

BASANOS runs two layers:

Per file — 17 detectors on one contract at a time: CEI patterns, access modifiers, delegatecall, raw ecrecover, spot-price reads, allow-lists that are written but never enforced.

Whole treebasanos/xcontract.py builds a contract graph: who may write each storage slot, who reads whose state across contracts.

Here is the story that forced the second layer.

A hand audit of our own ACEX contracts found two exploitable criticals:

  1. The same USDC pledged to a lending pool and to a bond series across two contracts.
  2. A default whose beneficiary could trigger it.

We ran BASANOS on that exact tree before the graph layer existed. Result: eight findings, all pragma.floating, pack verdict PASS.

Both bugs were correct in every single file. Only the graph was wrong.

Today the same vulnerable tree reads FAIL with root causes named. The fixed tree reads REVIEW — we do not hand out silent PASS on shapes nobody has checked.

The graph layer does not prove the invariant. It tells you which invariant to check and where both ends live. Every pack says so in basanos/limits.py.


One invoke, root id not path

Terminal curling BASANOS fixtures scan with signed FAIL pack

Capability: agent.security.contract-assurance@v1 · live UI at basanos.modelmarket.dev · landing at alexar76.github.io/basanos

git clone https://github.com/alexar76/basanos
cd basanos
uv sync --extra dev
uv run pytest -q          # 94 tests · ~95% coverage
uv run python agent.py    # http://127.0.0.1:9470/ui/
Enter fullscreen mode Exit fullscreen mode

On a fresh clone you can hit the deliberately-vulnerable corpus:

curl -sS http://127.0.0.1:9470/invoke \
  -H 'content-type: application/json' \
  -d '{"product_id":"basanos","capability_id":"agent.security.contract-assurance@v1","input":{"roots":["fixtures"]}}'
Enter fullscreen mode Exit fullscreen mode

You name a root id, never a filesystem path — that is what stops /invoke from becoming a local-file read primitive.

Root id Resolves to Standalone clone
fixtures, sound BASANOS test corpus yes
acex, lottery, core, zk Monorepo contract trees monorepo checkout only

fixtures should come back FAIL with real findings. Asking for acex from a standalone clone returns NO_SUBJECT — no Solidity was read, so the pack judges nothing. That is not a clean bill and not an accusation.

Verdicts: PASS · REVIEW · FAIL · NO_SUBJECT.

Optional intel (BASANOS_THREAT_INTEL=1): OSV + GitHub advisory allowlist only. Advisory text can reorder detectors. It cannot invent a new one.


What happens on FAIL (and what never happens)

Honest limits — not tier-1 PDF, not insurance, recommend-only playbook

On FAIL or REVIEW, the pack includes a recommend-only playbook aligned with ISO/IEC 29147 disclosure, ISO/IEC 30111 handling, and NIST SP 800-61 containment:

  • stop shipping that digest
  • verify privately
  • patch
  • re-scan
  • disclose after the fix or a 90-day CVD window

BASANOS will not pause a live contract. It will not publish an exploit. It will not replace a human who decides to stake USDC on a listing.

ACEX timeline — per-file PASS, hand audit criticals, graph FAIL


What BASANOS is not

  • Not a tier-1 firm audit PDF — say machine assurance pack
  • Not Slither or Foundry forge test — complementary, not a drop-in replacement
  • Not a formal prover — it finds the shape of a cross-contract flaw, not a proof
  • Not HEPHAESTUS — forge.modelmarket.dev is the smith; BASANOS is the stone you scrape the alloy on

Crypto stays off the hero. On-chain insurance is a separate act humans opt into after reading the pack.


Try it

git clone https://github.com/alexar76/basanos
cd basanos && uv sync --extra dev && uv run python agent.py
Enter fullscreen mode Exit fullscreen mode

If you have ever shipped a green CI scan and still felt uneasy about cross-contract state — this is the layer we were missing. A star helps; a reproducible FAIL on fixtures helps more.

Top comments (0)