harn_guard/lib.rs
1//! `harn-guard` — the downloadable on-device prompt-injection classifier for
2//! Harn (security Layer 2).
3//!
4//! This crate is the **management layer**: a catalog of upstream, already-hosted
5//! models ([`catalog`]), an on-disk store that installs and verifies them
6//! ([`store`]), and selector resolution ([`resolve_dir`]). It hosts nothing,
7//! bundles no weights, and makes no network calls itself — the CLI downloads
8//! from the catalog's upstream URLs on the user's machine and hands the bytes to
9//! [`GuardStore::install`] for SHA-256 verification + atomic install.
10//!
11//! The heavy inference runtime (ONNX) lives behind the off-by-default `neural`
12//! feature, so the default binary never links a model runtime. When built
13//! without it, [`load_classifier`] is a no-op returning `None` and the runtime
14//! keeps using the always-available built-in heuristic classifier. When built
15//! with it, a host installs [`load_classifier`] into the runtime's lazy loader
16//! seam (`harn_vm::security::set_injection_classifier_loader`), which fires the
17//! first time a `local-ml` policy scores untrusted content.
18
19pub mod catalog;
20mod error;
21#[cfg(any(feature = "neural", test))]
22mod neural_math;
23pub mod resolve;
24pub mod store;
25
26#[cfg(feature = "neural")]
27mod neural;
28
29pub use catalog::{CatalogFile, CatalogModel, ModelFormat, DEFAULT_MODEL};
30pub use error::{GuardError, Result};
31pub use resolve::resolve_dir;
32pub use store::{sha256_hex, GuardStore, Manifest, ManifestFile};
33
34#[cfg(feature = "neural")]
35pub use neural::OnnxInjectionClassifier;
36
37/// Load the installed model named/located by `selector` into a boxed
38/// [`InjectionClassifier`](harn_vm::security::InjectionClassifier), or `None` if
39/// the neural backend is unavailable, the model is not installed, or it fails to
40/// load. Never panics or registers anything itself — the caller (typically the
41/// runtime's lazy loader seam) decides what to do with the result.
42///
43/// Without the `neural` feature this always returns `None`, so the built-in
44/// heuristic classifier stays active and no model runtime is linked.
45#[cfg(feature = "neural")]
46pub fn load_classifier(
47 base_dir: &std::path::Path,
48 selector: &str,
49) -> Option<Box<dyn harn_vm::security::InjectionClassifier>> {
50 let store = GuardStore::new(base_dir);
51 let dir = match resolve_dir(&store, selector) {
52 Ok(Some(dir)) => dir,
53 Ok(None) => return None,
54 Err(error) => {
55 eprintln!("[harn-guard] cannot resolve guard model `{selector}`: {error}");
56 return None;
57 }
58 };
59
60 // Prefer the installed manifest's recorded id + format; fall back to ONNX
61 // for a bring-your-own directory selector.
62 let (model_id, format) = match store.read_manifest(selector) {
63 Ok(manifest) => (manifest.name, manifest.format),
64 Err(_) => (selector.to_owned(), ModelFormat::Onnx),
65 };
66
67 match neural::OnnxInjectionClassifier::load(&dir, &model_id, format) {
68 Ok(classifier) => Some(Box::new(classifier)),
69 Err(error) => {
70 eprintln!("[harn-guard] failed to load guard model `{model_id}`: {error}");
71 None
72 }
73 }
74}
75
76/// Stub for builds without the `neural` feature: the runtime keeps the heuristic.
77#[cfg(not(feature = "neural"))]
78pub fn load_classifier(
79 _base_dir: &std::path::Path,
80 _selector: &str,
81) -> Option<Box<dyn harn_vm::security::InjectionClassifier>> {
82 None
83}