Active host-level discovery and explainable threat modeling for local AI runtimes
CHARA maps the complete AI attack surface on a developer or enterprise machine. It discovers every Model Context Protocol (MCP) server configured or running locally, classifies what each one can do (shell execution, file access, network egress), actively verifies exploitability using safe non-destructive payloads, and produces an explainable runtime intelligence report tailored to either security analysts or enterprise administrators.
| Finding Type | Source | Example Risk |
|---|---|---|
| Credentials in IDE configs | hunter |
AWS key in Claude Desktop env block |
| MCP servers in unsafe directories | hunter |
Binary served from /tmp |
| Running MCP processes | recon |
Python process with MCP keyword in cmdline |
| Unauthenticated TCP MCP endpoints | probe |
execute_bash tool exposed on loopback, execution verified |
| Stdio MCP servers (all registered IDEs) | ghost |
Shell-capable server registered in Cursor + Gemini |
| Unknown binaries registered as MCP servers | ghost |
Standalone absolute-path binary with no interpreter |
| Installed AI tooling | inventory |
VS Code, Copilot, Ollama, Claude Code, Gemini CLI |
main.py
└── engine.py Facade — coordinates all scanners
├── inventory.py Phase 0 — AI-BOM: IDEs, extensions, CLI agents, runtimes
├── hunter.py Phase 1 — Static config scan (Claude/Cursor/Windsurf/Gemini/JetBrains)
├── recon.py Phase 2 — Live process table scan
├── ghost.py Phase 3 — Stdio subprocess probe (deep mode only)
├── probe.py Phase 4 — TCP JSON-RPC interrogation + echo verification
└── export.py Phase 5 — ARS scoring, JSON export, narrative summary
src/explain.py Explainability Engine — converts Finding → Explanation
src/classify.py Tool manifest classifier — edge detection from tool names/descriptions
src/models.py DTOs: Finding, Explanation, ScanContext, CharaGraph, ARSResult
All scanners share a single DTO (Finding). All configuration flows through ScanContext. No cross-imports between scanner modules. The Explainability Engine (explain.py) is pure logic — no I/O, no discovery calls.
CHARA scores each finding using the Agentic Risk Score (ARS) matrix:
| Score | Condition |
|---|---|
CRITICAL |
Unauthenticated RPC + shell execution capability, or process running as root/SYSTEM |
HIGH |
Unauthenticated RPC + file access or network egress |
MEDIUM |
Plaintext credential found in static config |
LOW |
Unauthenticated RPC with benign utility tools only |
CHARA distinguishes between what was observed and what was confirmed:
| Label | Meaning | When |
|---|---|---|
EXECUTION VERIFIED |
Echo probe returned exact string | Shell tool accepted echo 'CHARA-VALIDATION' |
HANDSHAKE VERIFIED |
Full MCP protocol completed | Ghost probe completed stdio handshake |
CAPABILITY OBSERVED |
Server responded, capability declared | tools/list returned but no echo probe needed |
CONFIGURATION ONLY |
Seen in config file, not live | Port closed or server not running |
PROCESS OBSERVED |
Process matched MCP patterns | Process scan match, no active probe |
When shell execution is detected, CHARA sends exactly one active payload:
echo 'CHARA-VALIDATION'
EXECUTION VERIFIED is set only when the exact string CHARA-VALIDATION appears in the response. No destructive, state-altering, or network-propagating payloads are ever sent. Ghost probing spawns subprocesses with a minimal environment (PATH only on macOS/Linux, seven safe keys on Windows) — credentials are never forwarded to spawned processes.
CHARA flags unknown stdio binaries using OS-native signals — no hardcoded lists:
- Standalone binary signal — command is an absolute path with no interpreter run target in args. Legitimate MCP servers are almost always
interpreter + target(e.g.npx -y @org/server,python3 -m module). A standalone absolute-path binary is unusual. - Quarantine attribute (macOS only) — checks
com.apple.quarantinexattr. Presence means the binary arrived from an untrusted download channel. Homebrew and system package managers clear this on install.
- Python 3.11+
psutil >= 5.9.0, < 7
pip install -r requirements.txtpython3 main.py [OPTIONS]| Flag | Default | Description |
|---|---|---|
--scan-mode fast|balanced|deep |
balanced |
Scan depth. fast ~2s, balanced ~10s, deep ~30s+ |
--ghost-probe |
off | Spawn stdio MCP servers to verify capabilities (deep mode only) |
--no-probe |
off | Skip TCP probing — hunter + recon only |
--audience analyst|admin |
analyst |
Output view: security assessment or administrator governance |
--ports N [N ...] |
built-in list | Override loopback ports to probe |
--output FILE / -o |
chara_graph_export.json |
Path for JSON export |
--verbose / -v |
off | Per-module detail during scan |
# Quick passive scan
python3 main.py --scan-mode fast
# Full deep scan with stdio verification — analyst view
python3 main.py --scan-mode deep --ghost-probe --verbose
# Administrator governance report
python3 main.py --scan-mode deep --ghost-probe --audience admin
# CI/CD gate — probe specific ports, fail on CRITICAL
python3 main.py --scan-mode balanced --ports 3000 8080 8989
echo "Exit: $?" # 4=CRITICAL 3=HIGH 2=MEDIUM 1=LOW 0=clean| Code | Meaning |
|---|---|
0 |
No findings |
1 |
Worst finding is LOW |
2 |
Worst finding is MEDIUM |
3 |
Worst finding is HIGH |
4 |
Worst finding is CRITICAL |
Use exit codes to gate CI/CD pipelines — a non-zero exit blocks a build when MCP risk is detected.
CHARA produces two sections: an AI-BOM inventory followed by the threat surface report.
=======================================================================
CHARA AI Runtime Intelligence Report — Security Assessment
2026-06-27 14:22 | Mode: deep | 3 finding(s)
=======================================================================
[CRITICAL] Cursor Desktop -- filesystem-mcp
Confidence: EXECUTION VERIFIED
Transport: TCP 127.0.0.1:1235
Cursor Desktop has a server on port 1235 exposing a shell execution
tool (execute_bash). The tool was actively verified — it accepted and
executed a test command with no authentication required.
Chain: Cursor Desktop -> filesystem-mcp -> execute_bash
-> Developer Authority (developer) -> No Authentication
-> Arbitrary Execution Risk
Evidence:
* tools/list returned: [execute_bash, read_file, write_file]
* Echo probe confirmed: "CHARA-VALIDATION" (exact match)
* Config: ~/.cursor/mcp.json -> "filesystem-mcp"
Analyst: Verified RCE surface. No credential required to invoke
execute_bash. Enumerate available paths, environment
variables, and network access from this execution context.
-----------------------------------------------------------------------
[MEDIUM] Cursor Desktop -- data-server (config)
Confidence: CONFIGURATION ONLY
Cursor Desktop configuration at ~/.cursor/mcp.json contains a
plaintext AWS key for 'data-server'.
Evidence:
* Credential found: aws_access_key -> AKIA...EXAMPLE
Analyst: Determine whether this credential is valid and what cloud
resources it grants access to. Check for credential reuse.
=======================================================================
CRITICAL: 1 HIGH: 0 MEDIUM: 1 LOW: 0 | Total: 2
Confidence: EXECUTION VERIFIED: 1 | CONFIGURATION ONLY: 1
=======================================================================
Same scan, governance-focused language:
=======================================================================
CHARA AI Runtime Intelligence Report — Administrator View
2026-06-27 14:22 | Mode: deep | 3 finding(s)
=======================================================================
[CRITICAL] Cursor Desktop -- filesystem-mcp
Confidence: Actively Verified
Cursor Desktop has a server on port 1235 exposing a shell execution
tool (execute_bash). The tool was actively verified — it accepted and
executed a test command with no authentication required.
Evidence:
* tools/list returned: [execute_bash, read_file, write_file]
* Config: ~/.cursor/mcp.json -> "filesystem-mcp"
Admin: Immediate review required. Disable or restrict this server
if not actively needed for approved workflows. Verify this
capability is covered by your AI governance policy.
=======================================================================
CRITICAL: 1 HIGH: 0 MEDIUM: 1 LOW: 0 | Total: 2
Review any CRITICAL or HIGH findings with your AI governance team.
=======================================================================
{
"metadata": { "generated_at": "...", "scan_mode": "deep", "total_findings": 3 },
"nodes": [
{
"id": "endpoint_127.0.0.1_1235",
"type": "MCPEndpoint",
"edges": ["UnauthenticatedRPC", "ExposesShellExecution"],
"verified": true,
"metadata": {
"host": "127.0.0.1", "port": 1235,
"tool_names": ["execute_bash", "read_file"],
"shell_tool": "execute_bash",
"verified_meaning": "rce_confirmed",
"ide": "cursor"
}
}
],
"edges": [ { "from": "endpoint_127.0.0.1_1235", "label": "ExposesShellExecution" } ],
"ars_findings": [ { "node_id": "...", "score": "CRITICAL", "reasons": [...] } ],
"explanations": [
{
"finding_id": "endpoint_127.0.0.1_1235",
"runtime_id": "Cursor Desktop",
"narrative": "Cursor Desktop has a server on port 1235...",
"confidence_label": "EXECUTION VERIFIED",
"evidence_items": ["tools/list returned: [execute_bash, ...]", "Echo probe confirmed..."],
"relationship_chain": ["Cursor Desktop", "filesystem-mcp", "execute_bash", "No Authentication"],
"next_action_admin": "Immediate review required...",
"next_action_analyst": "Verified RCE surface...",
"ars_score": "CRITICAL"
}
]
}The explanations array is additive — all existing keys (nodes, edges, ars_findings) are unchanged.
| Edge | Meaning |
|---|---|
UnauthenticatedRPC |
Server responded to unauthenticated tools/list |
ExposesShellExecution |
Tool name or description matches shell keywords (execute_bash, run_cmd, etc.) |
FileAccess |
Tool exposes file read/write operations |
NetworkEgress |
Tool exposes HTTP/fetch/download operations |
VerifiedStdioMCP |
Full MCP handshake completed via stdio transport |
PlaintextCredential |
Credential pattern matched in static config (env block or args) |
AgenticTier0 |
Process running as root/SYSTEM |
ActiveStdioBind |
Process holds an open loopback socket or named pipe |
InheritsContext |
Process running under developer (non-elevated) context |
Every probe and ghost finding carries a verified_meaning key in its metadata:
| Value | Meaning |
|---|---|
rce_confirmed |
Echo probe returned exact CHARA-VALIDATION string |
handshake_confirmed |
Full MCP stdio handshake completed by ghost probe |
declared_only |
Shell tool declared in manifest but echo probe failed |
not_applicable |
Finding has no shell execution capability |
A self-contained testbed validates all modules with no external dependencies:
python3 testbed/run_testbed.py22 scenarios covering hunter, recon, probe, ghost, export, and full pipeline end-to-end. S06 auto-skips on Windows (chmod is a no-op). S10 auto-skips without root.
Each module has a standalone self-check:
python3 src/models.py
python3 src/explain.py
python3 src/hunter.py
python3 src/recon.py
python3 src/probe.py
python3 src/ghost.py
python3 src/inventory.py
python3 src/export.pyGitHub Actions runs the full self-check + testbed matrix on every push:
| Platform | Python |
|---|---|
| macOS (latest) | 3.11, 3.12 |
| Ubuntu (latest) | 3.11, 3.12 |
| Windows (latest) | 3.11, 3.12 |
- Sensitive file cross-reference — CHARA detects
FileAccesscapability but does not enumerate which specific files on disk (e.g.~/.aws/credentials,~/.ssh/id_rsa) fall within the server's reach. Deferred pending scope confirmation (seeDocs/FUTURE_IMPLEMENTATION.mdS23). - Network scanning beyond loopback — CHARA only probes
127.0.0.1. No LAN or remote scanning. - CVE lookups — No external API calls during a scan.
- Payload beyond echo — The safe-probe contract is fixed. No additional payloads will be added to the core engine.
CHARA is a local, authorized-use security tool. Run it only on systems you own or have explicit written permission to scan. --ghost-probe spawns real subprocesses from your IDE configurations — use only on authorized systems within an approved engagement scope.