agentsec_core/lib.rs
1//! # `agentsec-core` — AgentSec pure-logic library
2//!
3//! Pure-Rust logic crate. No rmcp / clap / GUI dependency — the umbrella
4//! binary `agentsec` is the only transport adapter (see the `agentsec`
5//! crate for CLI / MCP server / hook wiring).
6//!
7//! This crate doc is the single source of truth for the threat model,
8//! module roster, and read-only invariants. Module-level docs reference
9//! the sections below by anchor (e.g. *crate root §Threat surface × vector*).
10//!
11//! ---
12//!
13//! ## §Threat surface × vector
14//!
15//! Defense is framed as a 2-axis matrix. The vertical axis is **where**
16//! the threat is observed, the horizontal axis is **how** the injection
17//! arrives. Modules cover the following cells:
18//!
19//! | Surface | Vector | Module |
20//! |----------------------|----------------------|---------------|
21//! | L1 Agent config dir | V3 Config tampering | [`scan`] |
22//! | L4 Network egress | V1 Prompt injection | [`web`] |
23//! | L5 User-facing input | V1 Prompt injection | [`paste`] |
24//!
25//! Surface key:
26//!
27//! - **L1 Agent config** — `~/.claude/`, `~/.cursor/`, `.mcp.json`, skills /
28//! agents / plugins dirs, dependency manifests, lockfiles, `.env`
29//! - **L2 Dependency manifest** — `package.json`, `Cargo.toml`, lockfiles
30//! (covered by [`scan`] as a sub-axis of L1)
31//! - **L3 Runtime process** — Agent / MCP subprocess (not implemented)
32//! - **L4 Network egress** — outbound URL fetch from Agent context
33//! - **L5 User-facing input** — pasted text, prompt body, tool args
34//!
35//! Vector key:
36//!
37//! - **V1 Prompt injection** — instruction-override text in fetched URL / RAG
38//! / pasted content
39//! - **V2 Supply chain** — typosquat / hijacked package (not implemented)
40//! - **V3 Config tampering** — newly-installed / mutated config under L1
41//! - **V4 Runtime tampering** — process injection (not implemented)
42//!
43//! ## §Module roster
44//!
45//! | Module path | Role |
46//! |--------------------|---------------------------------------------------|
47//! | [`scan`] | Hash inventory of agent config + lockfiles; diff against the previous snapshot. |
48//! | [`paste`] | Multi-layer decode + multi-pattern scan + unicode anomaly check; verdict Clean / Suspicious / Blocked. |
49//! | [`web`] | URL fetch with body cap + 2-layer sanitize (regex then optional LLM) + `<untrusted_content>` envelope. |
50//! | [`output`] | Render a [`scan::ScanOutcome`] as Markdown. |
51//! | [`registry`] | Known-good MCP server registry (builtin + cache + net fetch). |
52//! | [`plain_mode`] | Temporarily disable `.mcp.json` files via rename + stub. |
53//! | [`emergency_stop`] | Enumerate Agent / MCP processes by name and SIGTERM them. |
54//! | [`config`] | Edge-resolved `Config` (env → struct); the only env-reading code. |
55//! | [`error`] | Crate-wide error enum. |
56//!
57//! ## §Runtime data root
58//!
59//! All persisted state lives under [`config::Paths::home`], resolved at
60//! the binary's outer rim by [`config::Config::from_env`] as:
61//!
62//! 1. `$AGENTSEC_HOME` if set (used by integration tests to redirect to a
63//! tempdir), else
64//! 2. `$HOME/.agentsec/`, else
65//! 3. `./.agentsec/` (last-resort fallback when `HOME` is unset).
66//!
67//! Layout:
68//!
69//! ```text
70//! <home>/snapshots/<UTC-ts>.json — scan snapshots (scan::snapshot)
71//! <home>/scans/last-session-start.txt — last SessionStart hook summary
72//! <home>/paste_log/<UTC-ts>-<id>.json — paste verdicts
73//! <home>/web_log/<UTC-ts>-<id>.json — sanitize results
74//! ```
75//!
76//! ## §Environment variables
77//!
78//! All env reads are funneled through [`config::Config::from_env`] (the
79//! *only* function in this crate that touches `std::env`). Library
80//! functions take `&Config` (or sub-references) as a parameter; they do
81//! not consult the process environment at the point of use. See
82//! [`config`] for the full mapping and the test-friendly
83//! [`config::Config::from_env_lookup`] override.
84//!
85//! ## §Read-only invariants
86//!
87//! - [`scan`] never mutates any scanned path; it only reads bytes and writes
88//! to `<home>/snapshots/`.
89//! - [`paste`] never persists the raw input plaintext outside the JSON audit
90//! row under `<home>/paste_log/`.
91//! - [`web::fetch`] enforces a 2 MiB body cap and 15 s timeout; oversized
92//! bodies are rejected, not truncated.
93//! - [`web::sanitize::semantic_layer`] **fails open**: a missing API key or
94//! non-2xx response returns the regex-stripped input unchanged. The 1st
95//! (regex) layer alone is the floor of protection.
96//! - Symbolic links are not followed during [`scan::inventory::collect`].
97pub mod config;
98pub mod emergency_stop;
99pub mod error;
100pub mod output;
101pub mod paste;
102pub mod plain_mode;
103pub mod registry;
104pub mod scan;
105pub mod web;
106
107pub use config::{Config, LlmConfig, Paths};
108pub use error::Error;