This document covers security features and testing in Quill.
Quill implements security measures to protect against common attacks in RPC systems:
- 0-RTT Replay Protection - Prevents replay attacks on non-idempotent methods
- Compression Side-Channel Mitigation - Protects secrets from CRIME/BREACH-style attacks
- Fuzz Testing - Validates robustness of parsers against malformed input
- TLS Requirements - Enforces TLS 1.3 for all connections
HTTP/3 (QUIC) supports 0-RTT connection resumption for reduced latency. However, 0-RTT data can be replayed by an attacker, making it unsafe for non-idempotent operations.
Quill provides the IdempotencyChecker to control which methods can use 0-RTT:
use quill_server::{IdempotencyChecker, is_early_data_request};
// Create checker and register idempotent methods
let mut checker = IdempotencyChecker::new();
checker.register_idempotent("image.v1.ImageService/GetMetadata");
checker.register_idempotent("image.v1.ImageService/GetThumbnail");
// In request handler
fn handle_request(req: &http::Request<()>, checker: &IdempotencyChecker) {
let path = req.uri().path();
let is_early = is_early_data_request(req.headers());
if let Err(problem) = checker.validate_early_data(path, is_early) {
// Return HTTP 425 Too Early
return Err(problem);
}
// Process request...
}When a non-idempotent method is called with 0-RTT data, Quill returns:
HTTP/3 425 Too Early
Content-Type: application/problem+json
{
"type": "urn:quill:error:425",
"title": "Request rejected due to early data",
"status": 425,
"detail": "Method 'image.v1.ImageService/Upload' is not idempotent and cannot be sent with 0-RTT data. Please retry without early data."
}-
Mark idempotent methods in proto files:
service ImageService { rpc GetMetadata(GetRequest) returns (ImageMetadata) { option (quill.rpc) = { idempotent: true }; } }
-
Keep 0-RTT disabled by default - Only enable for specific performance-critical paths
-
Monitor 425 responses - Track rejected early data to identify configuration issues
CRIME and BREACH attacks exploit HTTP compression to extract secrets by observing response sizes. Quill mitigates this by excluding sensitive headers from compression.
The following headers are never compressed:
AuthorizationCookieSet-CookieX-API-KeyX-Auth-Token
use quill_server::CompressionExclusions;
let mut exclusions = CompressionExclusions::default_exclusions();
exclusions.add_exclusion("X-Custom-Secret");
exclusions.add_exclusion("X-Session-Token");Quill includes fuzz testing targets to validate parser robustness.
Fuzz testing requires the nightly Rust toolchain and cargo-fuzz:
# Install cargo-fuzz
cargo +nightly install cargo-fuzz
# Run frame parser fuzzer
cd fuzz
cargo +nightly fuzz run fuzz_frame_parser
# Run Problem Details JSON fuzzer
cargo +nightly fuzz run fuzz_problem_details
# Run varint fuzzer
cargo +nightly fuzz run fuzz_varintTests the frame parser against arbitrary binary input:
- Validates handling of malformed varints
- Tests oversized frame rejection (> 4MB)
- Verifies incomplete frame handling
- Checks frame flag parsing
Tests Problem Details JSON deserialization:
- Invalid JSON handling
- Missing required fields
- Type coercion edge cases
- Unicode and escape sequences
Tests varint encoding/decoding:
- Roundtrip consistency
- Overflow handling (> 64 bits)
- Edge cases (0, max values)
Add seed inputs to fuzz/corpus/<target>/ for better coverage:
# Add a valid frame as seed
mkdir -p fuzz/corpus/fuzz_frame_parser
echo -ne '\x05\x01hello' > fuzz/corpus/fuzz_frame_parser/valid_frame
# Add valid Problem Details JSON
mkdir -p fuzz/corpus/fuzz_problem_details
echo '{"type":"urn:test","title":"Test","status":200}' > fuzz/corpus/fuzz_problem_details/valid.json- TLS 1.3 is required for all connections
- mTLS for service-to-service communication
- JWT/OIDC for end-user authentication
use rustls::{ServerConfig, Certificate, PrivateKey};
// Load certificates
let certs = load_certs("server.crt");
let key = load_private_key("server.key");
// Create TLS config with TLS 1.3 only
let config = ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("TLS 1.3 required")
.with_no_client_auth()
.with_single_cert(certs, key)
.expect("Invalid certificate");use rustls::{ClientConfig, RootCertStore};
let mut root_store = RootCertStore::empty();
root_store.add_parsable_certificates(webpki_roots::TLS_SERVER_ROOTS);
let config = ClientConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("TLS 1.3 required")
.with_root_certificates(root_store)
.with_no_client_auth();The security module includes comprehensive tests:
cargo test --package quill-server securityTests cover:
- Idempotency checking
- Early data validation
- Compression exclusions
- Header case-insensitivity
For end-to-end security testing:
-
0-RTT Replay Test:
# Start server with 0-RTT enabled ./server --enable-0rtt # Capture and replay 0-RTT data # Non-idempotent methods should return 425
-
Compression Test:
# Verify Authorization header is not in compressed payload # by checking response size doesn't correlate with header value
Before deploying Quill services:
- TLS 1.3 enabled
- 0-RTT disabled or idempotent methods properly marked
- Sensitive headers excluded from compression
- Fuzz testing completed without crashes
- Problem Details don't leak internal errors
- Rate limiting configured
- Authentication middleware enabled
Report security vulnerabilities to security@quillprism.dev.
Do not open public issues for security vulnerabilities.