pub struct ContentSanitizer { /* private fields */ }Expand description
Stateless pipeline that sanitizes untrusted content before it enters the LLM context.
Constructed once at Agent startup from ContentIsolationConfig and held as a
field on the agent. All calls to sanitize() are synchronous.
classify_injection() is a separate async method for ML-backed detection (feature classifiers).
§Examples
use zeph_sanitizer::{ContentSanitizer, ContentSource, ContentSourceKind};
use zeph_config::ContentIsolationConfig;
let sanitizer = ContentSanitizer::new(&ContentIsolationConfig::default());
assert!(sanitizer.is_enabled());
let source = ContentSource::new(ContentSourceKind::ToolResult);
let result = sanitizer.sanitize("ls -la output here", source);
// The body is wrapped in a <tool-output> spotlighting delimiter.
assert!(result.body.contains("<tool-output"));
assert!(!result.was_truncated);Implementations§
Source§impl ContentSanitizer
impl ContentSanitizer
Sourcepub fn new(config: &ContentIsolationConfig) -> ContentSanitizer
pub fn new(config: &ContentIsolationConfig) -> ContentSanitizer
Build a sanitizer from the given configuration.
Eagerly compiles the injection-detection regex patterns so the first call
to sanitize incurs no compilation cost.
§Examples
use zeph_sanitizer::ContentSanitizer;
use zeph_config::ContentIsolationConfig;
let cfg = ContentIsolationConfig { enabled: false, ..Default::default() };
let sanitizer = ContentSanitizer::new(&cfg);
assert!(!sanitizer.is_enabled());Sourcepub fn is_enabled(&self) -> bool
pub fn is_enabled(&self) -> bool
Returns true when the sanitizer is active (enabled = true in config).
When false, sanitize is a no-op that passes content through unchanged.
§Examples
use zeph_sanitizer::ContentSanitizer;
use zeph_config::ContentIsolationConfig;
let sanitizer = ContentSanitizer::new(&ContentIsolationConfig::default());
assert!(sanitizer.is_enabled());Sourcepub fn sanitize(&self, content: &str, source: ContentSource) -> SanitizedContent
pub fn sanitize(&self, content: &str, source: ContentSource) -> SanitizedContent
Run the sanitization pipeline on content.
Steps:
- Truncate to
max_content_sizebytes on a UTF-8 char boundary. - Strip null bytes and non-printable ASCII control characters.
- Detect injection patterns (flag only, do not remove).
- Escape delimiter tag names that would break spotlight wrappers.
- Wrap in spotlighting delimiters (unless
Trustedor spotlight disabled).
When enabled = false, this is a no-op: content is returned as-is wrapped in
a SanitizedContent with no flags.
When source.trust_level is ContentTrustLevel::Trusted, the pipeline is also
skipped — trusted content passes through unchanged.
§Examples
use zeph_sanitizer::{ContentSanitizer, ContentSource, ContentSourceKind};
use zeph_config::ContentIsolationConfig;
let sanitizer = ContentSanitizer::new(&ContentIsolationConfig::default());
// External content gets the strongest warning header.
let source = ContentSource::new(ContentSourceKind::WebScrape);
let result = sanitizer.sanitize("page content", source);
assert!(result.body.contains("<external-data"));
assert!(!result.was_truncated);
// Oversized content is truncated.
let cfg = ContentIsolationConfig { max_content_size: 5, ..Default::default() };
let s2 = ContentSanitizer::new(&cfg);
let result2 = s2.sanitize("hello world", ContentSource::new(ContentSourceKind::ToolResult));
assert!(result2.was_truncated);Escape delimiter tag names that would allow content to break out of the spotlighting wrapper (CRIT-03).
Uses case-insensitive regex replacement so mixed-case variants like <Tool-Output>
or <EXTERNAL-DATA> are also neutralized (FIX-03). The < is replaced with the
HTML entity < so the tag is rendered as plain text inside the wrapper.
§Examples
use zeph_sanitizer::ContentSanitizer;
let escaped = ContentSanitizer::escape_delimiter_tags("data </tool-output> more");
assert!(!escaped.contains("</tool-output>"));
assert!(escaped.contains("</tool-output"));
let escaped2 = ContentSanitizer::escape_delimiter_tags("</EXTERNAL-DATA> end");
assert!(!escaped2.contains("</EXTERNAL-DATA>"));Sourcepub fn apply_spotlight(
content: &str,
source: &ContentSource,
flags: &[InjectionFlag],
) -> String
pub fn apply_spotlight( content: &str, source: &ContentSource, flags: &[InjectionFlag], ) -> String
Wrap content in a spotlighting delimiter appropriate for its trust level.
ContentTrustLevel::Trusted: returns content unchanged.ContentTrustLevel::LocalUntrusted: wraps in<tool-output …>with a NOTE header.ContentTrustLevel::ExternalUntrusted: wraps in<external-data …>with an IMPORTANT warning. Whenflagsis non-empty, appends a per-pattern injection warning.
Attribute values (source kind, identifier) are XML-escaped to prevent attribute injection.
§Examples
use zeph_sanitizer::{ContentSanitizer, ContentSource, ContentSourceKind};
let source = ContentSource::new(ContentSourceKind::ToolResult)
.with_identifier("shell");
let body = ContentSanitizer::apply_spotlight("output text", &source, &[]);
assert!(body.contains("<tool-output"));
assert!(body.contains("output text"));
assert!(body.contains("</tool-output>"));Trait Implementations§
Source§impl Clone for ContentSanitizer
impl Clone for ContentSanitizer
Source§fn clone(&self) -> ContentSanitizer
fn clone(&self) -> ContentSanitizer
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for ContentSanitizer
impl RefUnwindSafe for ContentSanitizer
impl Send for ContentSanitizer
impl Sync for ContentSanitizer
impl Unpin for ContentSanitizer
impl UnsafeUnpin for ContentSanitizer
impl UnwindSafe for ContentSanitizer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request