Security Primitives
Cross-cutting security layers including SSRF protection, content sanitization, security headers, runtime guard, and persistent audit logging.
The security module provides cross-cutting security primitives covering Aleph's outbound requests, HTTP response headers, injection detection, and runtime orchestration. It complements the gateway's auth / identity system — the latter answers "who can connect", the former answers "what may not leave Aleph, and what must not be written to a log".
Design Philosophy
- Defense in depth — multiple independent checks at different layers
- Non-blocking audit — security events land via an async channel and never block the hot path
- Configurable strictness — policies can be relaxed or tightened per deployment
SSRF Protection
The SSRF engine lives at src/security/ssrf/ and validates every outbound URL before the request is sent:
src/security/ssrf/
├── mod.rs — public API: validate_url, validate_url_async, safe_fetch, SsrfError
├── policy.rs — SsrfPolicy configuration
├── ip.rs — IPv4 / IPv6 classification and blocked ranges
├── hostname.rs — hostname blocklist, allowlist, legacy IP literal detection
├── dns.rs — async DNS resolution with address pinning
└── fetch.rs — safe_fetch (with redirect chain validation)Blocks:
- RFC1918 private networks (10/8, 172.16/12, 192.168/16)
- Loopback (127/8,
::1) - Link-local + cloud metadata (169.254/16)
- CGNAT (100.64/10)
- Unique local IPv6 (fc00::/7), link-local IPv6, IPv6 multicast
- Legacy IPv4 literals (octal, hex, decimal, short form)
- IPv4-embedded IPv6 (IPv4-mapped / NAT64 / 6to4 / Teredo / IPv4-compatible)
- Hostname blocklist
- Embedded URL credentials
DNS pinning: after async resolution, every returned IP is validated; the caller pins the pre-validated address via reqwest::Client::builder().resolve(host, validated_addr), closing the resolve-to-connect TOCTOU window.
Allowlist support: exact hosts or wildcard subdomains (*.example.com).
safe_fetch (fetch.rs) is the single entry point for every outbound HTTP request: URL parse → legacy-literal rejection → credential-obfuscation detection → hostname / allowlist → IP literal or async DNS + all-IP validate → DNS pinning → issue request (redirect::Policy::none()) → redirect loop: extract Location, repeat the steps above, strip Authorization / Cookie / Proxy-Authorization on cross-origin redirects, dedupe the URL set, exceed max_redirects ⇒ SsrfError::TooManyRedirects.
Callers include the web-fetch tool, webhook delivery, media downloaders, MCP HTTP transport, and browser navigation.
Content Sanitization
src/security/content_sanitizer.rs wraps external content with boundary markers before it enters the LLM:
pub fn wrap_external_content(content: &str, source: ContentSource) -> String;Supported sources: web fetch / MCP tool output / webhook payloads / email / browser content / user upload.
Detects: injection patterns / tokenizer marker manipulation / model format marker spoofing / homoglyph attacks (Unicode normalization) / invisible characters (unicode_guard.rs) / token-level injection (injection_patterns.rs). Hits are flagged rather than blocked — the LLM decides trust (see R8 — LLM Sovereignty).
The prompt-context inputs that feed into the marker are escaped first too — transcript / focus / prior-summary text gets sanitized before being concatenated with the prompt, so a forged boundary marker cannot bleed into the next session.
Security Headers
src/security/headers.rs is a Tower layer that injects security headers on every HTTP response:
| Header | Value |
|---|---|
Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; ... |
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 0 |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=() |
Cache-Control | no-store (except static assets) |
Static assets (.js, .css, .wasm, .png, etc.) are exempt from Cache-Control: no-store.
Runtime Security Guard
src/security/runtime_guard.rs::RuntimeSecurityGuard orchestrates every check during the agent loop:
pub struct SecurityGuardConfig {
pub pii_filtering: bool,
pub content_sanitization: bool,
pub leak_detection: bool,
pub secret_injection: bool,
pub audit_enabled: bool,
pub custom_leak_patterns: Vec<CustomLeakPattern>,
}Outbound (process_outbound):
- Placeholder extraction + secret resolution (
{{secret:NAME}}viaAsyncSecretResolver) - Leak detection (the exec scanner + the secret scanner) —
Blockdenies,Redactrewrites,Warnrecords - PII filtering via
PiiEngine::filter_with_platform - Placeholder substitution (longest-first ordering, so
{{secret:api_key}}is replaced before{{secret:api}})
Inbound (process_inbound):
- Leak detection —
Blockfindings are returned throughSecretMasker::mask, never raw - PII filtering — strips sensitive data the LLM echoes back before it reaches the host
Lock discipline: the PII engine uses crate::sync_primitives::RwLock (synchronous, short critical section); the leak detectors use tokio::sync::Mutex (held across await). The audit channel never blocks the hot path — try_send failures are counted and logged periodically rather than awaited.
Audit Log
src/security/audit.rs::SecurityAuditLog is a bounded tokio::sync::mpsc channel; a background task spawn_audit_drain (audit_drain.rs) drains entries and writes them to the SQLite security_audit_log table via SecurityStore::insert_audit_entry, applying a periodic retention purge at DEFAULT_RETENTION_SECS (30 days).
Event types:
pub enum AuditEventType {
AuthFailure,
RateLimited,
SsrfBlocked,
ExecBlocked,
ExecApprovalDenied,
InvisibleCharsDetected,
TokenizerMarkerScrubbed,
InjectionPatternDetected,
EnvInjectionDetected,
PathTraversalBlocked,
PermissionDenied,
PiiDetected,
LeakWarning,
}Severities: Critical / Warn / Info.
Non-blocking: SecurityAuditLog::log calls try_send; when the channel is full, entries are dropped with a warning and the running drop count is exposed. new_with_audit returns (guard, receiver) so the receiver end is owned by the caller and never silently closed — without that, calling new would consume and drop the receiver, closing the channel before any entry can land.
Pairing Single-Sourced
The channel-local pairing store is retracted: access / pairing / allowlists are single-sourced on the inbound router. Telegram, Discord, and iMessage share one pairing store.
Command-Injection Fixes
- Desktop
open/launchare dispatched throughShellExecuteW, not a shell - Bundled-content extraction refuses symlink planting via
symlink_metadata - Skill-download path traversal is closed and the scanner file size is capped
Cluster file.write No TOCTOU
Cluster file.write no longer has a time-of-check-to-time-of-use window between the existence check and the write.
Certificate Approval Fingerprint Match
Certificate approval must match a fingerprint to authorize; the WebView microphone permission must be origin-restricted on Linux / Windows.
Path Safety
- Symlink-safe delete / move
- Glob / deny re-checks
- Collision de-dup
- Per-path locks
- Hardened path deny gate (
%APPDATA%expansion, system / proc guards) apply_patchforward cursor that fails honestly
Code Location
src/security/mod.rs— module entrysrc/security/ssrf/— SSRF engine (policy / ip / hostname / dns / fetch)src/security/content_sanitizer.rs— injection detectionsrc/security/headers.rs— HTTP security headerssrc/security/runtime_guard.rs— runtime orchestrationsrc/security/audit.rs— audit log channelsrc/security/audit_drain.rs— audit persistence tasksrc/security/unicode_guard.rs— invisible-character detectionsrc/security/injection_patterns.rs— token-level injection patternssrc/security/safe_regex.rs— regex literal compile helpersrc/security/context_id_hasher.rs— context ID hashingsrc/security/secret_env.rs/secret_equal.rs— credential environment and constant-time comparesrc/security/dangerous_tools.rs—tools.invokedenylist
See Also
- PII Protection — PII detection and redaction
- Secret Management — encrypted credential storage
- Approval — capability-domain approval engine
Capability System
Capability declarations, plugin permissions, action-aware tool enforcement, and the runtime capability ledger.
PII Protection
Gateway-level PII filtering engine that detects and redacts personally identifiable information before it reaches LLM API providers, with per-category and per-platform policy.