Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Inhuman MCP — a forensic-discovery MCP server whose every verdict is signable

SANS FIND EVIL! submissionthe inhuman team, powered by here.build.

A purpose-built MCP server that turns any agent host into a forensic analyst who cannot fabricate a finding and cannot modify the evidence — because neither is a state the architecture can represent. We demo Claude Code driving it to autonomously solve a published Windows intrusion (DFIR Madness case001, "Stolen Szechuan Sauce").

See docs/SUBMISSION.md for the full writeup and the mapping to the six judging criteria.

The one idea — fabrication is unconstructable, not mitigated

The #1 fear of AI-driven IR is the model summarizing raw data into a fabricated finding. Every other approach fights this with prompts ("only report what you can confirm"), graders, and confidence scores — mitigations on an output that can still lie.

This server removes the failure mode at the wire. It exposes one tool, sift_discovery(intent, expr), and every result it returns is sealed: a value signs only if it was read from a real evidence row. A value the host typed into its query does not sign — even when it's correct:

the real C2 IP, READ from a Volatility row  →  "203.78.103.109"   signable: TRUE
the real C2 IP, TYPED into the query         →  "203.78.103.109"   signable: FALSE

Same answer-key value — it signs when read, refuses when written. The only path to a signed finding is a tool execution against hash-verified evidence. (The same move Delta makes for CSS — wrong states unrepresentable — pointed at forensic grounding.)

The structural idea — one membrane, at the wire

The agent host and the evidence are separated by a single grounding membrane: this MCP server.

   ┌─ AGENT HOST (Claude Code) ──┐   stdio MCP    ┌─ INHUMAN MCP (the membrane) ──┐
   │ has the model               │ ──intent+expr─►│ has the evidence tools         │
   │ reasons · plans · self-fixes │                │ has NO model                   │
   │ no RAW evidence —            │ ◄─sealed result┤ seals every result             │
   │   only sealed projections    │   + custody    │ NO destructive verb exists     │
   └──────────────────────────────┘                └───────────────┬────────────────┘
                                                                    │ read-only (:ro)
                                                    ┌───────────────▼───────────────┐
                                                    │ frozen evidence image ·         │
                                                    │ SHA-256 custody · Docker        │
                                                    └─────────────────────────────────┘

The host is external and untrusted — Claude Code, another model, or your own client. The guarantee does not depend on which:

  • It cannot modify the evidence. The only verb on the wire is sift_discovery. There is no execute_shell, no write, no delete — a destructive command isn't a thing the host can name. The image is bind-mounted read-only.
  • It cannot fabricate a signable finding. The host writes the query, so it can type a guessed value — but the seal refuses to sign any value that wasn't read from a row (the §2b typed-literal gate). Fabrication isn't caught by a grader; it is structurally unsignable.
  • Both hold against any host, because the guarantee lives in the server, not in our orchestration of it. A frontier agent we don't control cannot fabricate or destroy against this membrane — which is a stronger claim than "our own loop behaves."

Run it

One command — proves the seal, runs the full offline gate, then tells you what's next:

./try-it.sh

Or step through it by hand:

# Prove the seal — a READ signs, a TYPED value does not (no Docker, no model, no downloads):
SIFT_MOCK_EVIDENCE=1 node --experimental-strip-types mcp/src/cli.ts --probe

# The gate — the full offline suite (no Docker, no model):
pnpm --filter @sift/mcp --filter @sift/evidence smoke

# Drive one grounded query by hand, with provenance:
SIFT_MOCK_EVIDENCE=1 node --experimental-strip-types mcp/src/cli.ts '(socket/netscan)'

Connect Claude Code (the headline) — or any MCP host. The server speaks stdio MCP on the official SDK; point a host at mcp/src/server.ts. Add to your project .mcp.json (Claude Code) or claude_desktop_config.json (Claude Desktop):

{
  "mcpServers": {
    "sift": {
      "command": "/ABSOLUTE/PATH/TO/node",
      "args": [
        "--experimental-strip-types",
        "/ABSOLUTE/PATH/TO/inhuman-sift/mcp/src/server.ts"
      ],
      "env": { "SIFT_EVIDENCE_ID": "/cases/dc01/memory.mem" }
    }
  }
}

⚠️ Use an absolute path to node (≥ 22.6, for --experimental-strip-types), not bare "node" — MCP hosts launch servers with a minimal PATH that omits nvm/volta/fnm shims. No build step: the server runs .ts source directly. For a no-Docker trial drop SIFT_EVIDENCE_ID and set "env": { "SIFT_MOCK_EVIDENCE": "1" } (bundled case001 fixtures). All diagnostics go to stderr — stdout is the JSON-RPC transport.

Evidence images are public downloads, registered locally — see EVIDENCE.md for the judge's on-ramp (URLs, registration, expected timings).

What's real

Layer What runs
Substrate @here.build/arrival-scheme — real born-provenance, promise-parallelism
Memory Volatility 3 (pslist, netscan, malfind, …) in Docker
Disk Sleuth Kit (carve) · Plaso (super-timeline) · regipy (registry/amcache) · python-evtx (event logs) · analyzeMFT
Evidence DFIR Madness case001 — DC01 memory + disk; DESKTOP; MemLabs 1–6 — registered, SHA-256-verified
Model supplied by the host — we demo Claude Code; the server is model-agnostic

Six evidence families, nine pure reference families, ~51 forensic functions. The forensic values are typed entities (SchemeIP, SchemeDateTime, Hash, Path, …) that thread provenance through pure operations — externality is derived, timestamps are comparable instants (Fantasy-Land Ord), never locale-fragile string sorts.

Trust boundary — architectural, with an intentionally empty column

Every guarantee is enforced by STRUCTURE. The "prompt-based" column is empty on purpose.

Guarantee How it's enforced Kind
Host cannot modify the evidence The only wire verb is sift_discovery; no write/delete/shell exists; image bind-mounted :ro Architectural (absent verb + RO mount)
Host cannot fabricate a signable finding The seal refuses to sign any value not read from a row — a typed/guessed value is unsignable (§2b) Architectural (the seal)
Host cannot reach an ungranted family The grant constructs the env; ungranted tools are absent — calling one is an unknown-symbol error, not a denied permission Architectural (structural absence)
Evidence cannot be silently altered SHA-256 hashed on first access, stamped into every custody entry + cache key; a re-registration mismatch is refused Architectural (custody-by-hash)
Prompt-based guardrails none — this row is empty by design

A fabricated finding is not caught — it is unconstructable. There is no "only report what you can confirm" prompt, no after-the-fact grader, no confidence threshold.

Honest bounds

  • Per-finding, not per-verdict. Each signable finding is read-derived, custodied, and re-runnable. The host synthesizes findings into a verdict — that synthesis is the host's reasoning (transparent in the execution log), and every atom it rests on is independently re-runnable. The mechanical stage that decomposed a verdict into audited sub-claims lives in our reference driver (a separate development repo, not part of this submission).
  • Evidence integrity is custody-by-hash, not re-hash-per-read. The image is hashed on first access and that SHA is stamped into every custody entry and cache key (re-hashing a multi-GB image on every read would be wrong engineering). A mismatch at re-registration is refused.
  • A local 8-bit model also solves the case through the same server — $0, no cloud — via our reference driver (a small-model fan-out: rnj scouts and writes the queries, larger models ideate; it found the evil in under 5 minutes). Lifting it onto the pure wire path is future work.
  • Three execution-family artifacts (prefetch / userassist / srum) are not yet wired to a real parser and return empty under real evidence; the offline suite exercises their mock rows — nothing fabricates.

Layout — the two packages that ARE the submission

inhuman-sift/
├── mcp/        @sift/mcp — THE SUBMISSION: the discovery membrane + the stdio MCP server
│   ├── src/server.ts         the stdio MCP endpoint a host connects to (tools/list → tools/call)
│   ├── src/discovery-tool.ts sift_discovery — describe() + call(intent, expr), the sealed surface
│   ├── src/discovery.ts      runDiscovery + sealVerdict — the grounding seal (signable/scoped/unsigned)
│   ├── src/discovery-env.ts  the family/grant roster (capability = structural absence)
│   ├── src/entities/*.ts     typed forensic values (IP, DateTime, Hash, Path, …)
│   └── src/cli.ts            drive one grounded query by hand, with provenance
│
├── evidence/   @sift/evidence — THE SUBMISSION: the substrate (zero workspace deps, node builtins)
│   ├── src/docker-exec.ts    the Dockerized forensic runners (Vol3 / TSK / Plaso), read-only mount
│   ├── src/ledger.ts         the append-only chain of custody (each read carries its `intent`)
│   ├── src/evidence-paths.ts the path resolver + SHA-256 custody hashing
│   └── src/{cache,plaso,tsk,extract,artifacts}.ts   tool plumbing + result cache
│
└── docs/       SUBMISSION.md · submission/{ARCHITECTURE,ACCURACY,DATASET,DESCRIPTION,TRY-IT-OUT} · examples/

The submission ships @sift/mcp + @sift/evidence. They depend strictly downward (mcp → evidence), and neither imports a model — that one-directional, model-free dependency is what makes "the host cannot fabricate" a property of the artifact rather than a promise about our code.

Dependencies. The membrane is built on the arrival framework (@here.build/arrival, -mcp, -provenance, -serializer, -sweet, …) — a separate open-source project; pnpm install resolves it from there. Everything else is standard npm (@modelcontextprotocol/sdk, zod, yaml). A multi-agent reference driver (the speed layer — qwen · rnj · glm · opus) lives in our development repo; it is not part of this submission.

A separate project on top of the arrival architecture, not part of here.build proper — its own thing.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages