Skip to content

Releases: reloading01/certstream-server-rust

v1.5.0 — parser optimizations, internal tuning, optional sonic-rs JSON backend

21 May 15:30

Choose a tag to compare

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: 0

previously reached runtime and panicked immediately.

Validation now runs during all startup paths.


Slow WebSocket client protection

Outbound WebSocket writes now enforce:

WRITE_TIMEOUT = 10s

Stalled 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_LAGS

Idle-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 = 16

and skips already-cached fingerprints.


Gzip bomb protection

Tile decompression is now capped at:

MAX_DECOMPRESSED_TILE_BYTES = 16 MiB

Oversized 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::Text

instead 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_eq

to 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-alpine

Removed

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.0

Compatibility 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

04 May 10:08

Choose a tag to compare

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_index SCT extension (type 0, 40-bit BE) parsed from CtExtensions; validated against the tile-derived index, mismatches counted via certstream_static_ct_leaf_index_mismatch.
  • Partial-tile width enforced: a tile body must contain exactly floor(s / 256^l) mod 256 leaves 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 YAML ct_log.rfc6962_enabled: false) skips the legacy watcher pool entirely. Prepares for the 2027 RFC6962 sunset.
  • CERTSTREAM_STATIC_CT_ENABLED=false mirrors 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.
  • dedup config block is new (defaults backward-compatible).
  • additional_log_lists defaults to fetching Apple's list at startup. Set to an empty array (or CERTSTREAM_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.0

Drop-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: 300

v1.3.4 — Submission Timestamp Support

03 Apr 19:47

Choose a tag to compare

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.4

Community

Thanks to @raffysommy for the contribution (#5).

v1.3.3 — Bandwidth Optimization & Stream Control

13 Mar 19:52

Choose a tag to compare

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 streams config section is optional — defaults to all enabled
  • For bandwidth-constrained deployments: CERTSTREAM_STREAM_FULL_ENABLED=false
  • Override log list URL with CERTSTREAM_CT_LOGS_URL if needed
docker pull ghcr.io/reloading01/certstream-server-rust:1.3.3
docker pull reloading01/certstream-server-rust:1.3.3

v1.3.2

10 Mar 22:09

Choose a tag to compare

Republished to trigger CI/CD release workflow after workflow trigger fix.