agentsec-core — AgentSec pure-logic library
Pure-Rust logic crate. No rmcp / clap / GUI dependency — the umbrella
binary agentsec is the only transport adapter (see the agentsec
crate for CLI / MCP server / hook wiring).
This crate doc is the single source of truth for the threat model, module roster, and read-only invariants. Module-level docs reference the sections below by anchor (e.g. crate root §Threat surface × vector).
§Threat surface × vector
Defense is framed as a 2-axis matrix. The vertical axis is where the threat is observed, the horizontal axis is how the injection arrives. Modules cover the following cells:
| Surface | Vector | Module |
|---|---|---|
| L1 Agent config dir | V3 Config tampering | [scan] |
| L4 Network egress | V1 Prompt injection | [web] |
| L5 User-facing input | V1 Prompt injection | [paste] |
Surface key:
- L1 Agent config —
~/.claude/,~/.cursor/,.mcp.json, skills / agents / plugins dirs, dependency manifests, lockfiles,.env - L2 Dependency manifest —
package.json,Cargo.toml, lockfiles (covered by [scan] as a sub-axis of L1) - L3 Runtime process — Agent / MCP subprocess (not implemented)
- L4 Network egress — outbound URL fetch from Agent context
- L5 User-facing input — pasted text, prompt body, tool args
Vector key:
- V1 Prompt injection — instruction-override text in fetched URL / RAG / pasted content
- V2 Supply chain — typosquat / hijacked package (not implemented)
- V3 Config tampering — newly-installed / mutated config under L1
- V4 Runtime tampering — process injection (not implemented)
§Module roster
| Module path | Role |
|---|---|
[scan] |
Hash inventory of agent config + lockfiles; diff against the previous snapshot. |
[paste] |
Multi-layer decode + multi-pattern scan + unicode anomaly check; verdict Clean / Suspicious / Blocked. |
[web] |
URL fetch with body cap + 2-layer sanitize (regex then optional LLM) + <untrusted_content> envelope. |
[output] |
Render a [scan::ScanOutcome] as Markdown. |
[registry] |
Known-good MCP server registry (builtin + cache + net fetch). |
[plain_mode] |
Temporarily disable .mcp.json files via rename + stub. |
[emergency_stop] |
Enumerate Agent / MCP processes by name and SIGTERM them. |
[config] |
Edge-resolved Config (env → struct); the only env-reading code. |
[error] |
Crate-wide error enum. |
§Runtime data root
All persisted state lives under [config::Paths::home], resolved at
the binary's outer rim by [config::Config::from_env] as:
$AGENTSEC_HOMEif set (used by integration tests to redirect to a tempdir), else$HOME/.agentsec/, else./.agentsec/(last-resort fallback whenHOMEis unset).
Layout:
<home>/snapshots/<UTC-ts>.json — scan snapshots (scan::snapshot)
<home>/scans/last-session-start.txt — last SessionStart hook summary
<home>/paste_log/<UTC-ts>-<id>.json — paste verdicts
<home>/web_log/<UTC-ts>-<id>.json — sanitize results
§Environment variables
All env reads are funneled through [config::Config::from_env] (the
only function in this crate that touches std::env). Library
functions take &Config (or sub-references) as a parameter; they do
not consult the process environment at the point of use. See
[config] for the full mapping and the test-friendly
[config::Config::from_env_lookup] override.
§Read-only invariants
- [
scan] never mutates any scanned path; it only reads bytes and writes to<home>/snapshots/. - [
paste] never persists the raw input plaintext outside the JSON audit row under<home>/paste_log/. - [
web::fetch] enforces a 2 MiB body cap and 15 s timeout; oversized bodies are rejected, not truncated. - [
web::sanitize::semantic_layer] fails open: a missing API key or non-2xx response returns the regex-stripped input unchanged. The 1st (regex) layer alone is the floor of protection. - Symbolic links are not followed during [
scan::inventory::collect].