Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- refactor(sanitizer): extract content sanitization into new `zeph-sanitizer` crate (Epic #1973 Phase 1e)
- New `zeph-sanitizer` crate at Layer 2 with 6 core modules: `exfiltration`, `guardrail`, `memory_validation`, `pii`, `quarantine`, and lib exports
- Extracted 4,337 LOC from `zeph-core/src/sanitizer/` including `ContentSanitizer`, `ExfiltrationGuard`, `PiiFilter`, `MemoryWriteValidator`, `QuarantinedSummarizer`, and guardrail logic
- Clean direct imports throughout `zeph-core` and binary crates: `use zeph_sanitizer::*` (no re-export shim pattern)
- Feature flag `guardrail` propagated from `zeph-core` to `zeph-sanitizer`
- `zeph-core` re-exports all public types from `zeph-sanitizer` preserving existing import paths for downstream consumers

- refactor(experiments): extract experiments logic into new `zeph-experiments` crate (Epic #1973 Phase 1d)
- New `zeph-experiments` crate at Layer 2 with `ExperimentEngine`, `Evaluator`, `BenchmarkSet`, and all experiment-related types
- Moved: `ExperimentEngine`, `ExperimentSessionReport`, `Evaluator`, `JudgeOutput`, `CaseScore`, `EvalReport`, `EvalError`, `VariationGenerator`, `GridStep`, `Random`, `Neighborhood`, `ParameterRange`, `SearchSpace`, `ParameterKind`, `Variation`, `VariationValue`, `ExperimentResult`, `ExperimentSource`, `BenchmarkCase`, `BenchmarkSet`, `ConfigSnapshot`, `GenerationOverrides`
Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ zeph-skills = { path = "crates/zeph-skills", version = "0.15.3" }
zeph-tools = { path = "crates/zeph-tools", version = "0.15.3" }
zeph-tui = { path = "crates/zeph-tui", version = "0.15.3" }
zeph-vault = { path = "crates/zeph-vault", version = "0.15.3" }
zeph-sanitizer = { path = "crates/zeph-sanitizer", version = "0.15.3" }

[workspace.lints.rust]
unsafe_code = "deny"
Expand Down Expand Up @@ -223,6 +224,7 @@ zeph-index.workspace = true
zeph-llm.workspace = true
zeph-memory.workspace = true
zeph-mcp.workspace = true
zeph-sanitizer.workspace = true
zeph-scheduler = { workspace = true, optional = true }
zeph-skills.workspace = true
zeph-tools.workspace = true
Expand Down
3 changes: 2 additions & 1 deletion crates/zeph-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ candle = ["zeph-llm/candle"]
compression-guidelines = ["zeph-memory/compression-guidelines", "zeph-config/compression-guidelines"]
cuda = ["zeph-llm/cuda"]
experiments = ["dep:ordered-float", "zeph-memory/experiments", "zeph-config/experiments", "zeph-experiments/experiments"]
guardrail = ["zeph-config/guardrail"]
guardrail = ["zeph-config/guardrail", "zeph-sanitizer/guardrail"]
lsp-context = ["zeph-config/lsp-context"]
metal = ["zeph-llm/metal"]
mock = ["zeph-vault/mock"]
Expand Down Expand Up @@ -57,6 +57,7 @@ uuid = { workspace = true, features = ["v4", "serde"] }
zeph-common.workspace = true
zeph-config.workspace = true
zeph-vault.workspace = true
zeph-sanitizer.workspace = true
zeph-experiments.workspace = true
zeph-index.workspace = true
zeph-llm.workspace = true
Expand Down
15 changes: 7 additions & 8 deletions crates/zeph-core/src/agent/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,14 +425,13 @@ impl<C: Channel> Agent<C> {
#[must_use]
pub fn with_security(mut self, security: SecurityConfig, timeouts: TimeoutConfig) -> Self {
self.security.sanitizer =
crate::sanitizer::ContentSanitizer::new(&security.content_isolation);
self.security.exfiltration_guard = crate::sanitizer::exfiltration::ExfiltrationGuard::new(
zeph_sanitizer::ContentSanitizer::new(&security.content_isolation);
self.security.exfiltration_guard = zeph_sanitizer::exfiltration::ExfiltrationGuard::new(
security.exfiltration_guard.clone(),
);
self.security.pii_filter =
crate::sanitizer::pii::PiiFilter::new(security.pii_filter.clone());
self.security.pii_filter = zeph_sanitizer::pii::PiiFilter::new(security.pii_filter.clone());
self.security.memory_validator =
crate::sanitizer::memory_validation::MemoryWriteValidator::new(
zeph_sanitizer::memory_validation::MemoryWriteValidator::new(
security.memory_validation.clone(),
);
self.runtime.rate_limiter =
Expand Down Expand Up @@ -487,16 +486,16 @@ impl<C: Channel> Agent<C> {
#[must_use]
pub fn with_quarantine_summarizer(
mut self,
qs: crate::sanitizer::quarantine::QuarantinedSummarizer,
qs: zeph_sanitizer::quarantine::QuarantinedSummarizer,
) -> Self {
self.security.quarantine_summarizer = Some(qs);
self
}

#[cfg(feature = "guardrail")]
#[must_use]
pub fn with_guardrail(mut self, filter: crate::sanitizer::guardrail::GuardrailFilter) -> Self {
use crate::sanitizer::guardrail::GuardrailAction;
pub fn with_guardrail(mut self, filter: zeph_sanitizer::guardrail::GuardrailFilter) -> Self {
use zeph_sanitizer::guardrail::GuardrailAction;
let warn_mode = filter.action() == GuardrailAction::Warn;
self.security.guardrail = Some(filter);
self.update_metrics(|m| {
Expand Down
6 changes: 3 additions & 3 deletions crates/zeph-core/src/agent/context/assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use zeph_skills::loader::SkillMeta;
use zeph_skills::prompt::{format_skills_catalog, format_skills_prompt_compact};

use crate::redact::scrub_content;
use crate::sanitizer::{ContentSource, ContentSourceKind};
use zeph_sanitizer::{ContentSource, ContentSourceKind};

#[cfg(feature = "lsp-context")]
use super::super::LSP_NOTE_PREFIX;
Expand Down Expand Up @@ -811,8 +811,8 @@ impl<C: Channel> Agent<C> {
"memory_retrieval",
"Content quarantined, facts extracted",
);
let escaped = crate::sanitizer::ContentSanitizer::escape_delimiter_tags(&facts);
msg.content = crate::sanitizer::ContentSanitizer::apply_spotlight(
let escaped = zeph_sanitizer::ContentSanitizer::escape_delimiter_tags(&facts);
msg.content = zeph_sanitizer::ContentSanitizer::apply_spotlight(
&escaped,
&sanitized.source,
&flags,
Expand Down
20 changes: 9 additions & 11 deletions crates/zeph-core/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use crate::config::{SecurityConfig, SkillPromptMode, TimeoutConfig};
use crate::context::{
ContextBudget, EnvironmentContext, build_system_prompt, build_system_prompt_with_instructions,
};
use crate::sanitizer::ContentSanitizer;
use zeph_sanitizer::ContentSanitizer;

use message_queue::{MAX_AUDIO_BYTES, MAX_IMAGE_BYTES, detect_image_mime};
#[cfg(feature = "context-compression")]
Expand Down Expand Up @@ -327,19 +327,17 @@ impl<C: Channel> Agent<C> {
reload_state: None,
},
security: SecurityState {
sanitizer: ContentSanitizer::new(
&crate::sanitizer::ContentIsolationConfig::default(),
),
sanitizer: ContentSanitizer::new(&zeph_sanitizer::ContentIsolationConfig::default()),
quarantine_summarizer: None,
exfiltration_guard: crate::sanitizer::exfiltration::ExfiltrationGuard::new(
crate::sanitizer::exfiltration::ExfiltrationGuardConfig::default(),
exfiltration_guard: zeph_sanitizer::exfiltration::ExfiltrationGuard::new(
zeph_sanitizer::exfiltration::ExfiltrationGuardConfig::default(),
),
flagged_urls: std::collections::HashSet::new(),
pii_filter: crate::sanitizer::pii::PiiFilter::new(
crate::sanitizer::pii::PiiFilterConfig::default(),
pii_filter: zeph_sanitizer::pii::PiiFilter::new(
zeph_sanitizer::pii::PiiFilterConfig::default(),
),
memory_validator: crate::sanitizer::memory_validation::MemoryWriteValidator::new(
crate::sanitizer::memory_validation::MemoryWriteValidationConfig::default(),
memory_validator: zeph_sanitizer::memory_validation::MemoryWriteValidator::new(
zeph_sanitizer::memory_validation::MemoryWriteValidationConfig::default(),
),
#[cfg(feature = "guardrail")]
guardrail: None,
Expand Down Expand Up @@ -2936,7 +2934,7 @@ impl<C: Channel> Agent<C> {
// Guardrail: LLM-based prompt injection pre-screening at the user input boundary.
#[cfg(feature = "guardrail")]
if let Some(ref guardrail) = self.security.guardrail {
use crate::sanitizer::guardrail::GuardrailVerdict;
use zeph_sanitizer::guardrail::GuardrailVerdict;
let verdict = guardrail.check(trimmed).await;
match &verdict {
GuardrailVerdict::Flagged { reason, .. } => {
Expand Down
12 changes: 6 additions & 6 deletions crates/zeph-core/src/agent/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ use crate::context::EnvironmentContext;
use crate::cost::CostTracker;
use crate::instructions::{InstructionBlock, InstructionEvent, InstructionReloadState};
use crate::metrics::MetricsSnapshot;
use crate::sanitizer::ContentSanitizer;
use crate::sanitizer::quarantine::QuarantinedSummarizer;
use crate::vault::Secret;
use zeph_memory::TokenCounter;
use zeph_memory::semantic::SemanticMemory;
use zeph_sanitizer::ContentSanitizer;
use zeph_sanitizer::quarantine::QuarantinedSummarizer;
use zeph_skills::matcher::SkillMatcherBackend;
use zeph_skills::registry::SkillRegistry;
use zeph_skills::watcher::SkillEvent;
Expand Down Expand Up @@ -112,13 +112,13 @@ pub(crate) struct FeedbackState {
pub(crate) struct SecurityState {
pub(crate) sanitizer: ContentSanitizer,
pub(crate) quarantine_summarizer: Option<QuarantinedSummarizer>,
pub(crate) exfiltration_guard: crate::sanitizer::exfiltration::ExfiltrationGuard,
pub(crate) exfiltration_guard: zeph_sanitizer::exfiltration::ExfiltrationGuard,
pub(crate) flagged_urls: std::collections::HashSet<String>,
pub(crate) pii_filter: crate::sanitizer::pii::PiiFilter,
pub(crate) memory_validator: crate::sanitizer::memory_validation::MemoryWriteValidator,
pub(crate) pii_filter: zeph_sanitizer::pii::PiiFilter,
pub(crate) memory_validator: zeph_sanitizer::memory_validation::MemoryWriteValidator,
/// LLM-based prompt injection pre-screener (opt-in).
#[cfg(feature = "guardrail")]
pub(crate) guardrail: Option<crate::sanitizer::guardrail::GuardrailFilter>,
pub(crate) guardrail: Option<zeph_sanitizer::guardrail::GuardrailFilter>,
}

/// Groups debug/diagnostics subsystems (dumper, trace collector, anomaly detector, logging config).
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-core/src/agent/tool_execution/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use zeph_tools::executor::{ToolError, ToolOutput};
use super::super::{Agent, DOOM_LOOP_WINDOW, format_tool_output};
use super::{AnomalyOutcome, doom_loop_hash, first_tool_name};
use crate::channel::{Channel, ToolOutputEvent, ToolStartEvent};
use crate::sanitizer::{ContentSource, ContentSourceKind}; // already imported for tool output sanitization
use tokio_stream::StreamExt;
use tracing::Instrument;
use zeph_sanitizer::{ContentSource, ContentSourceKind}; // already imported for tool output sanitization
use zeph_skills::evolution::FailureKind;

impl<C: Channel> Agent<C> {
Expand Down
10 changes: 5 additions & 5 deletions crates/zeph-core/src/agent/tool_execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use zeph_llm::provider::{LlmProvider, Message, MessageMetadata, Role, ToolDefini
use super::Agent;
use crate::channel::Channel;
use crate::redact::redact_secrets;
use crate::sanitizer::{ContentSource, ContentSourceKind};
use zeph_sanitizer::{ContentSource, ContentSourceKind};
use zeph_skills::loader::Skill;

/// Prefix used in the overflow notice appended to tool outputs that exceed the size threshold.
Expand Down Expand Up @@ -308,7 +308,7 @@ impl<C: Channel> Agent<C> {
// Collect URLs from the SANITIZED content (not raw body) for validate_tool_call.
// Using sanitized.body ensures only URLs the LLM actually sees are tracked,
// avoiding false-positive SuspiciousToolUrl warnings for truncated/stripped content.
let urls = crate::sanitizer::exfiltration::extract_flagged_urls(&sanitized.body);
let urls = zeph_sanitizer::exfiltration::extract_flagged_urls(&sanitized.body);
self.security.flagged_urls.extend(urls);
}
if sanitized.was_truncated {
Expand All @@ -334,9 +334,9 @@ impl<C: Channel> Agent<C> {
tool_name,
"Content quarantined, facts extracted",
);
let escaped = crate::sanitizer::ContentSanitizer::escape_delimiter_tags(&facts);
let escaped = zeph_sanitizer::ContentSanitizer::escape_delimiter_tags(&facts);
return (
crate::sanitizer::ContentSanitizer::apply_spotlight(
zeph_sanitizer::ContentSanitizer::apply_spotlight(
&escaped,
&sanitized.source,
&flags,
Expand Down Expand Up @@ -382,7 +382,7 @@ impl<C: Channel> Agent<C> {

#[cfg(feature = "guardrail")]
async fn apply_guardrail_to_tool_output(&self, mut body: String, tool_name: &str) -> String {
use crate::sanitizer::guardrail::GuardrailVerdict;
use zeph_sanitizer::guardrail::GuardrailVerdict;
let Some(ref guardrail) = self.security.guardrail else {
return body;
};
Expand Down
6 changes: 3 additions & 3 deletions crates/zeph-core/src/agent/tool_execution/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use super::super::Agent;
use super::{AnomalyOutcome, retry_backoff_ms, tool_args_hash, tool_def_to_definition};
use crate::channel::{Channel, StopHint, ToolOutputEvent, ToolStartEvent};
use crate::overflow_tools::OverflowToolExecutor;
use crate::sanitizer::{ContentSource, ContentSourceKind};
use tracing::Instrument;
use zeph_llm::provider::MAX_TOKENS_TRUNCATION_MARKER;
use zeph_sanitizer::{ContentSource, ContentSourceKind};
use zeph_skills::evolution::FailureKind;
use zeph_tools::executor::ToolCall;

Expand Down Expand Up @@ -1558,8 +1558,8 @@ impl<C: Channel> Agent<C> {
.sanitizer
.sanitize(
&summary,
crate::sanitizer::ContentSource::new(
crate::sanitizer::ContentSourceKind::WebScrape,
zeph_sanitizer::ContentSource::new(
zeph_sanitizer::ContentSourceKind::WebScrape,
),
)
.body;
Expand Down
Loading
Loading