Skip to content

Latest commit

 

History

History
892 lines (687 loc) · 26.7 KB

File metadata and controls

892 lines (687 loc) · 26.7 KB

ChainProof Documentation

Smart Contract Audit Copilot — real-time vulnerability scanner, gas advisor, and audit report generator for Solidity.


Table of Contents


Overview

ChainProof helps developers catch smart contract vulnerabilities early — in the editor, terminal, and CI pipeline — without waiting weeks or paying tens of thousands for a full audit.

Problem ChainProof response
$1.8B+ lost to exploits in 2023 Built-in SWC-aligned detectors + optional Slither
6-week audit queues at $30k–$100k Instant scans on every save and PR
No tooling for indie devs and small DAOs Free, open-source CLI, extension, and GitHub Action

What ChainProof does

  • Parses Solidity source into an AST and runs custom vulnerability rules
  • Optionally integrates Slither for broader static analysis
  • Flags gas optimization opportunities separately from security findings
  • Optionally sends critical/high findings to Claude for contextual explanations and fixes
  • Emits reports as terminal tables, JSON, or Markdown

Disclaimer: ChainProof is a developer tool, not a substitute for a professional security audit. Always have critical contracts reviewed by qualified humans before mainnet deployment.


Architecture

All user-facing packages share a single scanning engine (@chainproof/core). Every interface calls the same scan() function with a ScanConfig object.

flowchart TB
    subgraph clients [Clients]
        cli[CLI]
        vscode[VS Code]
        gha[GitHub Action]
    end

    subgraph core [Core Engine]
        scanner[Scanner]
        parser[AST Parser]
        rules[SWC Rules]
        slitherWrap[Slither Wrapper]
        llm[LLM Layer]
        report[Reports]
    end

    subgraph ext [External]
        slitherBin[Slither]
        claude[Claude API]
    end

    cli --> scanner
    vscode --> scanner
    gha --> scanner
    scanner --> parser
    scanner --> rules
    scanner --> slitherWrap
    scanner --> llm
    scanner --> report
    slitherWrap --> slitherBin
    llm --> claude
Loading

Package responsibilities

Package NPM name Purpose
packages/core @chainproof/core AST parsing, rules, Slither wrapper, LLM layer, report generation
packages/cli @chainproof/cli Command-line interface (scan, check, init)
packages/vscode-extension chainproof-vscode Inline diagnostics, auto-scan on save, audit report command
packages/github-action CI gate, PR comments, workflow annotations, artifacts

Scan Pipeline

Each .sol file passes through the pipeline below. Directory targets are expanded recursively before scanning.

flowchart TB
    a[Collect files] --> b[Parse AST]
    b --> c{Valid AST}
    c -->|no| d[Parse error]
    c -->|yes| e[Run rules]
    e --> f[Gas hints]
    f --> g{Slither on}
    g -->|yes| h[Merge Slither]
    g -->|no| i[Filter severity]
    h --> i
    i --> j{LLM on}
    j -->|yes| k[Enhance findings]
    j -->|no| l[File result]
    k --> l
Loading

Severity levels

Findings are ranked for filtering and CI gating:

Severity Rank Typical use
critical 5 Immediate blocker — e.g. reentrancy
high 4 Must fix before deploy — e.g. tx.origin auth
medium 3 Should review — e.g. unchecked return values
low 2 Minor issues
info 1 Informational notes
gas 0 Optimization hints (not security vulnerabilities)

Repository Layout

chainproof/
├── packages/
│   ├── core/
│   │   └── src/
│   │       ├── ast/          # Solidity parser + Slither wrapper
│   │       ├── rules/        # SWC vulnerability detectors + gas optimizer
│   │       ├── llm/          # Claude-powered explanation layer
│   │       ├── report/       # Markdown / JSON / table generators
│   │       ├── scanner.ts    # Main scan orchestrator
│   │       └── types.ts      # Shared TypeScript interfaces
│   ├── cli/                  # `chainproof` CLI
│   ├── vscode-extension/     # VS Code extension
│   └── github-action/        # GitHub Action for CI/CD
├── examples/
│   └── contracts/
│       ├── VulnerableVault.sol   # Intentionally vulnerable (test target)
│       └── SecureVault.sol       # Patched reference implementation
└── .github/workflows/audit.yml   # Example CI workflow

Installation

Prerequisites

Tool Version Required for
Node.js ≥ 18 CLI, extension, core engine
Python ≥ 3.10 Slither (optional)
slither-analyzer latest Extended static analysis (optional)
# Optional but recommended
pip install slither-analyzer

From source (development)

git clone https://github.com/your-org/chainproof
cd chainproof
npm install          # installs all workspaces
npm run build        # compiles TypeScript in all packages

Global CLI install

npm install -g @chainproof/cli

CLI Reference

chainproof scan

Scan one or more .sol files or directories.

chainproof scan contracts/
chainproof scan contracts/ --format markdown --output audit.md
chainproof scan contracts/ --api-key YOUR_ANTHROPIC_KEY
chainproof scan contracts/ --min-severity high --no-slither
Flag Default Description
--no-slither Slither on Skip Slither even if installed
--no-llm LLM on if key set Skip Claude enhancement
--api-key <key> $ANTHROPIC_API_KEY Anthropic API key
--min-severity <level> low Filter findings below this level
--format <format> table Output: table, json, or markdown
--output <file> stdout Write report to file

Exit codes: 0 if no critical/high findings; 1 if critical or high issues are detected.

When using the default table format without --output, a full Markdown report is also saved to chainproof-report.md.

chainproof check

Fast pass/fail check for CI. Only reports critical and high findings. LLM is always disabled.

chainproof check contracts/

Exits 1 on any critical or high severity finding.

chainproof init

Creates a .chainproofrc.json config file in the current directory.

chainproof init

VS Code Extension

Install from the VS Code Marketplace (search ChainProof) or load from packages/vscode-extension during development.

Commands

Command Description
ChainProof: Scan Current File Scan the active .sol file
ChainProof: Scan Entire Workspace Scan all open Solidity files
ChainProof: Generate Audit Report Write chainproof-audit-report.md
ChainProof: Clear Diagnostics Remove all ChainProof diagnostics

Behavior

sequenceDiagram
    participant Dev as Developer
    participant IDE as VS Code
    participant Ext as Extension
    participant Core as Core Engine

    Dev->>IDE: Open or save file
    IDE->>Ext: Document event
    Ext->>Core: scan
    Core-->>Ext: ScanResult
    Ext->>IDE: Diagnostics
    Ext->>IDE: Status bar
Loading

Findings appear in the Problems panel with severity mapped to VS Code diagnostic levels:

  • critical / high → Error
  • medium → Warning
  • low / info / gas → Information or Hint

Settings

Configure under Settings → ChainProof:

Setting Default Description
chainproof.enableOnSave true Auto-scan on save
chainproof.useSlither true Run Slither if available
chainproof.useLLM false Enhance findings with Claude
chainproof.apiKey "" Anthropic API key (or ANTHROPIC_API_KEY env)
chainproof.minSeverity low Minimum severity to display

GitHub Action

Add ChainProof to your workflow to gate merges on security findings.

# .github/workflows/audit.yml
- name: ChainProof Audit
  uses: your-org/chainproof@v1
  with:
    targets: "contracts/"
    min-severity: "high"
    use-slither: "true"
    api-key: ${{ secrets.ANTHROPIC_API_KEY }}
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Action inputs

Input Default Description
targets contracts/ Space-separated paths to scan
min-severity high Fail CI at this severity or above
use-slither true Run Slither if installed on runner
api-key "" Anthropic API key for LLM enhancement
report-format markdown PR comment format
upload-report true Write reports to chainproof-reports/
fail-on-gas false Fail CI when gas hints are present

Action outputs

Output Description
critical-count Number of critical findings
high-count Number of high findings
total-count Total findings including gas hints
report-path Path to generated Markdown report

CI workflow

flowchart TB
    a[PR or push] --> b[Setup runner]
    b --> c[Scan contracts]
    c --> d[PR comment]
    c --> e[Annotations]
    c --> f[Write reports]
    f --> g[Upload artifact]
    c --> h{Meets threshold}
    h -->|yes| i[Fail CI]
    h -->|no| j[Pass CI]
Loading

The action will:

  • Scan all .sol files under targets
  • Post or update a summary comment on pull requests
  • Annotate changed files with inline findings
  • Upload the full audit report as a GitHub Actions artifact
  • Fail the job when findings meet or exceed min-severity

See .github/workflows/audit.yml for a complete working example.


Vulnerability Rules

Built-in detectors

ID SWC Name Severity Detection approach
CP-107 SWC-107 Reentrancy Critical External call before state update in same function
CP-115 SWC-115 tx.origin authentication High tx.origin used in require or access control
CP-101 SWC-101 Integer overflow / underflow High Arithmetic on pragma < 0.8 without SafeMath
CP-104 SWC-104 Unchecked call return value Medium .call / .send return value not checked
GAS-* Gas optimizations Gas Storage in loops, packing, keccak256, etc.

When Slither is installed, all Slither detectors are merged in with deduplication by line + title. Slither findings are prefixed with SLITHER-.

Rule detection flow

flowchart TB
    ast[Parsed AST] --> r1[Reentrancy]
    ast --> r2[Tx Origin]
    ast --> r3[Overflow]
    ast --> r4[Unchecked Return]
    ast --> r5[Gas rules]
    r1 --> merge[Security findings]
    r2 --> merge
    r3 --> merge
    r4 --> merge
    slither[Slither] --> merge
    r5 --> gas[Gas hints]
Loading

Example contracts

examples/contracts/VulnerableVault.sol is intentionally vulnerable and should report critical + high findings. SecureVault.sol is the patched reference and should scan clean.

# After building from source
node packages/cli/dist/cli.js scan examples/contracts/VulnerableVault.sol
node packages/cli/dist/cli.js scan examples/contracts/SecureVault.sol

Data Model

Finding

Each detected issue is a Finding object:

interface Finding {
  id: string; // e.g. "CP-107"
  title: string;
  description: string;
  recommendation: string;
  severity: "critical" | "high" | "medium" | "low" | "info" | "gas";
  file: string;
  line: number;
  lineEnd?: number;
  snippet?: string;
  swcId?: string; // e.g. "SWC-107"
  llmEnhanced?: boolean;
}

ScanResult

interface ScanResult {
  version: string;
  timestamp: string;
  files: FileScanResult[];
  summary: {
    critical: number;
    high: number;
    medium: number;
    low: number;
    info: number;
    gas: number;
    total: number;
  };
}

Programmatic usage

import { scan, generateMarkdownReport } from "@chainproof/core";

const result = await scan({
  targets: ["contracts/MyToken.sol"],
  useSlither: true,
  useLLM: false,
  minSeverity: "low",
});

console.log(generateMarkdownReport(result));

Configuration

Environment variables

Variable Used by Description
ANTHROPIC_API_KEY CLI, extension, action Claude API key for LLM enhancement

.chainproofrc.json

Generated by chainproof init:

{
  "targets": ["contracts/"],
  "useSlither": true,
  "useLLM": true,
  "minSeverity": "low",
  "outputFormat": "markdown",
  "output": "audit-report.md"
}

Slither integration

When useSlither is enabled, ChainProof enriches Slither's raw output before merging it with built-in findings:

  • Detector registry (packages/core/src/ast/slither-detectors.ts) maps 90+ of Slither's built-in detectors to a human title, an SWC cross-reference where one applies, and a rule category used for deduplication. Unmapped detectors still produce a finding — just without the extra context.

  • Confidence-weighted severity — Slither reports both an impact (High/Medium/Low/Informational) and a confidence (High/Medium/Low) for every finding. ChainProof combines both axes instead of impact alone, so a High-impact/Low-confidence finding reads as medium, not critical:

    Impact ↓ / Confidence → High Medium Low
    High critical high medium
    Medium high medium low
    Low medium low info
    Informational info info info
  • Deduplication matches on rule category + normalized file + line-range overlap, so e.g. built-in CP-107 and Slither's reentrancy-eth firing on the same lines collapse into one finding instead of two, and Slither reporting the same issue once per inheritance level doesn't produce duplicates either.

  • Detector allowlist/denylist — exclude noisy detectors (or run only a specific set) via ScanConfig.slither.detectors:

    {
      "targets": ["contracts/"],
      "useSlither": true,
      "useLLM": false,
      "slither": {
        "detectors": {
          "exclude": ["assembly", "low-level-calls"]
        }
      }
    }

    include takes precedence over exclude when both are set. Omitting slither entirely runs every detector, matching prior behavior.

LLM enhancement

When enabled, critical and high findings are sent to Claude (claude-sonnet-4-20250514) for:

  • A developer-friendly explanation of the risk in context
  • A copy-paste-ready fix for the specific code
  • A brief real-world exploit scenario
export ANTHROPIC_API_KEY=sk-ant-...
chainproof scan contracts/ --llm

LLM calls are best-effort — if the API fails, the original scanner finding is returned unchanged.


Plugin API

ChainProof is extensible. Teams can ship custom detection rules via plugins without modifying the core engine. Plugins enable:

  • Protocol-specific rules — Detect usage of non-approved oracle wrappers or deprecated internal functions
  • Proprietary patterns — Auditing firms can bundle closed-source detection logic
  • Research & prototyping — Test new vulnerability detectors before contributing to core
  • Team standards — Enforce internal coding practices across projects

Plugin structure

A plugin is an NPM package or .js file that exports a ChainProofPlugin object:

interface ChainProofPlugin {
  name: string; // e.g. "myteam-rules"
  version: string; // semantic version
  rules: PluginRule[]; // array of detection rules
}

interface PluginRule {
  id: string; // e.g. "MYTEAM-001"
  title: string; // human-readable title
  severity: Severity; // "critical" | "high" | "medium" | "low" | "info"
  description: string; // why this is dangerous
  recommendation?: string; // how to fix it
  detect: (ast: ASTNode, source: string, filePath: string) => Finding[];
}

Example plugin

See examples/plugins/simple-rules/index.js for a complete working example with two simple rules.

To run the example:

# From the root directory
npm run build
chainproof scan examples/contracts/ --plugin examples/plugins/simple-rules/index.js

Loading plugins

Plugins can be loaded in three ways:

CLI: --plugin flag

# Load a single plugin
chainproof scan contracts/ --plugin ./my-rules.js

# Load multiple plugins (repeat the flag)
chainproof scan contracts/ --plugin ./my-rules.js --plugin @myteam/chainproof-rules

# Load a plugin via npm package
npm install @myteam/chainproof-rules
chainproof scan contracts/ --plugin @myteam/chainproof-rules

Config file: .chainproofrc.json

Create a .chainproofrc.json in your project root:

{
  "targets": ["contracts/"],
  "useSlither": true,
  "useLLM": false,
  "plugins": ["./local-rules/my-plugin.js", "@myteam/chainproof-rules"]
}

Then scan automatically picks up the plugins:

chainproof scan  # plugins from .chainproofrc.json

Programmatic: ScanConfig.plugins

import { scan, loadPlugins } from "@chainproof/core";

const plugins = loadPlugins(["@myteam/rules", "./local/rules.js"]);

const result = await scan({
  targets: ["contracts/"],
  useSlither: true,
  plugins,
});

Writing a plugin

Step 1: Create a .js or .ts file with a plugin object:

// my-custom-rules.js
const plugin = {
  name: "my-custom-rules",
  version: "1.0.0",
  rules: [
    {
      id: "CUSTOM-001",
      title: "Disallow magic numbers",
      severity: "medium",
      description: "Magic numbers reduce code clarity and maintainability.",
      recommendation: "Define named constants instead.",
      detect(ast, source, filePath) {
        // Return an array of Finding objects
        return [];
      },
    },
  ],
};

module.exports = plugin;

Step 2: Use the visitor pattern to traverse the AST:

The ast parameter is a Solidity AST (from @solidity-parser/parser). ChainProof's internal rules use this pattern:

detect(ast, source, filePath) {
  const findings = [];

  // Helper to walk the AST
  function visit(node, callback) {
    if (!node) return;
    callback(node);
    for (const key in node) {
      if (Array.isArray(node[key])) {
        node[key].forEach(child => visit(child, callback));
      } else if (typeof node[key] === "object") {
        visit(node[key], callback);
      }
    }
  }

  visit(ast, (node) => {
    if (node.type === "FunctionDefinition") {
      // Detect something about this function
      if (/* your condition */) {
        findings.push({
          id: "CUSTOM-001",
          title: "...",
          severity: "medium",
          description: "...",
          recommendation: "...",
          file: filePath,
          line: node.loc?.start?.line || 0,
          snippet: source.split("\n")[node.loc?.start?.line - 1],
        });
      }
    }
  });

  return findings;
}

Step 3: Publish as an NPM package (optional):

npm init -y
npm publish

Then users can install and use it:

npm install @myteam/chainproof-rules
chainproof scan contracts/ --plugin @myteam/chainproof-rules

Plugin error handling

If a plugin fails to load or throws an error during detection:

  • The warning is logged to stderr (non-fatal)
  • Scanning continues with other rules and plugins
  • The scan does not fail
[ChainProof] Failed to load plugin "my-plugin.js": Cannot find module
[ChainProof] Plugin "my-rules" rule "CUSTOM-001" failed: TypeError ...

This design ensures plugins are optional enhancements, not blockers.

Plugin types

You can import the plugin types for TypeScript projects:

import type {
  ChainProofPlugin,
  PluginRule,
  Finding,
  ASTNode,
  Severity,
} from "@chainproof/core";

API Reference

Full generated API documentation for @chainproof/core — every public function, type, and interface with JSDoc descriptions and examples — is published at:

https://dragoncode-01.github.io/StellarChainproofs/api/

It is rebuilt automatically from source on every release (see .github/workflows/docs.yml). To build it locally:

npm run docs --workspace=packages/core
# open packages/core/../../docs/api/index.html

@chainproof/core's exports are split into two tiers:

  • Public, stablescan, generateMarkdownReport, generateJSONReport, generateTableReport, isSlitherAvailable, loadPlugin, loadPlugins, loadConfigFile, mergePluginsFromConfig, and their associated types (ScanConfig, ScanResult, Finding, ChainProofPlugin, PluginRule, etc). These follow semantic versioning: breaking changes bump the major version, additive changes bump minor, fixes bump patch.
  • Internal — helpers like parseSolidity, visit, and runSlither are tagged @internal and excluded from the generated reference. They are implementation details of the rule engine and may change without notice between minor versions.

Deprecated public APIs are marked with @deprecated in a minor release and removed no earlier than the next major release — see the Changelog for specifics.

Quick start

import { scan, generateMarkdownReport } from "@chainproof/core";

const result = await scan({
  targets: ["contracts/"],
  useSlither: false,
  useLLM: false,
  useMetrics: false,
});

console.log(result.summary.critical); // number of critical findings
console.log(generateMarkdownReport(result));

See Programmatic usage and Plugin API above for more complete examples, including Slither, LLM enhancement, and custom rules.


Development Guide

Build and test

npm install
npm run build
npm run lint
npm run test

Adding a new rule

  1. Create packages/core/src/rules/swcXXX-your-rule.ts
  2. Export a detectXxx(ast, source, filePath): Finding[] function using the AST visitor pattern
  3. Import and call it in packages/core/src/scanner.ts
  4. Add an entry to the Vulnerability Rules table
  5. Add or update an example in examples/contracts/ to exercise the rule

Rule template:

import { visit, getSnippet } from "../ast/parser";
import type { Finding } from "../types";
import type { ASTNode } from "@solidity-parser/parser";

export function detectMyRule(
  ast: ASTNode,
  source: string,
  filePath: string,
): Finding[] {
  const findings: Finding[] = [];

  visit(ast, {
    // Match relevant AST node types
    FunctionDefinition(node: ASTNode) {
      // ... detection logic ...
      findings.push({
        id: "CP-XXX",
        swcId: "SWC-XXX",
        title: "Rule title",
        description: "Why this is dangerous",
        recommendation: "How to fix it",
        severity: "high",
        file: filePath,
        line: 0,
        snippet: getSnippet(source, node),
      });
    },
  });

  return findings;
}

Workspace scripts

Script Description
npm run build Build all packages
npm run test Run tests in all workspaces
npm run lint ESLint on packages/*/src/**/*.ts
npm run dev:cli Watch-build the CLI package

Roadmap

  • SWC-103: Floating pragma detector
  • SWC-116: Timestamp dependency
  • SWC-120: Weak randomness (block.timestamp / blockhash)
  • Foundry test generation for detected vulnerabilities
  • Hardhat plugin
  • SARIF output for GitHub Security tab
  • Web dashboard with project-level history
  • Support for Vyper

Changelog

See CHANGELOG.md for the full release history. This project follows Keep a Changelog and Semantic Versioning.


License

MIT © ChainProof Contributors