Releases: reloading01/certstream-server-rust
v1.5.0 — parser optimizations, internal tuning, optional sonic-rs JSON backend
Release Notes — v1.5.0
Release date: May 19, 2026
v1.5.0 is focused on one thing: making the server behave like production software under real load.
This release fixes multiple race conditions, removes several failure paths, hardens the networking layer, reduces memory usage, and significantly improves long-term runtime stability.
The old multi-tier rate limiting system has been removed completely. In practice it added complexity without delivering meaningful operational value.
The Rust toolchain is now pinned to Edition 2024.
This is a strongly recommended upgrade for all deployments.
TL;DR
Major improvements
- Fixed multiple P0 race conditions and state consistency bugs
- Eliminated duplicate broadcast edge cases
- Reduced idle and loaded CPU usage dramatically
- Lowered default memory footprint by ~22%
- Added rollback protection for RFC6962 logs
- Improved WebSocket reliability under slow/stalled clients
- Hardened gzip parsing and chain parsing logic
- Removed startup panic paths
- Shared issuer cache across watchers
- Added proper idle-server optimization
- Simplified rate limiting model
Breaking change
WebSocket messages now use Text frames instead of binary frames.
Most existing clients already support this automatically.
Performance
After tuning and long-duration soak testing:
| Metric | Before | After |
|---|---|---|
| Idle RSS | 223 MiB | 174 MiB |
| Loaded RSS | 253 MiB | 198 MiB |
| Loaded CPU | 49% | 25% |
12-hour soak testing results:
- 0 panics
- 0 restarts
- 0 healthcheck failures
- 38.3M duplicates filtered
- stable RSS plateau
Compared against the Go implementation (0rickyy0/certstream-server-go) with 100 WebSocket clients:
| Metric | Rust v1.5.0 | Go |
|---|---|---|
| Avg CPU | 13% | 38% |
| Peak RSS | 118 MiB | 161 MiB |
| Memory swing | ±5 MiB | ±66 MiB |
The Rust implementation consistently maintained:
- lower CPU usage
- tighter memory stability
- lower peak RSS
- full static-CT support
Data Integrity
Dedup race condition fixed
DedupFilter::is_new previously allowed concurrent inserts for the same SHA-256 hash under load, which could broadcast duplicate certificates.
The implementation now uses DashMap::entry, ensuring atomic check-and-insert behavior.
A regression test with:
- 32 threads
- 1000 calls each
now guarantees exactly one successful insertion.
Dedup cache wipe removed
Previously, reaching the 1M-entry dedup capacity triggered a full cache clear.
That caused immediate duplicate storms.
The cache now performs targeted expiration instead of catastrophic wipes.
State persistence race fixed
save_if_dirty previously cleared the dirty flag after disk writes, creating a TOCTOU window where updates could be lost.
The dirty flag is now cleared before snapshot generation using atomic swap semantics.
Failed saves automatically re-arm persistence.
RFC6962 rollback protection added
Rollback protection now exists for both:
- static-CT
- RFC6962 watchers
Logs returning smaller tree_size values than previously observed are now rejected safely.
Additional bounds protection was added around tree_size - 1.
Certificate cache eviction race fixed
TTL eviction could previously invalidate the API index for newer copies of the same certificate, causing false 404 responses.
Eviction now validates pointer identity before removing index entries.
Reliability & Stability
Startup panics removed
Critical startup paths no longer rely on .expect().
Graceful shutdown handling now covers:
- invalid TLS files
- occupied ports
- malformed YAML configs
All failures are logged properly through the cancellation token system.
Config validation now runs on normal startup
Invalid configs such as:
buffer_size: 0previously reached runtime and panicked immediately.
Validation now runs during all startup paths.
Slow WebSocket client protection
Outbound WebSocket writes now enforce:
WRITE_TIMEOUT = 10sStalled clients are disconnected automatically instead of permanently blocking connection tasks.
New metric:
certstream_ws_disconnect_write_timeout
Per-client lag disconnects
Clients that fall behind for 5 consecutive lag events are now disconnected automatically.
Threshold:
lag_policy::MAX_CONSECUTIVE_LAGSIdle-server CPU optimization
JSON serialization is now skipped entirely when no subscribers are connected.
This significantly reduces idle CPU usage on ingest-only deployments.
Memory & Resource Usage
Shared issuer cache
Issuer caches are now shared globally across watchers instead of allocating separate caches per CT log.
Benefits:
- lower RAM usage
- improved issuer reuse
- faster issuer prewarm behavior
Issuer prewarm concurrency capped
Large tiles previously triggered unbounded issuer fetch fan-out.
Prewarm now uses bounded concurrency:
MAX_INFLIGHT_ISSUER_FETCHES = 16and skips already-cached fingerprints.
Gzip bomb protection
Tile decompression is now capped at:
MAX_DECOMPRESSED_TILE_BYTES = 16 MiBOversized payloads are rejected safely.
Metric added:
certstream_static_ct_decompress_oversize
Protocol Changes
WebSocket frames now use Text
Certificate updates and heartbeat messages now use:
Message::Textinstead of binary frames.
Most clients already support this automatically, including:
- browser WebSocket APIs
- certstream-python
- standard websocket libraries
Zero-copy WebSocket text path
The initial Text-frame migration introduced unnecessary per-message string allocations.
The implementation now uses:
Utf8Bytes::try_from(bytes)allowing shared-buffer reuse without additional allocations.
Security & Hardening
Hot reload authentication bypass fixed
Before v1.5.0, partial hot reload configs could unintentionally disable authentication because omitted sections were replaced with defaults.
Authentication state is now preserved correctly during reloads.
Regression tests added for:
- empty YAML reloads
- partial config overrides
CORS scoping tightened
Permissive CORS headers now apply only to public endpoints:
- WebSocket
- SSE
/api/cert/{hash}
Operator-only routes such as:
/metrics/health/example.json
no longer expose permissive CORS behavior.
RFC6962 parser bounds enforced
Certificate chain parsing now strictly respects declared chain lengths.
Trailing bytes beyond the declared length are rejected.
Constant-time auth preserved
Authentication token comparison still uses:
subtle::ct_eqto avoid timing attacks.
Runtime Tuning
Default settings were tightened after profiling and soak testing.
Updated defaults
| Setting | Old | New |
|---|---|---|
| reqwest idle pool | 20 | 4 |
| dedup capacity | 1M | 200K |
| API cache | 10K | 1K |
| tokio worker threads | 8 | 4 |
These changes reduce:
- idle CPU usage
- long-term memory drift
- RSS footprint
while maintaining compatibility.
Major CPU Fix
Dedup hot-path thrash removed
DedupFilter::is_new previously triggered inline expiration scans whenever capacity was reached.
Under real-world ingest this caused repeated O(n) scans inside the hot path.
Observed behavior before the fix:
| Scenario | CPU Usage |
|---|---|
| Idle containers | 211% |
| Loaded containers | 268% |
Expiration now happens only inside the periodic cleanup task.
Post-fix:
| Scenario | CPU Usage |
|---|---|
| Idle containers | 5% |
| Loaded containers | 16% |
Dependency Updates
Core stack updated to current stable releases:
- tokio 1.52
- reqwest 0.13
- axum-server 0.8
- simd-json 0.17
- x509-parser 0.18
- notify 8
Builder image updated to:
rust:1.95-alpineRemoved
The following features no longer exist:
RateLimitTier::{Free, Standard, Premium}- tier token tables
- standard/premium throughput configs
Rate limiting is now unified and based solely on source IP.
Authentication controls access.
Rate limiting controls throughput.
Legacy YAML keys still deserialize through compatibility aliases.
Testing
Total test count:
425 passing tests
Coverage includes:
- unit tests
- integration tests
- fuzz targets
- graceful failure tests
- soak validation
No panics were found during fuzzing.
Upgrade
docker pull ghcr.io/reloading01/certstream-server-rust:1.5.0Compatibility Notes
For v1.4.x users
- old standard/premium rate-limit fields are ignored
- move legacy tier tokens into unified
auth.tokens - WebSocket clients must support Text frames instead of binary-only handling
v1.4.0 — static-ct-api v1.0.0-rc.1 + log-list discovery
May 2, 2026
Brings the project in line with the post-RFC6962 CT ecosystem: Apple-list discovery for tiled logs, static-ct-api v1.0.0-rc.1 conformance (leaf_index extension, partial-tile width validation, tree-size monotonicity), runtime kill switches per protocol family, and a critical tile-parser bug fix that had silently dropped 99% of static-CT entries since v1.2.
Highlights
Tile parser correctness (critical fix). The Fingerprint certificate_chain<0..2^16-1> field is byte-length-prefixed per the static-ct-api framing, not count-prefixed. Pre-1.4 builds treated the prefix as a fingerprint count, consuming subsequent leaves' bytes as chain data — every tile yielded only its first leaf. With the fix, full tiles correctly emit all 256 entries (verified against real Sycamore/Willow/Cloudflare Raio/IPng Networks tiles).
Log-list discovery. additional_log_lists (default: Apple's current_log_list.json) is fetched in parallel with Google's v3 list. Both lists' operators[].tiled_logs[] arrays are now read and surfaced as static-ct watchers. Logs appearing in multiple lists are deduped by log_id. Submission URL drives the checkpoint origin per spec; user-provided static_logs entries override discovery for the same URL.
static-ct-api v1.0.0-rc.1 conformance:
leaf_indexSCT extension (type 0, 40-bit BE) parsed fromCtExtensions; validated against the tile-derived index, mismatches counted viacertstream_static_ct_leaf_index_mismatch.- Partial-tile width enforced: a tile body must contain exactly
floor(s / 256^l) mod 256leaves for the last tile, 256 for full. Mismatches drop the tile and back off rather than emit partial data (certstream_static_ct_tile_width_mismatch). - Tree-size monotonicity: rollbacks are detected, logged, and refused (
certstream_static_ct_tree_size_rollbacks). - Witness signatures on checkpoints are passively accepted — extra
—lines beyond the primary log signature no longer trip the parser.
Runtime kill switches:
CERTSTREAM_RFC6962_ENABLED=false(or YAMLct_log.rfc6962_enabled: false) skips the legacy watcher pool entirely. Prepares for the 2027 RFC6962 sunset.CERTSTREAM_STATIC_CT_ENABLED=falsemirrors for static-ct.- Refuses to start when both are disabled rather than running with zero sources.
Type-aware health probes. Static-CT logs are reachability-probed against /checkpoint; RFC6962 logs against /ct/v1/get-sth. Static-CT logs no longer get false-negative-filtered out of the candidate pool.
Per-operator rate limiting for static-CT. Watchers belonging to the same operator (e.g. all of Cloudflare's Raio shards) now share a 2 req/s limiter, matching the existing RFC6962 behavior. Avoids thundering-herd toward a single CDN host.
Tunable cross-log dedup. New dedup.capacity / dedup.ttl_secs config (env: CERTSTREAM_DEDUP_CAPACITY / CERTSTREAM_DEDUP_TTL_SECS). Defaults bumped to 1M / 900s to cover the wider RFC6962↔static-CT propagation window.
New metrics
certstream_static_ct_leaf_index_mismatch{log}certstream_static_ct_tile_width_mismatch{log}certstream_static_ct_tree_size_rollbacks{log}
Breaking-ish changes
fetch_log_list(internal) now takes&[String]of additional list URLs and returns mixed RFC6962+static-CT logs; downstream binary integrators should re-pin.dedupconfig block is new (defaults backward-compatible).additional_log_listsdefaults to fetching Apple's list at startup. Set to an empty array (orCERTSTREAM_ADDITIONAL_LOG_LISTS=) to opt out.
Test coverage
205 unit tests (was 190 in v1.3.4). New: leaf_index extension parsing, byte-length-prefixed chain fingerprints, Apple-style log-list schema, partial-tile semantics.
Upgrade notes
docker pull ghcr.io/reloading01/certstream-server-rust:1.4.0Drop-in upgrade from v1.3.4. Existing config files keep working; additional_log_lists and dedup blocks are optional. To stay on the v1.3 behavior:
additional_log_lists: []
ct_log:
static_ct_enabled: false
dedup:
capacity: 500000
ttl_secs: 300v1.3.4 — Submission Timestamp Support
Adds the submission_timestamp field to all certificate messages — the moment the CT log issued the Signed Certificate Timestamp (SCT) per RFC 6962 §3.1.
New Features
submission_timestamp Field
Every certificate message (full, lite) now includes submission_timestamp: a Unix timestamp (seconds since epoch, millisecond precision) extracted from the TimestampedEntry.timestamp field in the CT log's Merkle tree leaf.
{
"seen": 1703808000.123,
"submission_timestamp": 1703721600.456
}| Field | Source | Meaning |
|---|---|---|
seen |
Server clock | When this server processed the entry |
submission_timestamp |
CT log | When the CT log accepted the certificate and issued the SCT |
Upgrade Notes
- Drop-in upgrade from v1.3.3. No config or state file changes.
- Additive change — no fields removed.
docker pull ghcr.io/reloading01/certstream-server-rust:1.3.4Community
Thanks to @raffysommy for the contribution (#5).
v1.3.3 — Bandwidth Optimization & Stream Control
New Features
Configurable Stream Types
Each stream type (full/lite/domains-only) can be independently enabled or disabled. Disabled streams skip JSON serialization and their routes are not registered — saving CPU and outbound bandwidth.
streams:
full: false # ~4-5 KB/cert — disable to save ~80% outbound
lite: true # ~1 KB/cert
domains_only: true # ~200 B/cert| Variable | Default |
|---|---|
CERTSTREAM_STREAM_FULL_ENABLED |
true |
CERTSTREAM_STREAM_LITE_ENABLED |
true |
CERTSTREAM_STREAM_DOMAINS_ONLY_ENABLED |
true |
Performance
HTTP Compression (gzip + brotli + deflate)
CT log fetch responses are now compressed. Expected inbound bandwidth reduction: ~30-50% (~100-180 GB/day on a full deployment).
Chrome-Trusted Log List
Default log list switched from all_logs_list.json to log_list.json. Removes 31 test/staging/legacy logs, adds 16 new production logs (TrustAsia, Geomys, IPng Networks).
Deferred Chain Parsing
Chain cert parsing deferred until after dedup check — skips DER-parsing 2-4 chain certs for duplicate entries (~60-80% of all fetched entries).
Upgrade Notes
- Drop-in upgrade from v1.3.2
- New
streamsconfig section is optional — defaults to all enabled - For bandwidth-constrained deployments:
CERTSTREAM_STREAM_FULL_ENABLED=false - Override log list URL with
CERTSTREAM_CT_LOGS_URLif needed
docker pull ghcr.io/reloading01/certstream-server-rust:1.3.3
docker pull reloading01/certstream-server-rust:1.3.3v1.3.2
Republished to trigger CI/CD release workflow after workflow trigger fix.