A zero-cost, offline static analyzer for AI-agent orchestration code.
HarnessGuard finds security and reliability mistakes where agents, tools, state, and control loops are wired together. It never calls an LLM, never uploads code, and never executes the project it scans.
Status: working alpha MVP. Findings are review signals, not proof of exploitability.
The recurring failures reported across agent frameworks are not only model failures. They are ordinary engineering defects amplified by autonomous tools:
- agents loop until recursion or budget exhaustion;
- model-controlled text reaches shell,
eval, files, or URLs; - unsafe checkpoints deserialize attacker-controlled state;
- one compromised agent delegates to another without authorization;
- concurrent agents overwrite shared state;
- waits and HTTP calls hang without deadlines;
- prompts, environment variables, and tool output leak into logs;
- broad tool and filesystem permissions multiply blast radius.
Existing tools mainly red-team live models, benchmark prompt injection, or scan MCP components. They tell you how a running agent behaves, not how your harness was built. The defects above live in the harness source and configuration, and they are reviewable before anything runs.
HarnessGuard scans agent orchestration code before execution, fully locally. Python gets AST-aware checks. JSON, JSONC, YAML, TOML, INI, environment, Markdown, and text files get conservative line-level checks. Large, generated, dependency, and binary files are skipped. No imported project modules are loaded.
Every finding is a deterministic, reviewable signal: rule ID, severity, file, line, and snippet. Findings stream as text or structured JSON and map directly into GitHub code scanning via SARIF.
- Zero cost and offline. No runtime dependencies, no endpoint, no token, no account, no paid API. Runs on Python 3.10+ anywhere, including air-gapped environments.
- Deterministic. No LLM in the loop means identical output for identical input. Every run is reproducible and CI-friendly.
- Safety by design. It reads source and configuration only. It never executes the project it scans and never uploads code.
- Built for the harness, not the model. It targets how agents, tools, state, and control loops are wired together, the layer most tooling ignores.
- CI-native. A least-privilege GitHub Actions workflow is included. SARIF uploads appear in GitHub code scanning when code-security upload is available.
- Safe to adopt. Baselines let you ship the scanner into an existing project without failing on old findings; changed findings reappear.
- Rule IDs are stable API. New IDs are added over time; released IDs never silently change meaning.
| ID | Severity | Check |
|---|---|---|
| HG001 | critical | Dynamic eval |
| HG002 | critical | Dynamic exec |
| HG003 | critical | Dynamic shell command |
| HG004 | high | Unsafe subprocess arguments |
| HG005 | critical | Pickle/dill deserialization |
| HG006 | high | YAML load without SafeLoader |
| HG007 | critical | Dangerous deserialization enabled |
| HG008 | critical | Secret-like literal |
| HG009 | high | Whole environment sent to model/log |
| HG010 | critical | Environment secrets imported into serializer |
| HG011 | high | LangGraph-style invocation without recursion limit |
| HG012 | high | CrewAI agent without max_iter |
| HG013 | critical | Zero, negative, or huge execution budget |
| HG014 | high | Automatic delegation without a budget |
| HG015 | critical | Agent code execution enabled |
| HG016 | medium | Human approval explicitly disabled |
| HG017 | high | Wildcard tool permission |
| HG018 | high | Admin/root/all-access capability grant |
| HG019 | medium | HTTP request without timeout |
| HG020 | high | Blocking wait without timeout |
| HG021 | medium | Thread join without timeout |
| HG022 | high | Unbounded retry/agent loop |
| HG023 | medium | Async shared-state mutation without visible lock |
| HG024 | high | Tool parameter used directly as file path |
| HG025 | high | Tool parameter used directly as URL |
| HG026 | high | Root/full-filesystem access |
| HG027 | medium | Untrusted data concatenated into prompt |
| HG028 | medium | Raw agent-output handoff |
| HG029 | high | Sensitive prompt/message logging |
| HG030 | high | TLS verification disabled |
List rules from the CLI:
harnessguard --list-rulesharnessguard . --format text
harnessguard . --format json --output report.json
harnessguard . --format sarif --output harnessguard.sarif
harnessguard . --severity mediumExit codes: 0 no finding at/above threshold; 1 policy failed; 2 scanner/config/read error.
Requires Python 3.10+ and no runtime dependencies. For the full CLI reference, configuration options, baselines, output formats, and CI setup, see docs/USAGE.md.
python -m pip install -e .
harnessguard path/to/agent-projectWithout installation:
PYTHONPATH=src python -m harnessguard path/to/agent-projectWindows PowerShell:
$env:PYTHONPATH="src"
python -m harnessguard .Scan the intentionally vulnerable example:
$env:PYTHONPATH="src"
python -m harnessguard examples/vulnerable_harness.py --severity highCreate .harnessguard.json in the scan root:
{
"exclude": ["generated/*", "vendor/*"],
"ignore": ["HG029"],
"max_file_size": 1000000
}Suppress one line inline in text/config files by including harnessguard: ignore. Prefer project-level rule ignores only after a security review.
Adopt the scanner in an existing project without failing on old findings:
harnessguard . --write-baseline .harnessguard-baseline.json
harnessguard . --baseline .harnessguard-baseline.jsonFingerprints include rule, path, line, and snippet; changed findings reappear.
The included workflow at .github/workflows/harnessguard.yml scans on every push and pull request with least-privilege permissions and uploads SARIF to GitHub code scanning.
Python gets AST-aware checks. JSON, JSONC, YAML, TOML, INI, environment, Markdown, and text files get conservative line-level checks. Large, generated, dependency, and binary files are skipped. No imported project modules are loaded.
This MVP emphasizes high-signal local patterns and deliberately avoids claiming full taint analysis. A finding means "review this boundary." Future versions should add language-neutral data flow, framework adapters, dependency advisory matching, and policy-as-code.
┌──────────────────────────────┐
│ Config (.harnessguard.json) │
│ exclude · ignore · max_size │
└──────────────┬───────────────┘
│
▼
┌──────────────┐ ┌──────────────┐
│ CLI (cli.py)│──args──────▶│ Scanner │──walk + filter──▶ supported files
│ --format │ │ (scanner.py) │ (.py/.json/.yaml/.toml/.env/.md/...)
│ --severity │ └──────┬───────┘
│ --baseline │ │ read text (never execute, never import)
└──────────────┘ ▼
┌─────────────────────────────┐
│ per-file analysis │
│ Python → python_analyzer.py │ AST-aware (ast.NodeVisitor)
│ Text → text_analyzer.py │ line-level regex
└──────────────┬──────────────┘
▼
┌──────────────────────┐
│ Rules (rules.py) │ 30 stable IDs + severity + fix
└──────────┬───────────┘
▼
┌──────────────────────┐
│ Models (models.py) │ Finding (fingerprint), Rule
└──────────┬───────────┘
▼
┌──────────────────────────────────┐
│ Reporters (reporters.py) │
│ text · json · sarif │
└──────────────────┬───────────────┘
▼
exit code 0 / 1 / 2 + baseline suppression (baseline.py)
- Discovery. The scanner walks the target file or directory, applies exclude globs and the file-size cap, and keeps only supported extensions and special names (
Dockerfile,mcp.json,requirements.txt). No scanned project module is imported or executed. - Analysis. Each supported file is read as text and analyzed twice when relevant: every file goes through the line-level text analyzer;
.pyfiles also go through the AST-aware Python analyzer. - Rules. Both analyzers emit
Findingobjects from the single rule registry (rules.py), which assigns each check a stable ID, severity, category, message, and fix hint. - Suppression. Rule-level ignores (config
ignore), inlineharnessguard: ignore, and baseline fingerprints are applied before findings are sorted and reported. - Reporting. Findings render as human-readable text, JSON, or SARIF 2.1. The exit code is
0when no finding meets the severity threshold,1when policy fails, and2on a scanner/config/read error.
The scanner has zero runtime dependencies and no network surface: the entire pipeline runs on the standard library, making it safe for laptops, pre-commit hooks, and air-gapped CI.
- Benchmark rules against real vulnerable and fixed framework examples.
- Add taint tracking from prompt/tool inputs to dangerous sinks.
- Add framework adapters for LangGraph, CrewAI, AutoGen, Semantic Kernel, PydanticAI, smolagents, and MCP.
- Add JavaScript/TypeScript and C# parsers without compromising offline use.
- Map checks to OWASP Agentic categories and published advisories in machine-readable metadata.
- Add safe autofix suggestions and reusable pre-commit integration.
Public reports demonstrate the pain:
- LangGraph #6731: recursion limit can allow many expensive calls
- LangGraph #7313: numeric recursion limit is a poor stopping contract
- CrewAI #737: repeated identical tool calls
- CrewAI #2997: task stuck in a silent THINKING hang
- AutoGen #7144: shared state across planner/executor/reviewer agents
- AutoGen #7726: governance controls around multi-agent tool execution
- AutoGen #7784: prompt-injected delegation
- LangChain advisory GHSA-c67j-w6g6-q2cm: unsafe deserialization
- Microsoft analysis: prompts reaching unsafe framework functions
- Prompt Infection research: injection propagating between agents
- OWASP Top 10 for Agentic Applications 2026
Nearby tools include garak, PyRIT, AgentDojo, and Snyk Agent Scan. Their scopes differ: HarnessGuard's MVP is deterministic static scanning of orchestration code with no endpoint, token, account, or paid API.
python -m pip install -e .
python -m unittest discover -s tests
harnessguard .See CONTRIBUTING.md and SECURITY.md. Licensed under MIT.