From ca206acdc2f4ee70bc864cf3ab1ff3d111dd5be2 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 17:13:50 +1000 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20release=20hardening=20=E2=80=94=20?= =?UTF-8?q?non=5Fexhaustive=20enums,=20missing=5Fdocs,=20cargo-deny=20fixe?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add #[non_exhaustive] to CachekitError and BackendErrorKind for semver safety - Enable #![warn(missing_docs)] and document all public items - Fix deny.toml schema for cargo-deny 0.19.x (advisories field changes) - Add MPL-2.0, BSL-1.0, CDLA-Permissive-2.0 to license allowlist - Update rustls-webpki 0.103.9 → 0.103.13 (RUSTSEC-2026-0049/0098/0099/0104) --- Cargo.lock | 4 ++-- crates/cachekit/src/backend/mod.rs | 3 +++ crates/cachekit/src/error.rs | 9 ++++++++- crates/cachekit/src/key.rs | 2 ++ crates/cachekit/src/l1/mod.rs | 7 +++++++ crates/cachekit/src/lib.rs | 12 ++++++++++++ crates/cachekit/src/metrics.rs | 7 +++++++ crates/cachekit/src/url_validator.rs | 2 ++ deny.toml | 11 +++++++---- 9 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 460e91e..4f44e6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1522,9 +1522,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", diff --git a/crates/cachekit/src/backend/mod.rs b/crates/cachekit/src/backend/mod.rs index b9de53a..8fd3683 100644 --- a/crates/cachekit/src/backend/mod.rs +++ b/crates/cachekit/src/backend/mod.rs @@ -125,6 +125,7 @@ pub trait LockableBackend: Backend { // ── Feature-gated backend modules ───────────────────────────────────────────── +/// HTTP backend for the cachekit.io SaaS API. #[cfg(feature = "cachekitio")] pub mod cachekitio; #[cfg(feature = "cachekitio")] @@ -132,8 +133,10 @@ mod cachekitio_lock; #[cfg(feature = "cachekitio")] mod cachekitio_ttl; +/// Redis backend via the [`fred`](https://crates.io/crates/fred) client. #[cfg(feature = "redis")] pub mod redis; +/// Cloudflare Workers backend using `worker::Fetch`. #[cfg(feature = "workers")] pub mod workers; diff --git a/crates/cachekit/src/error.rs b/crates/cachekit/src/error.rs index 09f74c5..2dbeaa8 100644 --- a/crates/cachekit/src/error.rs +++ b/crates/cachekit/src/error.rs @@ -2,6 +2,7 @@ use thiserror::Error; /// Top-level error type for all CacheKit operations. #[derive(Debug, Error)] +#[non_exhaustive] pub enum CachekitError { /// An error originating from the cache backend. #[error("backend error: {0}")] @@ -21,7 +22,12 @@ pub enum CachekitError { /// The payload exceeds the maximum allowed size. #[error("payload too large: {size} bytes (limit: {limit} bytes)")] - PayloadTooLarge { size: usize, limit: usize }, + PayloadTooLarge { + /// Actual payload size in bytes. + size: usize, + /// Maximum allowed size in bytes. + limit: usize, + }, /// The cache key is invalid (empty, too long, or contains illegal bytes). #[error("invalid cache key: {0}")] @@ -32,6 +38,7 @@ pub enum CachekitError { /// Classifies backend errors to determine retry behaviour. #[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] pub enum BackendErrorKind { /// Temporary failure — safe to retry (network blip, pool exhaustion). Transient, diff --git a/crates/cachekit/src/key.rs b/crates/cachekit/src/key.rs index 51753a6..b29c7ad 100644 --- a/crates/cachekit/src/key.rs +++ b/crates/cachekit/src/key.rs @@ -4,6 +4,8 @@ use crate::error::CachekitError; type Blake2b256 = Blake2b; +/// Generate a deterministic cache key by Blake2b-256 hashing the function name +/// and serialized arguments, optionally prefixed with a namespace. pub fn generate_cache_key( namespace: &str, function_name: &str, diff --git a/crates/cachekit/src/l1/mod.rs b/crates/cachekit/src/l1/mod.rs index 30a9f27..b2f4acb 100644 --- a/crates/cachekit/src/l1/mod.rs +++ b/crates/cachekit/src/l1/mod.rs @@ -21,11 +21,15 @@ impl Expiry for L1Expiry { } } +/// In-process LRU cache with per-entry TTL, backed by [`moka`]. +/// +/// Used as the L1 layer in the dual-layer cache architecture. pub struct L1Cache { store: Cache, } impl L1Cache { + /// Create a new L1 cache with the given maximum entry capacity. pub fn new(capacity: usize) -> Self { Self { store: Cache::builder() @@ -35,10 +39,12 @@ impl L1Cache { } } + /// Retrieve cached bytes by key, or `None` if absent or expired. pub fn get(&self, key: &str) -> Option> { self.store.get(key).map(|entry| entry.data.clone()) } + /// Insert or overwrite an entry with the given TTL. pub fn set(&self, key: &str, value: &[u8], ttl: Duration) { self.store.insert( key.to_string(), @@ -49,6 +55,7 @@ impl L1Cache { ); } + /// Remove an entry by key. pub fn delete(&self, key: &str) { self.store.invalidate(key); } diff --git a/crates/cachekit/src/lib.rs b/crates/cachekit/src/lib.rs index 052ab10..fe56cb1 100644 --- a/crates/cachekit/src/lib.rs +++ b/crates/cachekit/src/lib.rs @@ -5,6 +5,7 @@ // Production code lints — these only fire in src/, not tests/ #![warn(clippy::unwrap_used, clippy::expect_used, clippy::panic)] +#![warn(missing_docs)] // Mutually exclusive feature guards #[cfg(all(feature = "workers", feature = "redis"))] @@ -15,19 +16,30 @@ compile_error!( #[cfg(all(feature = "workers", feature = "l1"))] compile_error!("features `workers` and `l1` are mutually exclusive — moka requires std threads unavailable in wasm32"); +/// Pluggable cache backend trait and implementations (CachekitIO, Redis, Workers). pub mod backend; +/// High-level cache client with dual-layer (L1/L2) support. pub mod client; +/// Configuration types and environment variable parsing. pub mod config; +/// Error types for cache operations and backend communication. pub mod error; +/// Cache key generation using Blake2b hashing. pub mod key; +/// L1 cache hit-rate metrics for CachekitIO request headers. pub mod metrics; +/// Serialization and deserialization of cached values via MessagePack. pub mod serializer; +/// SDK session tracking (session ID and start timestamp). pub mod session; +/// SSRF-safe URL validation for CachekitIO endpoints. pub mod url_validator; +/// Client-side AES-256-GCM encryption with HKDF key derivation. #[cfg(feature = "encryption")] pub mod encryption; +/// In-process L1 cache backed by [`moka`] with per-entry TTL. #[cfg(feature = "l1")] pub mod l1; diff --git a/crates/cachekit/src/metrics.rs b/crates/cachekit/src/metrics.rs index 00265c8..6bdab28 100644 --- a/crates/cachekit/src/metrics.rs +++ b/crates/cachekit/src/metrics.rs @@ -1,14 +1,21 @@ use std::sync::Arc; +/// Snapshot of L1/L2 cache hit statistics. pub struct L1Stats { + /// Number of requests served from the L1 in-process cache. pub l1_hits: u64, + /// Number of requests served from the L2 backend. pub l2_hits: u64, + /// Number of cache misses. pub misses: u64, + /// Whether the L1 cache is currently enabled. pub l1_enabled: bool, } +/// Thread-safe closure that produces an optional [`L1Stats`] snapshot. pub type MetricsProvider = Arc Option + Send + Sync>; +/// Build `X-CacheKit-*` HTTP headers from the current L1 stats provider. pub fn metrics_headers(provider: Option<&MetricsProvider>) -> Vec<(&'static str, String)> { let disabled = vec![("X-CacheKit-L1-Status", "disabled".to_string())]; diff --git a/crates/cachekit/src/url_validator.rs b/crates/cachekit/src/url_validator.rs index 3c1b40d..e1cc8b4 100644 --- a/crates/cachekit/src/url_validator.rs +++ b/crates/cachekit/src/url_validator.rs @@ -2,6 +2,8 @@ use crate::error::CachekitError; const ALLOWED_HOSTS: &[&str] = &["api.cachekit.io", "api.staging.cachekit.io"]; +/// Validate that a CachekitIO API URL uses HTTPS, is not a private IP (SSRF +/// protection), and matches the allow-list unless `allow_custom_host` is set. pub fn validate_cachekitio_url( url_str: &str, allow_custom_host: bool, diff --git a/deny.toml b/deny.toml index 53e756e..cec686d 100644 --- a/deny.toml +++ b/deny.toml @@ -1,8 +1,8 @@ [advisories] -vulnerability = "deny" -unmaintained = "warn" +# cargo-deny 0.19+: vulnerability/unmaintained/unsound/notice all deny by default. +# Only yanked needs explicit config — warn instead of hard-fail so renovate can fix. yanked = "warn" -notice = "warn" +unmaintained = "workspace" [licenses] allow = [ @@ -10,11 +10,14 @@ allow = [ "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", + "BSL-1.0", + "CDLA-Permissive-2.0", "ISC", + "MPL-2.0", + "OpenSSL", "Unicode-3.0", "Unicode-DFS-2016", "Zlib", - "OpenSSL", ] confidence-threshold = 0.8 From b67cddd535eeb3139ce345d24a85a5cb29322d34 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 17:14:56 +1000 Subject: [PATCH 2/2] chore: gitignore AI agent configs, untrack CLAUDE.md Internal development tooling (CLAUDE.md, .claude/, .caliber/, etc.) should not ship in the public OSS repo. --- .gitignore | 8 +++++++ CLAUDE.md | 61 ------------------------------------------------------ 2 files changed, 8 insertions(+), 61 deletions(-) delete mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 2f7896d..b15c377 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ target/ + +# AI agent configs — internal dev tooling, not part of the shipped product +CLAUDE.md +AGENTS.md +.claude/ +.caliber/ +.cursor/ +CALIBER_LEARNINGS.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 05c7b35..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,61 +0,0 @@ -# cachekit-rs - -Rust SDK for [cachekit.io](https://cachekit.io) — production-ready caching with zero-knowledge encryption. - -## Crates - -| Crate | Path | Purpose | -|-------|------|---------| -| `cachekit` | `crates/cachekit/` | Main SDK | -| `cachekit-macros` | `crates/cachekit-macros/` | Proc-macro decorators | - -## Features - -| Feature | Default | Description | -|---------|---------|-------------| -| `cachekitio` | yes | HTTP backend for api.cachekit.io (reqwest + rustls) | -| `encryption` | yes | Zero-knowledge AES-256-GCM via cachekit-core | -| `l1` | yes | In-process L1 cache via moka | -| `redis` | no | Redis backend via fred (native only) | -| `workers` | no | Cloudflare Workers backend via worker::Fetch | -| `macros` | no | `#[cache]` proc-macro decorator | - -## Mutually exclusive - -- `workers` + `redis` — Workers runtime cannot use fred -- `workers` + `l1` — moka requires std threads unavailable in wasm32 - -## Quick start - -```rust -use cachekit::prelude::*; - -#[tokio::main] -async fn main() -> Result<(), CachekitError> { - let config = CachekitConfig::from_env()?; - let cache = CacheKit::new(config).await?; - - cache.set("greeting", &"Hello, world!", None).await?; - let val: String = cache.get("greeting").await?.unwrap(); - println!("{val}"); - Ok(()) -} -``` - -## Development - -```bash -make quick-check # fmt + clippy + test -make test # cargo test --all-features -make build # cargo build --release -make build-wasm # wasm32 target (workers feature) -``` - -## Environment Variables - -| Variable | Description | -|----------|-------------| -| `CACHEKIT_API_KEY` | API key for cachekit.io | -| `CACHEKIT_API_URL` | Override API endpoint (default: https://api.cachekit.io) | -| `CACHEKIT_MASTER_KEY` | Hex-encoded master key (min 32 bytes) for encryption | -| `CACHEKIT_DEFAULT_TTL` | Default TTL in seconds (min 1) |