Skip to content
forked from patuuh/ChainScope

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

ChainScope

License Focus Interface Scope

ChainScope is a local-first code graph for blockchain and protocol security research.

It indexes repositories into SQLite knowledge graphs and gives humans and AI agents fast structural queries over:

  • call paths
  • state readers and writers
  • trust boundaries
  • state transitions
  • sink reachability
  • ranked hotspots
  • research-vs-production provenance

Why use it

High-severity protocol bugs rarely live in one function. They emerge across:

  • public entry points
  • internal call chains
  • external calls
  • admin and upgrade surfaces
  • state mutations
  • helper scripts, PoCs, and fuzz harnesses

ChainScope gives you that structural map before you start deep manual reading.

It is also built for agentic research. Instead of forcing an agent to read a large repository linearly, ChainScope lets it ask high-signal questions first:

  • What are the riskiest functions?
  • Who writes balances?
  • Is there a path from deposit to delegatecall?
  • Which functions cross trust boundaries?
  • Which findings come from production code vs research scaffolding?

That means more context goes to exploitability and impact, and less to rebuilding repository structure from scratch.

Who it is for

Audience What ChainScope helps with
Protocol security researchers Fast graph-backed triage and path tracing
Bug bounty hunters Exploit-surface-first target selection
Auditors Structural navigation through large contract repos
AI-agent workflows High-signal code relations without linear reading
Protocol/backend engineers Trust-boundary and state-flow investigation

Field notes

Signal Note
Role Map, not researcher
Best use Graph-backed targeting, not verdicts
Speed Finds risky intersections fast
Output quality Good for hypothesis generation
Limitation Manual exploitability still required

At a glance

Area What ChainScope gives you
Triage Workspace profiling and exploit-surface-first target selection
Graphing Functions, state vars, calls, reads/writes, transitions, sinks
Discovery Hotspots, DeFi patterns, unsafe backend patterns
Tracing Paths, state access, cross-boundary calls, state machines
Provenance Research-mode indexing plus production-only query scope
Interfaces MCP server for agents and CLI wrappers for local workflows

Best fit

ChainScope is strongest on:

  • smart contract repos
  • multi-repo blockchain workspaces
  • protocol backends, keepers, relayers, indexers, and node code
  • mixed Solidity/Rust/Go/Java/Python/TypeScript blockchain systems
  • cross-chain messaging and bridge-style repos

Current language and ecosystem coverage includes:

  • Solidity
  • Vyper
  • Move
  • Clarity
  • TON
  • Cairo
  • Sway
  • Rust
  • Go
  • Java
  • Python
  • TypeScript / JavaScript
  • C / C++
  • protobuf
  • Stellar XDR

Installation

Python environment

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

MCP setup

The exported MCP server name is chainscope:

{
  "mcpServers": {
    "chainscope": {
      "command": "/opt/ChainScope/run_mcp.sh",
      "args": []
    }
  }
}

Sandbox image

docker build -f Dockerfile.sandbox -t chainscope-sandbox .

The Dockerfile copies the project to /opt/ChainScope/.

Working with agents

When Codex or Claude starts in a blockchain repo with the ChainScope MCP config loaded, the tools are available natively. Useful prompts include:

  • Profile this blockchain folder and recommend which subrepos to build first
  • Build the knowledge graph for this repo, then show me the attack surface
  • Trace who writes to balances
  • Path from deposit to the delegatecall
  • Tell me everything about deposit()
  • Run all ChainScope tools and output a summarized report

Typical tool flow:

  1. cs_profile to choose the right target
  2. cs_build to create the graph
  3. cs_summary to confirm graph health, build metadata, and scope
  4. cs_audit or cs_hotspots to rank the attack surface
  5. cs_paths, cs_trace, cs_cross_summary, cs_cross, and cs_state to validate the structure

graph.db is written to the current working directory unless you pass --db. After .mcp.json changes, start a fresh Codex session so the tool list reloads. In Claude Code, /mcp is the quickest way to confirm that the server is connected.

Sandbox workflow

  1. Copy .mcp.json into the target project folder.
  2. From that project folder, run docker sandbox run --template chainscope-sandbox claude.

The custom image includes:

  • Python plus the runtime dependencies from Dockerfile.sandbox
  • tree-sitter parsers for the supported languages
  • the ChainScope source tree at /opt/ChainScope/

Rebuild the image after local ChainScope changes:

docker build -f Dockerfile.sandbox -t chainscope-sandbox .

Workflow

cs_profile
   ->
pick target repo or package
   ->
cs_build
   ->
cs_summary
   ->
cs_hotspots / cs_audit
   ->
cs_paths / cs_trace / cs_cross_summary / cs_cross / cs_state
   ->
manual source review
   ->
PoC or report

Quick start

Profile a repo or workspace first:

python cs_profile.py /path/to/workspace --strategy bounty --json

Why:

  • --strategy bounty prioritizes exploit surface over raw repository size
  • build plans come back with per-target graph DB paths
  • you avoid indexing a giant workspace blindly

Build a graph:

python cs_build.py /path/to/repo --db graph.db

MCP cs_profile, cs_build, and graph query tools do not self-timeout by default. If you want a time-limited partial build or query, pass timeout_seconds; otherwise long workspace profiles, graph builds, and broad graph reads continue until the client or host stops them. MCP cs_profile also caps large output sections with max_output_items; set max_output_items=0 only when an exhaustive workspace inventory is intentional. MCP cs_build caps extractor failure_examples with max_failure_examples; set max_failure_examples=0 only when the full stored failure sample is needed. MCP responses are compact JSON by default so agents spend context on graph signal rather than indentation whitespace. CLI wrappers still pretty-print JSON when their --json output mode is used.

If a build is time-limited or partial, ChainScope prioritizes production protocol roots such as src/, contracts/, programs/, pallets/, and crates/ before lower-signal operational or config code. This keeps partial graphs useful for agent triage.

Query it:

python cs_summary.py --db graph.db
python cs_paths.py --db graph.db --from deposit --to withdraw
python cs_trace.py --db graph.db --var balances
python cs_cross.py --db graph.db --summary
python cs_cross.py --db graph.db --external-calls
python cs_state.py --db graph.db --all

For agents, the usual loop is:

  1. cs_profile to choose the right subrepo
  2. cs_build to create the graph
  3. cs_summary via MCP to confirm the DB is populated and scoped correctly
  4. cs_hotspots or cs_audit via MCP to identify promising surfaces
  5. cs_paths, cs_trace, cs_cross_summary, cs_cross, and cs_state to validate structure
  6. direct source reading only where the graph indicates it matters

For common function names, prefer qualified cs_lookup queries such as Vault.deposit or TokenMessaging.send. Broad lookups are capped by default so agents get candidates instead of an oversized response. max_matches caps full function profiles and max_candidates caps ambiguous candidate lists; set either value to 0 only when exhaustive output is intentional. Individual lookup relation lists such as callers, callees, state reads/writes, and other edges are capped by max_relation_items; set max_relation_items=0 only for exhaustive profiles. Capped relation totals use edge-only counts before fetching shown node details. Large parsed metadata blobs are compacted by max_metadata_bytes; set max_metadata_bytes=0 only when the full raw metadata is needed. Relation edge attributes are capped by max_attribute_bytes=2048; set max_attribute_bytes=0 only when full call-site or edge attributes are needed. Hidden relation rows are counted but do not format attributes after the relation cap is reached. Shared ambiguous-match helpers also count hidden candidate rows without materializing preview dictionaries after candidate caps.

cs_paths also caps ambiguous endpoint matches, endpoint candidate lists, and returned paths. Use qualified endpoint names first; set max_endpoint_matches=0, max_endpoint_candidates=0, or max_paths=0 only when exhaustive path search is intentional. Bounded path search streams tuple paths directly into the final capped result instead of building a separate per-endpoint-pair path list. Returned path label disambiguation batches label counts for the capped result instead of issuing one count query per path node. Optional guard and state annotations are capped per path node by max_guards_per_node=20 and max_state_access_per_node=25; set either to 0 only when exhaustive annotations are needed.

The same rule applies to broad cs_trace variable queries. Names like total, owner, or balance are capped by default and return compact candidates when ambiguous. max_matches caps fully traced variables and max_candidates caps the candidate list; set either value to 0 only for exhaustive trace output. Writer and reader lists are capped independently by max_accessors_per_relation; set it to 0 only when exhaustive accessor output is intentional. Single-variable capped traces use indexed distinct counts plus limited accessor rows instead of scanning every accessor. When using show_callers, caller lists are capped by max_callers_per_accessor; capped caller totals use edge-only counts before fetching shown caller details. Full parsed variable metadata is omitted by default; set include_metadata=true only when that raw metadata is needed. Included variable metadata is capped by max_metadata_bytes=4096; set max_metadata_bytes=0 only when full variable metadata is needed.

cs_summary caps source-context counters by max_source_contexts=20. Set max_source_contexts=0 only when every custom provenance bucket is needed. cs_summary --attack-surface is also a bounded overview. Its _summary reports the total entry points, how many were shown, and whether the list was truncated; increase top when you need a broader entry-point inventory. All-source attack-surface summaries fetch and format function metadata only for retained top rows.

Scanner category output from cs_defi and cs_unsafe is capped by max_per_category=25 by default. The summary still reports full category totals; set max_per_category=0 only when an exhaustive category dump is intentional. Per-finding scanner detail lists such as risks, sinks, operations, and unchecked_calls are capped by max_detail_items=10; set max_detail_items=0 only when exhaustive detector detail is needed. Narrow category scans use SQL metadata prefilters. Broad all-category scans stream function rows and index top-level metadata keys once, avoiding wide metadata LIKE ... OR ... chains that can be slower on large graphs.

cs_audit is a top-N overview. Its _summary reports full section totals and which sections were truncated so agents can drill down with specialized tools instead of treating the overview as exhaustive. Raw attack-surface metadata is omitted by default for MCP context; set include_metadata=true only when that raw JSON is needed. Included attack-surface metadata is capped by max_metadata_bytes=4096; set max_metadata_bytes=0 only when full metadata is needed. Source-context counters are capped by max_source_contexts=20; set max_source_contexts=0 only when every provenance bucket is needed. Detailed dead-code rows are also omitted by default; set include_dead_code_details=true when dead-code investigation is the current task. Append-style audit sections such as access gaps and detailed dead code count hidden rows without building their detail payloads after top is reached.

Broad cs_hotspots scans use one streaming function-row pass that retains only scoreable candidates before guard counting, use indexed edge aggregates for state-write and external-call counts, and screen metadata with cached top-level key sets instead of wide SQL metadata LIKE ... OR ... chains. This keeps normal broad scans usable on larger graphs while preserving exact scoring and false-positive rejection. Write-count and external-call aggregate helpers use the source/relation edge index directly and fall back only when older graph DBs lack it. Traversal relation scans, guard-label lookups, and writable-entry guard counts use composite relation indexes the same way, so audit-style broad scans avoid avoidable edge-table walks. Older graph DBs that still have single-column edge indexes use those directly instead of first attempting missing composite indexes and retrying after an SQLite error. Preferred/fallback edge-index groups are resolved with one schema lookup so broad MCP scans do not repeatedly probe sqlite_master. The core graph API used by local workflows also filters traversal, reverse-call, accessor, guard, and attack-surface edge queries through composite indexes, with direct single-column index fallback for older graph DBs. Local graph path formatting batches node-label loads and ambiguity counts for capped paths. MCP broad scans also cache repeated raw metadata probes so common fields such as source_context, is_sink, and sink_type do not require repeated JSON scans. Top-level metadata key sets are cached too, while oversized metadata blobs are scanned uncached so long-lived MCP servers do not retain them. Repeated detector key groups are cached as sets for broad audit and scanner passes. Follow-up tools such as cs_cross, cs_sinks, cs_paths, cs_trace, and cs_lookup use composite edge indexes when present, but fall back cleanly for older graph DBs that were built before those indexes existed, including direct use of older source/target edge indexes for relation-filtered follow-up queries. Reachability follow-ups such as cs_cross(from_func=...) and cs_sinks batch node cache lookups per frontier instead of issuing one node query per traversed edge.

Broad cs_state output is also capped by entity groups, transitions per entity, warnings, and summary entity total counters. Raw transition metadata is retained only for transitions that can be shown after max_transitions_per_entity. All-source broad state scans fetch function metadata only for shown transitions; exclude_research=true keeps metadata in the stream because filtering needs it. Prefer entity= when investigating one state machine, or set the state caps to 0 when you intentionally need every transition or entity total.

For large graphs, prefer cs_cross_summary before broad cs_cross. It returns totals, top source files, top targets, and bounded sample calls so agents can choose where to inspect without dumping every trust-boundary edge. Sample calls are capped by top; source-context, source-file, and target counters are capped by max_counter_items. Broad cross-boundary discovery uses the edge relation index when available and falls back cleanly for older graph DBs. cs_cross_summary(from_func=...) also streams the reachable boundary calls directly instead of expanding an exhaustive raw cs_cross result first. Raw cs_cross output is capped by max_results=50 by default; ambiguous from_func candidates are capped by max_start_candidates and require a more qualified name instead of silently picking the first match. Set max_results=0 only when an exhaustive edge list is intentional. Cross-boundary call attribute payloads are capped by max_attribute_bytes=2048; set max_attribute_bytes=0 only when full call attributes are needed. Broad raw rows omit source and target graph IDs by default; set include_node_ids=true when exact graph IDs are needed. Hidden raw cross rows do not format source context after max_results is reached; cs_cross_summary still formats contexts for all rows because its counters depend on them.

Broad cs_sinks output is capped by sink count and reachable callers per sink (max_results=50, max_callers_per_sink=10 by default). The response still reports total sinks, type counts, and caller truncation. Full parsed sink metadata is omitted by default for MCP context; set include_metadata=true only when the raw sink metadata is needed. Raw sink metadata is not retained in default sink buffers, and hidden sink rows do not format source context after the sink cap is reached. Included sink metadata is capped by max_metadata_bytes=4096 and formatted only for shown sinks; set max_metadata_bytes=0 only when full sink metadata is needed. Caller rows omit verbose fields by default; set include_caller_details=true when caller signatures and line_end are needed. Set max_results=0 or max_callers_per_sink=0 only when exhaustive sink expansion is intentional.

Query surface

MCP tools

  • cs_profile
  • cs_build
  • cs_summary
  • cs_hotspots
  • cs_audit
  • cs_defi
  • cs_unsafe
  • cs_paths
  • cs_trace
  • cs_cross_summary
  • cs_cross
  • cs_sinks
  • cs_state
  • cs_lookup

CLI wrappers

  • python cs_profile.py ...
  • python cs_profile.py --max-output-items 0 ...
  • python cs_build.py ...
  • python cs_build.py --json ...
  • python cs_build.py --max-failure-examples 0 --json ...
  • python cs_summary.py ...
  • python cs_summary.py --max-source-contexts 0 --json ...
  • python cs_summary.py --attack-surface --top 10 ...
  • python cs_paths.py ...
  • python cs_paths.py --max-paths 0 --max-endpoint-matches 0 ...
  • python cs_paths.py --max-endpoint-candidates 0 ...
  • python cs_paths.py --show-guards --max-guards-per-node 0 ...
  • python cs_paths.py --show-state --max-state-access-per-node 0 ...
  • python cs_trace.py ...
  • python cs_trace.py --max-matches 0 ...
  • python cs_trace.py --max-candidates 0 ...
  • python cs_trace.py --max-accessors-per-relation 0 ...
  • python cs_trace.py --show-callers --max-callers-per-accessor 0 ...
  • python cs_trace.py --include-metadata --json ...
  • python cs_trace.py --include-metadata --max-metadata-bytes 0 --json ...
  • python cs_cross.py --summary ...
  • python cs_cross.py --summary --max-counter-items 0 ...
  • python cs_cross.py ...
  • python cs_cross.py --max-results 0 ...
  • python cs_cross.py --max-attribute-bytes 0 ...
  • python cs_cross.py --include-node-ids --json ...
  • python cs_cross.py --max-start-candidates 0 ...
  • python cs_state.py ...
  • python cs_state.py --max-entities 0 --max-transitions-per-entity 0 --max-warnings 0 ...
  • python cs_state.py --max-entity-totals 0 --json ...
  • python cs_sinks.py ...
  • python cs_sinks.py --max-results 0 --max-callers-per-sink 0 ...
  • python cs_sinks.py --include-metadata --max-metadata-bytes 0 --json ...
  • python cs_sinks.py --include-metadata --include-caller-details --json ...

Research mode

By default, ChainScope stays production-first and skips low-signal or research-only paths such as:

  • scripts/
  • poc/
  • fuzz/
  • invariant/
  • certora/
  • echidna/

If you want those artifacts included:

python cs_profile.py /path/to/repo --include-research --json
python cs_build.py /path/to/repo --include-research

Mixed graphs preserve provenance with source_context tags such as:

  • production
  • script
  • poc
  • fuzz
  • invariant

Production-only querying

When you build a mixed graph, you can still query just production code:

python cs_summary.py --db graph.db --exclude-research
python cs_paths.py --db graph.db --from start --to finish --exclude-research
python cs_trace.py --db graph.db --var total --exclude-research
python cs_cross.py --db graph.db --summary --exclude-research
python cs_cross.py --db graph.db --external-calls --exclude-research
python cs_state.py --db graph.db --all --exclude-research
python cs_sinks.py --db graph.db --type self_destruct --exclude-research

The MCP server exposes the same scope control through exclude_research=true.

Limitations

ChainScope is high-signal, but it is not a verdict engine.

Keep these limits in mind:

  • exploitability still requires manual verification
  • live state, balances, roles, and deployment wiring are outside pure static structure
  • business-logic intent is not inferred reliably from graph shape alone
  • some findings are intentionally noisy because they are meant to prioritize investigation, not replace it
  • it is not yet a strong general web application security platform

ChainScope does not currently model:

  • HTTP routes and middleware stacks
  • session and cookie flows
  • CSRF and browser-side auth semantics
  • template rendering and XSS sinks
  • file upload pipelines
  • framework-specific ORM behavior
  • cloud/IAM/runtime policy boundaries

You can still use it on backend code, and it already catches useful cross-language patterns such as command execution, deserialization, weak crypto, SQL injection, unsafe blocks, and race-like behavior. But the highest-signal heuristics are still blockchain-first.

Sample output

cs_profile

{
  "workspace_mode": true,
  "strategy": "bounty",
  "recommended_clusters": [
    {
      "name": "bridges-and-messaging",
      "reason": "high trust-boundary density and externally reachable execution"
    }
  ],
  "build_plan": [
    {
      "label": "gmx-synthetics",
      "tool_call": {
        "tool": "cs_build",
        "repo_path": "/path/to/repo",
        "db": "graphs/gmx-synthetics.db"
      }
    }
  ]
}

cs_trace

{
  "query_scope": "production_only",
  "variable_matches": 1,
  "variables": [
    {
      "variable": "balances",
      "writers": [
        "Vault.deposit",
        "Vault.withdraw"
      ],
      "readers": [
        "Vault.previewWithdraw",
        "Vault.totalAssets"
      ]
    }
  ]
}

These outputs are intentionally structural. They tell you where to look next, not whether something is exploitable.

Contributing

Contributions are welcome, especially in:

  • parser quality for blockchain ecosystems
  • protocol semantics and higher-signal heuristics
  • CLI and MCP parity
  • test fixtures for real protocol patterns

Start with CONTRIBUTING.md.

Roadmap

Near-term expansion areas:

  • richer protocol semantics for roles, upgrades, assets, and config surfaces
  • broader parser-grade support for additional blockchain ecosystems
  • stronger runtime and fork-aware workflows for validating live exploit paths
  • better backend/web framework understanding beyond protocol-heavy repos
  • more publish-ready CLI parity for every MCP query surface

Verification

The current exported copy was verified with:

pytest -q

Result at the time of this README update:

565 passed

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages