Skip to content

Latest commit

 

History

History

README.md

kotlin — Smart Contract Security Auditor

Point ARCP at a Solidity contract; it parses, fans out 8 vulnerability-class checkers in parallel via Kotlin Flow + structured concurrency, runs analyzer hits through a small LLM for triage, and emits a severity-ranked PDF + SARIF audit bundle. The audit's cost ceiling is set on the lease, not discovered after the fact.

Showcases: flatMapMerge for per-vulnerability-class fanout, per-child lease grants as IAM-for-agents (tool.call allowlists scoped per check), cross-language tool calls (Slither/Mythril in a Python sidecar), JSON-schema-constrained triage, SARIF + PDF artifacts.

Architecture

Four containers:

ollama  ◀──http──  arcp-runtime  ──tool.call──▶  analyzer-pool  (Slither/Mythril)
                        ▲
                        │ ws/arcp
                   arcp-client (Clikt + Mordant)

The runtime registers a parent contract.audit agent plus 8 children (contract.check_reentrancy, contract.check_integer_overflow, …, contract.check_tx_origin). Each child receives a narrowed lease — a reentrancy check can't accidentally invoke the oracle-manipulation analyzer.

Quickstart

cp .env.example .env
make up                                 # ollama + analyzer-pool + runtime + client
make audit FILE=samples/Reentrant.sol   # runs the deliberately-vulnerable sample
make audit FILE=samples/Vault-safe.sol  # returns 0 surviving findings

Configure

Variable Default Effect
DEFAULT_COMPILER 0.8.20 Solidity compiler version passed to solidity.parse_ast.
DEFAULT_PARALLELISM 4 flatMapMerge concurrency across the 8 vuln-class children.
SEVERITY_FLOOR LOW Discard analyzer hits below this severity before triage.
AUDIT_BUDGET_USD 1.50 Parent cost.budget lease grant.
PER_CLASS_BUDGET_USD 0.10 Each child's cost.budget grant.
TRIAGE_PARALLELISM 4 Concurrent triage calls in the finding-level flatMapMerge.
ANALYZER_POOL_URL http://analyzer-pool:9300 Where the runtime POSTs /analyze.
ANALYZER_CACHE_DIR /cache/analyzers sha256-keyed analyzer result cache (in the pool container).
ARCP_SDK_VERSION latest Resolved via Gradle's + dynamic version.

Where to add code

  • src/main/kotlin/com/example/arcp/agents/ContractAudit.kt — parent fan-out, triage, artifacts.
  • src/main/kotlin/com/example/arcp/agents/Check*.kt — one file per vulnerability class.
  • src/main/kotlin/dev/arcp/example/audit/ — in-process JVM tools (SolidityAstParser, CallGraphBuilder, SeverityMap, EvmDisassembler).
  • src/main/kotlin/dev/arcp/example/audit/AnalyzerPool.kt — HTTP client for the Slither/Mythril sidecar.
  • analyzer-pool/pool.py — Flask app exposing POST /analyze (slither.*, mythril.simple).
  • samples/Reentrant.sol (vulnerable DAO clone), Vault-safe.sol (uses a nonReentrant modifier).

Tests

Fast in-process smoke tests — no docker, no Slither, no Ollama:

./gradlew --no-daemon test

Coverage:

  • AST parser surfaces the vulnerable withdraw function in Reentrant.sol.
  • CheckReentrancy returns one finding when wired to a stub analyzer-pool response.
  • ContractAudit.leaseGrantsFor("reentrancy") matches the PROMPT §5 grant set exactly.
  • Triage step filters FALSE_POSITIVE verdicts out.
  • SarifBundle emits valid SARIF 2.1.0 JSON with one result for the reentrant sample.

Stubs and where this differs from the published SDK

The PROMPT references high-level agent-author APIs (@ArcpAgent, JobContext, LeaseRequest, etc.) that are not yet in dev.arcp:arcp. The example carries a thin local mirror in src/main/kotlin/dev/arcp/example/stubs/ — every type there is marked with a TODO: replace with real SDK API when published comment. The agent code in com/example/arcp/agents/ uses the same names and signatures the PROMPT specifies, so swapping in the real SDK is an import-only change.

Verify install only

make verify