Aleph
Concepts

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

  1. Defense in depth — multiple independent checks at different layers
  2. Non-blocking audit — security events land via an async channel and never block the hot path
  3. 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_redirectsSsrfError::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:

HeaderValue
Content-Security-Policydefault-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; ...
Strict-Transport-Securitymax-age=31536000; includeSubDomains
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
X-XSS-Protection0
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policycamera=(), microphone=(), geolocation=()
Cache-Controlno-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):

  1. Placeholder extraction + secret resolution ({{secret:NAME}} via AsyncSecretResolver)
  2. Leak detection (the exec scanner + the secret scanner) — Block denies, Redact rewrites, Warn records
  3. PII filtering via PiiEngine::filter_with_platform
  4. Placeholder substitution (longest-first ordering, so {{secret:api_key}} is replaced before {{secret:api}})

Inbound (process_inbound):

  1. Leak detection — Block findings are returned through SecretMasker::mask, never raw
  2. 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 / launch are dispatched through ShellExecuteW, 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_patch forward cursor that fails honestly

Code Location

  • src/security/mod.rs — module entry
  • src/security/ssrf/ — SSRF engine (policy / ip / hostname / dns / fetch)
  • src/security/content_sanitizer.rs — injection detection
  • src/security/headers.rs — HTTP security headers
  • src/security/runtime_guard.rs — runtime orchestration
  • src/security/audit.rs — audit log channel
  • src/security/audit_drain.rs — audit persistence task
  • src/security/unicode_guard.rs — invisible-character detection
  • src/security/injection_patterns.rs — token-level injection patterns
  • src/security/safe_regex.rs — regex literal compile helper
  • src/security/context_id_hasher.rs — context ID hashing
  • src/security/secret_env.rs / secret_equal.rs — credential environment and constant-time compare
  • src/security/dangerous_tools.rstools.invoke denylist

See Also

On this page