Build ExploitLens as a local-first TypeScript monorepo around a pinned Foundry runner. The runner generates versioned JSON evidence bundles. The React application reads those bundles and renders the Differential Replay Workbench. A small local API coordinates runs, but it is not the source of truth.
For the public hackathon link, deploy a read-only static frontend with a committed, pre-generated demo bundle. Run the live Foundry comparison locally during the demo. This preserves reliability and avoids exposing an unsafe arbitrary-code execution service.
- Evidence before interface.
- Deterministic artifacts before database state.
- Semantic trace identity before row-by-row diffing.
- Effects and invariants before revert status.
- Honest ambiguity before fabricated certainty.
- Narrow allowlisted execution before hosted generality.
- AO coordinates development but is not shipped in the runtime.
| Layer | Choice | Reason |
|---|---|---|
| Monorepo | pnpm workspaces | Fast, simple package boundaries and shared TypeScript configuration. |
| Frontend | React, Vite, TypeScript | Static-deployable, fast feedback, no server framework needed for artifact rendering. |
| Styling | Tailwind CSS plus CSS variables | Buildable visual system with explicit tokens and no dependency on generic component themes. |
| Client data | TanStack Query | Clear run polling and cached artifact reads. |
| Local API | Fastify, TypeScript | Small typed HTTP surface and predictable request validation. |
| Schema | Zod plus checked-in JSON Schema | Shared runtime validation and portable artifact contracts. |
| Execution | Foundry: Forge and optional Anvil | Native reproduction of supplied Solidity exploit tests. |
| Process control | Node child_process.spawn with argument arrays |
Streams output without shell interpolation. |
| Core storage | Versioned JSON artifact directories | Portable, inspectable, hashable evidence. |
| Optional index | SQLite | Local run discovery only, never authoritative. |
| Unit tests | Vitest | Shared TypeScript tests. |
| Browser tests | Playwright | Judge-flow and artifact-rendering smoke tests. |
| Contract tests | Forge | Fixture, exploit, control, and variant execution. |
| CI | GitHub Actions | Lint, typecheck, unit, fixture, verifier, build, and smoke gates. |
| Web deployment | Cloudflare Pages | Static frontend and prepared artifacts. |
| Optional runner deployment | Private Render Docker service | Controlled internal demo only, with allowlisted fixture and no public arbitrary input. |
flowchart TD
UI["React workbench"] --> API["Local Fastify API"]
API --> RUN["Execution coordinator"]
RUN --> FOUNDRY["Pinned Forge and Anvil"]
RUN --> CORE["Normalizer, aligner, gates, variants"]
FOUNDRY --> ART["Versioned JSON evidence bundle"]
CORE --> ART
ART --> UI
ART --> VERIFY["Independent verifier"]
The deployed static site replaces the local API with a prepared artifact bundle. It uses the same frontend artifact adapter and therefore demonstrates the same evidence model.
exploitlens/
apps/
web/ React workbench
runner/ Fastify local API and coordinator
packages/
manifest/ Exploit Bundle schema, canonicalization, hashing
executor/ workspaces, Foundry commands, timeouts, capture
trace-ir/ versioned canonical call/effect types
trace-normalizer/ raw Foundry output to Trace IR
effects/ native/token/event/storage effect extraction
aligner/ semantic tree alignment and divergence
invariants/ security and legitimate-control gate logic
variants/ deterministic candidate generation and scoring
receipt/ receipt creation and evidence references
verifier/ offline hash and consistency verification
ui-contracts/ frontend-safe artifact view models
fixtures/
demo-exploit/ pinned vulnerable, patched, exploit, and control case
schemas/ generated and reviewed JSON Schemas
artifacts/
demo/ committed fallback bundle
scripts/
reproduce-demo.sh
verify-demo.sh
docs/
architecture.md
evidence-model.md
trace-alignment.md
limitations.md
.github/workflows/ci.yml
pnpm-workspace.yaml
package.json
stateDiagram-v2
[*] --> ValidateManifest
ValidateManifest --> Invalid: schema or policy failure
ValidateManifest --> Baseline
Baseline --> Invalid: exploit not reproduced
Baseline --> PatchedReplay
PatchedReplay --> Control
Control --> NormalizeAlign
NormalizeAlign --> Variants
Variants --> Receipt
Receipt --> Complete
Invalid --> [*]
Complete --> [*]
- Parse the manifest and reject paths or inputs outside policy.
- Canonicalize and hash the frozen manifest.
- Create temporary workspaces for both commits.
- Confirm toolchain and dependency identity.
- Execute the vulnerable PoC and validate the expected exploit effect.
- Execute the same PoC on the patch.
- Execute the legitimate control on the patch.
- Normalize both traces and extract effects/state.
- Align the semantic trees and identify the first causal divergence.
- Compute the security and liveness gates.
- Generate candidate variants, validate them on vulnerable, then replay valid cases on patched.
- Write the receipt, artifact index, hashes, and verification script atomically.
type ExploitBundle = {
schemaVersion: "1.0";
caseId: string;
repository: { path: string; baselineCommit: string; patchedCommit: string };
foundry: {
testPath: string;
testSelector: string;
controlSelector: string;
profile?: string;
fuzzSeed: string;
compilerVersion: string;
evmVersion: string;
};
fork?: { chainId: number; blockNumber: number; rpcAlias: string };
expectedExploit: { effectType: string; asset?: string; minimumDelta?: string };
invariant: { id: string; description: string; evaluator: string };
variants: { strategy: string; count: number; seed: string };
};type CallNode = {
id: string;
parentId: string | null;
depth: number;
contractRole?: string;
address: string;
codeIdentity?: string;
selector?: string;
functionName?: string;
callType: "CALL" | "STATICCALL" | "DELEGATECALL" | "CREATE" | "CREATE2";
semanticPath: string;
msgValue: string;
effects: EffectRef[];
storageReads: StorageAccess[];
storageWrites: StorageAccess[];
events: EventRef[];
result: { class: "return" | "revert" | "halt"; label?: string };
children: string[];
};type AlignmentRecord = {
baselineNodeId?: string;
patchedNodeId?: string;
relation: "match" | "insert" | "delete" | "replace" | "ambiguous";
score: number;
reasons: string[];
};type GateResult = {
gate: "baseline" | "exact-replay" | "legitimate-control" | "neighborhood";
status: "pass" | "fail" | "inconclusive";
evidenceRefs: string[];
explanation: string;
};Build candidate matches using:
- Call type.
- Contract role or code identity.
- Function selector.
- Semantic ancestry.
- Relative sibling neighborhood.
- Security-relevant effects.
- Return or revert class.
Do not use these as primary identity:
- Gas used.
- Trace row number.
- Source byte offset.
- Absolute address when the same role is deterministically redeployed at a different address.
- Low-confidence matches remain
ambiguous. - The causal seam must reference evidence on both sides or an explicit insertion/deletion.
- The divergence algorithm should prioritize the earliest changed predicate, call, or effect that dominates the missing prohibited outcome.
- Golden alignment fixtures are required before UI polish.
POST /api/runs
{
"caseId": "demo-reentrancy",
"manifestPath": "fixtures/demo-exploit/exploitlens.manifest.json"
}Response:
{
"runId": "run_01",
"status": "queued",
"statusUrl": "/api/runs/run_01"
}The API accepts an allowlisted case identifier and manifest path, not a raw shell command or arbitrary repository URL.
GET /api/runs/:runId
{
"runId": "run_01",
"status": "running",
"phase": "align",
"progress": 62,
"gates": [],
"error": null
}The replay, variants, receipt, and artifact-index endpoints return schema-versioned JSON and strong ETags derived from artifact hashes.
artifacts/runs/<run-id>/
artifact-index.json
manifest.json
receipt.json
vulnerable/
patched/
alignment.json
divergence.json
variants/
Writes use a temporary directory and an atomic rename only after required artifacts validate. A partially completed run remains separate and cannot be shown as a verified receipt.
SQLite, if added, stores only run discovery metadata. Artifact hashes and receipt references remain the authority.
- The local API listens on
127.0.0.1. - No accounts or sessions.
- The public frontend is read-only.
- RPC secrets remain environment variables and are replaced with aliases in artifacts.
| Boundary | Risk | Control |
|---|---|---|
| Manifest to runner | Command or path injection | Zod validation, allowlists, normalized paths, spawn argument arrays. |
| Repository code to host | Malicious build/test behavior | MVP uses a reviewed fixture; hosted arbitrary execution is disabled. |
| Fork RPC | Secret leakage and nondeterminism | Environment-only secret, pinned block, redacted logs, fallback artifact. |
| Raw trace to UI | Oversized or malformed data | Schema validation, size limits, normalized UI contracts. |
| Artifact bundle to verdict | Tampering | Manifest hash, artifact hashes, receipt consistency checks. |
/cases/:caseId— Case overview./cases/:caseId/replay— Differential Replay Workbench./cases/:caseId/receipt— Fix Receipt and verifier result.
StaticHeaderCaseIdentityGateStripReplayRailAlignedCallRowCausalSeamEffectLedgerVariantLedgerReceiptPanelRunProgress
TanStack Query polls status only while a local run is active. Completed views are rendered entirely from immutable artifacts. Selection and synchronized scrolling are local component state; they do not mutate evidence.
- Editorial “Forensic Ledger” direction.
- Warm mineral canvas, dark ink, restrained rules, scarce cobalt accent.
- Muted red for vulnerable loss and muted green for preserved or blocked outcomes.
- Static subtly translucent header; no overlay panels.
- No gradients, glow, decorative stars, generic metric-card grid, or gratuitous scaling.
- Motion limited to progress, synchronized selection, and causal-seam orientation.
| Test layer | Required checks |
|---|---|
| Schema | Valid and invalid manifests, trace IR, receipt, artifact index. |
| Executor | Arguments, timeouts, cleanup, redaction, baseline stop behavior. |
| Normalizer | Golden raw traces produce stable canonical IR. |
| Effects | Known transfers, storage writes, events, and balance deltas. |
| Aligner | Match, insertion, deletion, replacement, ambiguity, stable causal seam. |
| Gates | All combinations, especially false success and failed control. |
| Variants | Vulnerable-first validation and correct denominator. |
| Verifier | Untouched bundle passes; any mutated referenced file fails. |
| Frontend | Artifact loading, synchronized rails, errors, receipt states. |
| End to end | One command creates and verifies the demo bundle. |
- Install pinned Node and pnpm.
- Install or restore pinned Foundry.
- Lint and format check.
- Typecheck all packages.
- Run TypeScript unit and golden tests.
- Run Forge fixture tests.
- Generate the demo artifact in CI or compare it with a stable snapshot.
- Run the independent verifier.
- Build the frontend.
- Run a Playwright smoke test against prepared artifacts.
No change merges through AO until the relevant checks pass and the orchestrator reviews the worker diff.
- Build
apps/webas static assets. - Bundle
artifacts/demowith the build. - Deploy to Cloudflare Pages.
- Disable or omit mutation endpoints.
- Show a clear “prepared evidence” state when live runner is unavailable.
- Run Fastify on localhost.
- Run the prepared Foundry fixture with a pinned command.
- Serve generated artifacts to the same frontend.
- Keep the committed artifact fallback ready in a second browser tab.
A Dockerized Render service is acceptable only for the allowlisted fixture, with no arbitrary repository input, strict timeouts, and no stored RPC secret in artifacts.
flowchart TD
ORCH["Claude Code orchestrator"] --> W1["Codex: executor and manifest"]
ORCH --> W2["Codex: trace IR and effects"]
ORCH --> W3["Codex: frontend artifact renderer"]
W1 --> REVIEW["Review and CI feedback"]
W2 --> REVIEW
W3 --> REVIEW
REVIEW --> ORCH
Use no more than two or three parallel workers initially. Shared schemas and repository foundation merge first. The aligner receives one owner and a separate review task because parallel edits to the same algorithm would increase conflict and reduce accountability.
Every worker brief must contain:
- One task ID and objective.
- Allowed files or directories.
- Inputs and already-merged contracts.
- Acceptance criteria.
- Required validation commands.
- A stop condition.
- “Do not merge or push” unless explicitly authorized.
- AO: https://github.com/Untrivial-ai/agent-orchestrator
- Forge test reference: https://www.getfoundry.sh/reference/forge/test
- Anvil reference: https://www.getfoundry.sh/reference/anvil/anvil