This file provides guidance to AI coding agents when working with this repository.
AgentShield is a Rust-based, offline-first security scanner for AI agent extensions (MCP servers, OpenClaw skills, CrewAI tools, LangChain tools, GPT Actions, Cursor Rules). It produces SARIF output compatible with GitHub Code Scanning, supports DSSE attestation, and includes baseline diffing, suppressions, and egress policy generation.
agentshield/
├── src/
│ ├── lib.rs # Public API: scan(), render_report()
│ ├── error.rs # ShieldError (thiserror)
│ ├── bin/cli.rs # Clap CLI: scan, list-rules, init, suppress, list-suppressions, certify
│ ├── ir/ # Intermediate Representation (ScanTarget)
│ │ ├── mod.rs # ScanTarget, Framework, SourceFile, ArgumentSource
│ │ ├── tool_surface.rs # Tool definitions, permissions
│ │ ├── execution_surface.rs # Commands, file IO, network IO
│ │ ├── data_surface.rs # Sources, sinks, taint paths
│ │ ├── dependency_surface.rs # Dependencies, lockfiles
│ │ └── provenance_surface.rs # Author, repo, license
│ ├── adapter/ # Framework → IR (3-phase pipeline)
│ │ ├── mod.rs # Adapter trait, auto_detect_and_load(root, ignore_tests)
│ │ ├── mcp.rs # MCP server adapter + is_test_file() + shared helpers
│ │ ├── openclaw.rs # OpenClaw SKILL.md adapter
│ │ ├── crewai.rs # CrewAI adapter (BaseTool, @tool)
│ │ ├── langchain.rs # LangChain adapter (@tool, BaseTool, langgraph)
│ │ ├── gpt_actions.rs # GPT Actions adapter (OpenAPI specs)
│ │ └── cursor_rules.rs # Cursor Rules adapter (.cursorrules files)
│ ├── parser/ # Language parsers
│ │ ├── mod.rs # Parser trait, ParsedFile, FunctionDef, CallSite
│ │ ├── python.rs # tree-sitter Python + regex patterns
│ │ ├── typescript.rs # tree-sitter TypeScript/TSX + regex fallback
│ │ ├── shell.rs # Regex-based shell parser
│ │ └── json_schema.rs # JSON Schema → ToolSurface
│ ├── analysis/ # Static analysis
│ │ ├── mod.rs # Module exports
│ │ ├── cross_file.rs # Cross-file sanitizer-aware validation (v0.2.2)
│ │ └── sensitivity.rs # Sensitive-name heuristics (pub(crate))
│ ├── rules/ # Detection engine
│ │ ├── mod.rs # RuleEngine, Detector trait
│ │ ├── finding.rs # Finding, Severity, Evidence structs
│ │ ├── registry.rs # Rule metadata registry
│ │ ├── policy.rs # Policy evaluation (.agentshield.toml)
│ │ └── builtin/ # 18 built-in detectors (SHIELD-001..018)
│ ├── output/ # Report formatters
│ │ ├── mod.rs # OutputFormat enum, render()
│ │ ├── console.rs # Plain text
│ │ ├── json.rs # JSON
│ │ ├── sarif.rs # SARIF 2.1.0
│ │ └── html.rs # Self-contained HTML
│ └── config/ # .agentshield.toml parsing (policy + scan sections)
├── tests/fixtures/ # Test fixtures (safe + vulnerable)
│ ├── mcp_servers/
│ │ ├── safe_calculator/ # Zero-finding baseline
│ │ ├── safe_filesystem/ # Cross-file validation test (v0.2.2)
│ │ ├── vuln_cmd_inject/ # SHIELD-001 true positive
│ │ ├── vuln_ssrf/ # SHIELD-003 true positive
│ │ └── vuln_cred_exfil/ # SHIELD-002 true positive
│ ├── crewai_project/ # CrewAI adapter test (v0.2.4)
│ └── langchain_project/ # LangChain adapter test (v0.2.4)
├── vscode/ # VS Code extension (v0.1.0)
│ ├── package.json # Extension manifest
│ ├── tsconfig.json # TypeScript config
│ └── src/ # Extension source (TypeScript)
│ ├── extension.ts # Activate, commands, auto-scan
│ ├── scanner.ts # Spawn binary, parse JSON
│ ├── diagnostics.ts # Finding → vscode.Diagnostic
│ └── types.ts # JSON interfaces (mirrors Rust)
├── .github/workflows/
│ ├── ci.yml # Test + clippy + fmt + smoke
│ └── release.yml # 5-platform binary builds
└── action.yml # GitHub Action (composite)
# Build
cargo build --release
# Test (212 tests)
cargo test
# Lint
cargo clippy -- -D warnings
cargo fmt --check
# Run CLI
cargo run -- scan tests/fixtures/mcp_servers/vuln_cmd_inject
cargo run -- scan . --ignore-tests --format html --output report.html
cargo run -- scan . --write-baseline baseline.json
cargo run -- scan . --baseline baseline.json
cargo run -- list-rules
cargo run -- suppress SHIELD-001 src/tools.py:42 --reason "accepted risk"
cargo run -- list-suppressions
cargo run -- certify . --output attestation.json
cargo run -- certify . --sign-key key.bin --output attestation.json- Adapters produce IR, detectors consume IR. Adding a new framework never changes any detector.
- All adapters run.
auto_detect_and_load()runs every matching adapter, not just the first. - ArgumentSource is the taint abstraction. Detectors check
is_tainted()— no full dataflow needed. - Policy is separate from detection. Detectors always run; policy decides what to report and whether to fail.
- Cross-file analysis runs between parsing and detection. Downgrades taint for functions that only receive sanitized input.
ScanTarget— unified IR with 5 surfaces (tool, execution, data, dependency, provenance)Finding— detector output with severity, confidence, location, evidence, remediationArgumentSource—Literal(safe),Parameter(tainted),EnvVar,Interpolated,Unknown,Sanitized(safe, v0.2.2)Detectortrait —metadata() -> RuleMetadata,run(&ScanTarget) -> Vec<Finding>PolicyVerdict— pass/fail with threshold and highest severityScanConfig—[scan]config section withignore_testsboolParsedFile— parser output withcommands,file_operations,network_operations,function_defs,call_sites,sanitized_varsFunctionDef— extracted function definition with name, params,is_exportedCallSite— function call with callee name, classified arguments, caller context
Adapters use a 3-phase pipeline:
Phase 1: Parse — each source file → ParsedFile (with FunctionDef, CallSite, sanitized_vars)
Phase 2: Analyze — apply_cross_file_sanitization() downgrades tainted params to Sanitized
Phase 3: Merge — combine all ParsedFiles into ScanTarget surfaces
This eliminates false positives from internal helpers that receive already-validated input:
// index.ts — handler validates input
const validPath = await validatePath(args.path); // sanitizer detected
const content = await readFileContent(validPath); // CallSite with Sanitized arg
// operations.ts — helper uses validated input
export async function readFileContent(filePath: string) {
return fs.readFile(filePath, 'utf-8'); // Parameter downgraded → no SHIELD-004
}The apply_cross_file_sanitization() function:
- Phase 1: Builds function def map (
name → file_index, params, is_exported) - Phase 2: Builds call-site map (
callee → Vec<argument_sources>) - Phase 3: For each function, checks if ALL call sites pass safe args (Literal or Sanitized) per parameter
- Phase 4: If all-safe, downgrades matching
ArgumentSource::ParametertoSanitizedin the callee's operations
Conservative rules:
- Exported functions with zero discovered call sites stay tainted
- If ANY call site passes a tainted argument, the parameter stays tainted
- Only one level deep (caller → callee, not recursive)
Sanitizer registry (is_sanitizer()): recognizes validatePath, path.resolve, os.path.realpath, parseInt, URL.parse, and pattern-based matches like validate*Path, sanitize*.
The --ignore-tests flag skips test files at the file-walking stage (before parsing). Available via:
- CLI:
agentshield scan . --ignore-tests - Config:
[scan] ignore_tests = truein.agentshield.toml - GitHub Action:
ignore-tests: trueinput - Library:
ScanOptions { ignore_tests: true, .. }
CLI flag overrides config (options.ignore_tests || config.scan.ignore_tests).
is_test_file() in src/adapter/mcp.rs matches:
- Directories:
test/,tests/,__tests__/,__pycache__/ - Suffixes:
.test.{ts,js,tsx,jsx,py},.spec.{ts,js,tsx,jsx} - Prefixes:
test_*.py(pytest) - Config:
conftest.py,jest.config.*,vitest.config.*,pytest.ini,setup.cfg
- Create
src/rules/builtin/your_detector.rs - Implement
Detectortrait (metadata()+run()) - Register in
src/rules/builtin/mod.rs→all_detectors() - Add tests in the same file
- Add fixture in
tests/fixtures/if applicable - Run
cargo test && cargo clippy -- -D warnings
- Create
src/adapter/your_framework.rs - Implement
Adaptertrait (framework(),detect(),load()) - Register in
src/adapter/mod.rs→all_adapters() detect()checks for framework-specific filesload()uses the 3-phase pipeline (parse → cross-file analysis → merge)- Reuse shared helpers from
mcp.rs:collect_source_files(),parse_dependencies(),parse_provenance()
Existing adapters: MCP (mcp.rs), OpenClaw (openclaw.rs), CrewAI (crewai.rs), LangChain (langchain.rs), GPT Actions (gpt_actions.rs), Cursor Rules (cursor_rules.rs)
thiserrorfor error types,?operator everywhere- No
unwrap()in production paths - tree-sitter for AST parsing, regex for pattern matching and fallback
- Tests use real fixture files under
tests/fixtures/ - Conventional Commits for git messages
- Parsers extract
FunctionDef,CallSite, andsanitized_varsfor cross-file analysis ArgumentSource::Sanitizedis the safe variant for cross-file validated params —is_tainted()returnsfalse- Findings include stable fingerprints (SHA-256 hash of rule+file+line+snippet) for baseline diffing and suppression
--baseline/--write-baselinefor CI noise reduction — only report new findingssuppress/list-suppressionsCLI commands manage.agentshield.tomlsuppressionscertifycommand generates DSSE attestation envelopes with optional Ed25519 signing--emit-egress-policyanalyzes scan results and generates a starter egress policy- PR inline annotations verified via agentshield-test PR #1 (IBVI-488)
| Version | Tests | Key Feature |
|---|---|---|
| 0.1.0 | 46 | 12 detectors, Python parser, MCP/OpenClaw adapters |
| 0.2.0 | 69 | TypeScript tree-sitter parser, Homebrew, GitHub Action |
| 0.2.1 | 69 | Async HTTP detection, GitPython, typosquat allowlist, Marketplace |
| 0.2.2 | 83 | Cross-file validation tracking (IBVI-482) |
| 0.2.3 | 83 | --ignore-tests flag, [scan] config section, 5-platform release, PR annotations verified |
| 0.2.4 | 95 | CrewAI + LangChain adapters (IBVI-486, -487) — 4 adapters total, shared helpers |
| 0.3.0 | ~120 | Stable finding fingerprints (SHA-256), versioned baseline file schema |
| 0.4.0 | ~140 | suppress + list-suppressions CLI, .agentshield.toml suppression entries |
| 0.5.0 | ~160 | Taint path upgrades (credential_exfil, prompt_injection), egress policy schema, --emit-egress-policy, --baseline/--write-baseline |
| 0.6.0 | ~180 | GPT Actions + Cursor Rules adapters — 6 adapters total |
| 0.7.0 | ~195 | Egress policy operator override layering for wrap command |
| 0.8.0 | 212 | certify command (DSSE attestation + Ed25519), 6 new detectors (SHIELD-013..018) |