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//! | [`diagnostics`] | Observability: info / status / recent_activity / doctor. |
54//! | [`emergency_stop`] | Enumerate Agent / MCP processes by name and SIGTERM them. |
55//! | [`config`] | Edge-resolved `Config` (env → struct); the only env-reading code. |
56//! | [`error`] | Crate-wide error enum. |
57//!
58//! ## §Runtime data root
59//!
60//! All persisted state lives under [`config::Paths::home`], resolved at
61//! the binary's outer rim by [`config::Config::from_env`] as:
62//!
63//! 1. `$AGENTSEC_HOME` if set (used by integration tests to redirect to a
64//! tempdir), else
65//! 2. `$HOME/.agentsec/`, else
66//! 3. `./.agentsec/` (last-resort fallback when `HOME` is unset).
67//!
68//! Layout:
69//!
70//! ```text
71//! <home>/snapshots/<UTC-ts>.json — scan snapshots (scan::snapshot)
72//! <home>/scans/last-session-start.txt — last SessionStart hook summary
73//! <home>/paste_log/<UTC-ts>-<id>.json — paste verdicts
74//! <home>/web_log/<UTC-ts>-<id>.json — sanitize results
75//! ```
76//!
77//! ## §Environment variables
78//!
79//! All env reads are funneled through [`config::Config::from_env`] (the
80//! *only* function in this crate that touches `std::env`). Library
81//! functions take `&Config` (or sub-references) as a parameter; they do
82//! not consult the process environment at the point of use. See
83//! [`config`] for the full mapping and the test-friendly
84//! [`config::Config::from_env_lookup`] override.
85//!
86//! ## §Read-only invariants
87//!
88//! - [`scan`] never mutates any scanned path; it only reads bytes and writes
89//! to `<home>/snapshots/`.
90//! - [`paste`] never persists the raw input plaintext outside the JSON audit
91//! row under `<home>/paste_log/`.
92//! - [`web::fetch`] enforces a 2 MiB body cap and 15 s timeout; oversized
93//! bodies are rejected, not truncated.
94//! - [`web::sanitize::semantic_layer`] **fails open**: a missing API key or
95//! non-2xx response returns the regex-stripped input unchanged. The 1st
96//! (regex) layer alone is the floor of protection.
97//! - Symbolic links are not followed during [`scan::inventory::collect`].
98pub mod config;
99pub mod diagnostics;
100pub mod emergency_stop;
101pub mod error;
102pub mod installer;
103pub mod output;
104pub mod paste;
105pub mod plain_mode;
106pub mod registry;
107pub mod scan;
108pub mod web;
109
110pub use config::{Config, LlmConfig, Paths};
111pub use error::Error;