Rullst Security π‘οΈ
βDefense-in-Depth RASP, Cryptographic Vault & Runtime Protection for Rustβ
rullst-security provides authenticated field encryption, bounded defensive
middleware, local abuse controls, and security telemetry. Its RASP/DLP rules are
defense-in-depth heuristics: they reduce specific risks but do not establish
complete OWASP coverage, replace parameterized SQL/authorization, or certify the
application that mounts them. Which security layer to use, and when
compares these layers with the Core baseline and gives one recommended
production stack.
β‘ Capability & Lifecycle Matrix
| Subsystem | Lifecycle Status | Description |
|---|---|---|
| Rullst Vault | π’ [Implemented] | Authenticated AES-256-GCM encryption with 96-bit nonces, AAD, versioned envelopes, and keyring rotation. Key custody remains external. |
| Bounded RASP | π’ [Implemented: defense in depth] | ASCII signature matching plus one decoding pass for URI, headers, and bounded textual/JSON/XML/form bodies. Body media types are classified case-insensitively, with +json/+xml suffixes and prefixed form types, so every body axumβs Json or Form extractor accepts is inspected. A stock PowerShell User-Agent product token (PowerShell/7.4.1, WindowsPowerShell/5.1β¦) is not treated as the powershell command keyword; execution syntax still blocks. Decoding and body inspection may allocate. |
| Login Guard Tarpit | π’ [Implemented: local] | Progressive delay decisions and bounded, expiring in-memory jails keyed by a hashed identity. The caller performs the returned delay. |
| Fixed-Window Rate Limiter | π’ [Implemented: local] | In-memory limiter keyed from the verified socket peer: per IPv4 address and per IPv6 /64, with IPv4-mapped IPv6 keyed as IPv4. It tracks at most 16,384 keys and does not coordinate multiple processes. |
| Redis Rate Limiter | π’ [Implemented: feature-gated foundation] | redis-rate-limit uses an atomic fixed-window Lua script, namespace validation, hashed client keys and TTL-derived retry metadata. A limiter and its clones share one lazily opened multiplexed connection and reconnect after a connection-level failure instead of connecting per check. Empty/mock_* URLs select an explicit process-local test mode that tracks at most 16,384 client keys and denies new keys while all tracked windows are active; call require_distributed() at production startup. Windows must be at least 1 ms. A live contract proves independent clients share one Redis budget; cluster/failover remains application evidence. |
| DLP & Secret Masking | π’ [Implemented: bounded] | Masks complete PEM private-key blocks (PKCS#8 plain or encrypted, RSA, EC, DSA, OpenSSH and OpenPGP), AWS access-key patterns, and credentials in supported textual database URLs. Textual responses are text/*, JSON, XML (including +xml), YAML and application/javascript. A 206 range that would need masking is withheld with 502 instead of losing its Content-Range; a multipart/byteranges response is checked part by part and withheld the same way when a textual part would need masking or the body cannot be split exactly (at most 256 parts, 8 KiB of headers per part). Each range is inspected on its own. Binary, compressed, streaming, unknown-size, and oversized bodies are not rewritten. |
| Local SIEM Journals | π’ [Implemented: bounded local] | DurableSiemSpool preserves synchronized unsigned SHA-256 frames. The opt-in AuthenticatedSiemSpool adds HMAC-SHA256 sequence/predecessor integrity, one active plus seven historical zeroized keys, byte/record quotas and fail-closed restart/forgery/ordering/external-change behavior. Whole-tail checkpoints, multi-writer coordination, retention, delivery, retry, acknowledgement and external adapters remain operator work. |
| TOTP Multi-Factor Auth | π’ [Implemented: foundation] | Six-digit SHA-1 TOTP generation/verification with a Β±1 time-step window, percent-encoded otpauth URI builder, and subject-bound single-use recovery-code verifiers. Enrollment, transactional persistence, rate limits, and account policy remain application concerns. |
| CSWSH Guard | π’ [Implemented] | Exact normalized scheme/host/port validation for WebSocket origins, with a fail-closed default for missing origins. Without an allowlist, the Origin must match the Host (or HTTP/2 :authority) and, when known from a trusted proxy or HTTP/2 :scheme, the request scheme. |
| HTML Sanitizer | π’ [Implemented: allowlist] | ammoniaβs default allowlist for user-supplied HTML; sanitize_text escapes for HTML text and quoted attributes only. Allowed links and images can still reference external sites. |
| JSON Schema Guard | π’ [Implemented: bounded] | Rejects malformed, duplicate-key, over-2 MiB and over-32-level JSON; a route-scoped JSON Schema 2020-12 policy checks shape, not business rules. The global guard passes non-JSON media types through. |
| Honeypot and Decoy Routes | π’ [Implemented: local] | Exact trap paths are refused; HoneypotLayer bans the direct peerβs exact IP for 15 minutes by default, deception_trap_middleware only records. Bans are per process and avoided by address rotation. |
| LLM Prompt Filter | π‘ [Implemented: heuristic] | Fixed English phrase, chat-token, Markdown-beacon and invisible-character checks on prompt/content/message JSON fields. Rephrased, translated or encoded instructions and indirect injection pass; legitimate text can be refused. |
Session Fingerprint (zero_trust) | π‘ [Implemented: helper] | HMAC of User-Agent, Accept-Language and the client /24 or /64. All inputs are client-controlled or shared; expect false mismatches after browser or network changes. Not a zero-trust architecture. |
| Timing Guard | π‘ [Implemented: coarse] | Pads responses to a minimum duration with jitter. Slower paths are not padded and the synthetic CPU work does not match a real password hash. |
| Canonical Server security stack | π‘ [Partial] | CSP nonce identity is shared across Core and extended layers, but Core still owns the default Server CSRF/WAF/header/PII stack. Explicit composition is required. |
| Distributed Rate Limiting Evidence | π‘ [Partial] | The Redis adapter is implemented, and the two-process deployment acceptance behind a Caddy proxy shares one Redis budget; eviction, failover and trusted-proxy deployment evidence remain required. The legacy no-argument RateLimiter::try_with_distributed still returns DistributedBackendUnsupported rather than guessing configuration. |
π 1. Rullst Vault (Authenticated AES-256-GCM)
FieldEncryptor provides authenticated encryption at rest (AEAD) with a
versioned envelope and keyring-assisted rotation. Applications can keep prior
keys readable while writing with a new key; deployment coordination, key
custody, data re-encryption, and retirement remain operator responsibilities.
Usage Example
use rullst_security::vault::FieldEncryptor;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let master_key = [0x42u8; 32]; // 256-bit cryptographic key
let sensitive_data = "user_ssn_123-45-6789";
// Encrypt with key ID and Additional Authenticated Data (AAD)
let encrypted = FieldEncryptor::encrypt_with_key_id(
sensitive_data,
master_key,
"key-2026-v1",
b"tenant-organization-id-42",
)?;
println!("Ciphertext Envelope: {}", encrypted);
// Decrypt and verify authentication tag and AAD
let decrypted = FieldEncryptor::decrypt_with_aad(
&encrypted,
master_key,
b"tenant-organization-id-42",
)?;
assert_eq!(decrypted, sensitive_data);
Ok(())
}
π‘οΈ 2. Runtime Application Self-Protection (RASP)
The RASP request inspector scrutinizes incoming requests before they reach your controllers:
#![allow(unused)]
fn main() {
use rullst_security::RaspInspector;
let is_sqli = RaspInspector::inspect_text("admin' OR '1'='1");
assert!(is_sqli);
let is_traversal = RaspInspector::inspect_uri("/files/../../../etc/passwd");
assert!(is_traversal);
}
Mount RaspSecurityLayer explicitly when the extended inspector is desired.
The default rullst-core::Server currently mounts the smaller Core WAF rather
than this layer; consolidation remains roadmap work.
The inspector matches case-insensitive signatures, raw and after one
percent-decoding pass, in the request target, every header except Cookie
and Authorization, and identity-encoded textual bodies up to 1 MiB. It does
not inspect multipart or binary bodies, does not undo double encoding, HTML
entities or SQL comments, and refuses ordinary text that contains a signature
(../, sleep(, /bin/sh). It reduces exposure to known payloads; it is not
an injection defense on its own.
π« 3. Anti-Bruteforce Login Guard & Tarpit
#![allow(unused)]
fn main() {
use rullst_security::LoginGuard;
async fn apply_login_delay() {
let guard = LoginGuard::new(); // defaults: 5 failures, 15-minute local jail
// Record failure and apply the returned progressive delay in the async handler.
let delay = guard.record_login_failure("account:alice");
tokio::time::sleep(delay).await;
if guard.is_jailed("account:alice") {
println!("Identity is in the local Login Jail");
}
}
}
π± 4. Multi-Factor Authentication (TOTP MFA)
#![allow(unused)]
fn main() {
use rullst_security::{
build_otpauth_uri, generate_mfa_secret, generate_totp_code, verify_totp_code,
};
let secret = generate_mfa_secret();
let otp_uri = build_otpauth_uri("Rullst SaaS", "[email protected]", &secret);
let current_code = generate_totp_code(&secret);
// Validate 6-digit code submitted by user
let is_valid = verify_totp_code(&secret, "123456");
}
The secret must be encrypted at rest. Recovery helpers return plaintext codes only at enrollment and salted HMAC verifiers for storage; consume/delete must be one durable transaction. The application still owns replay/attempt limiting, enrollment confirmation, recovery UX, audit and clock-monitoring policy.