This release includes 2 security fixes for security teams reviewing exposed deployments.
Topics
+2 more
Affected surfaces
ReleasePort's take
Moderate signalIn v4.0.0 public CacheStack reads return undefined for misses and negative‑cache entries, while read‑through fetchers now cache null as a regular value; `getOrThrow()` returns stored nulls and throws only for undefined.
Why it matters: Cache behavior changes affect application logic that distinguishes between null and missing values; developers must update conditional checks to handle undefined misses and null results accordingly.
Summary
AI summaryPublic cache reads now return undefined on misses, fetchers default to caching nulls, and write coordination applies finite limits rejecting saturation with CacheWriteSaturationError.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Security | High |
Fixed implicit Express/Hono private URL caching to bypass sensitive query parameters unless a custom `keyResolver` is supplied. Fixed implicit Express/Hono private URL caching to bypass sensitive query parameters unless a custom `keyResolver` is supplied. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
Namespace clearing now invalidates only the delimiter‑bound prefix, preventing sibling namespace eviction. Namespace clearing now invalidates only the delimiter‑bound prefix, preventing sibling namespace eviction. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
DiskLayer protected reads now reject plaintext legacy payloads when encryption or signing keys are configured; opt‑in via `allowLegacyPlaintext`. DiskLayer protected reads now reject plaintext legacy payloads when encryption or signing keys are configured; opt‑in via `allowLegacyPlaintext`. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
Generation cleanup now streams old‑generation keys in bounded batches and caps unique‑key discovery at 10,000 entries by default. Generation cleanup now streams old‑generation keys in bounded batches and caps unique‑key discovery at 10,000 entries by default. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
OpenTelemetry instrumentation now exports `layercache.key_hash` by default; raw `layercache.key` requires `includeRawKeyAttributes: true`. OpenTelemetry instrumentation now exports `layercache.key_hash` by default; raw `layercache.key` requires `includeRawKeyAttributes: true`. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
CLI invalidation now requires `--force` for wildcard‑only patterns (`*`, `**`, `?*`). CLI invalidation now requires `--force` for wildcard‑only patterns (`*`, `**`, `?*`). Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Security | High |
Hardened snapshot commits by rejecting symlinked target parents before final rename. Hardened snapshot commits by rejecting symlinked target parents before final rename. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | High |
Hardened docs playground by keeping token‑bearing worker message sender outside user‑code global reach, in addition to sandboxed iframe, Blob Worker, and token checks. Hardened docs playground by keeping token‑bearing worker message sender outside user‑code global reach, in addition to sandboxed iframe, Blob Worker, and token checks. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Medium |
CLI Redis connection errors scrub Redis URLs from nested error messages before printing. CLI Redis connection errors scrub Redis URLs from nested error messages before printing. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Medium |
Prevented plain objects from forging native structured cache-key tags; rotated keys from j: to j2: to avoid reuse of ambiguous entries. Prevented plain objects from forging native structured cache-key tags; rotated keys from j: to j2: to avoid reuse of ambiguous entries. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Medium |
Prevented stale single and bulk writes from repopulating invalidated keys or deleting newer mset() values by sharing a per‑key ordering boundary. Prevented stale single and bulk writes from repopulating invalidated keys or deleting newer mset() values by sharing a per‑key ordering boundary. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Medium |
Replaced reusable per‑key epoch values with monotonic tokens to eliminate ABA windows for stale writes. Replaced reusable per‑key epoch values with monotonic tokens to eliminate ABA windows for stale writes. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Low |
RedisInvalidationBus emits a warning when constructed without signingSecret and a logger is provided. RedisInvalidationBus emits a warning when constructed without signingSecret and a logger is provided. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Low |
Bounded retained write‑ordering state with configurable global, active‑key, and per‑key limits; rejects saturation with CacheWriteSaturationError. Bounded retained write‑ordering state with configurable global, active‑key, and per‑key limits; rejects saturation with CacheWriteSaturationError. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Security | Low |
Fixed per‑key fetch rate limiting so a ready key can preempt a later shared drain timer chosen by a throttled key. Fixed per‑key fetch rate limiting so a ready key can preempt a later shared drain timer chosen by a throttled key. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Breaking | High |
Public CacheStack reads now return undefined for misses and negative-cache entries instead of null. Public CacheStack reads now return undefined for misses and negative-cache entries instead of null. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Breaking | High |
Read-through fetchers now cache null as a regular value by default; `getOrThrow()` returns stored nulls while throwing only for undefined. Read-through fetchers now cache null as a regular value by default; `getOrThrow()` returns stored nulls while throwing only for undefined. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Breaking | High |
Automatically derived structured `wrap()` keys now use the `j2:` schema; old `j:` entries become cold misses and expire naturally. Automatically derived structured `wrap()` keys now use the `j2:` schema; old `j:` entries become cold misses and expire naturally. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Breaking | Low |
Write coordination applies finite default limits and can reject saturated work with CacheWriteSaturationError. Write coordination applies finite default limits and can reject saturated work with CacheWriteSaturationError. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
| Feature | Medium |
Updated Vite and related development‑only transitive dependencies to resolve the release audit finding. Updated Vite and related development‑only transitive dependencies to resolve the release audit finding. Source: llm_adapter@2026-07-19 Confidence: high |
— |
| Refactor | Low |
Updated API, migration, serialization, resilience, integration, architecture‑decision, and security documentation for v4 miss/null semantics, finite cleanup/write limits, structured‑key rotation, HTTP credential handling, CLI guard, and playground isolation boundary. Updated API, migration, serialization, resilience, integration, architecture‑decision, and security documentation for v4 miss/null semantics, finite cleanup/write limits, structured‑key rotation, HTTP credential handling, CLI guard, and playground isolation boundary. Source: granite4.1:30b@2026-07-19-audit Confidence: high |
— |
Full changelog
Breaking
- Public
CacheStack, namespace,wrap(),getOrSet(), andmget()reads now returnundefinedfor misses and negative-cache entries instead ofnull, resolving issue #90. - Read-through fetchers now cache
nullas a regular value by default, andgetOrThrow()returns stored nulls while throwing only forundefined. SetcacheNullValues: falsewhen null represents absence. - Automatically derived structured
wrap()keys use thej2:schema, so oldj:entries become cold misses and expire naturally. - Write coordination and generation cleanup now apply finite default limits and can reject saturated work with
CacheWriteSaturationError.
Security
- Fixed implicit Express/Hono private URL caching so requests containing sensitive query parameters, including OAuth
client_secretand client assertions, bypass implicit URL-only caching unless a customkeyResolveris supplied. - Fixed namespace clearing so
CacheNamespace.clear()invalidates the delimiter-bound namespace prefix (for exampletenant:), preventing sibling namespace eviction such asaclearingab. - Fixed DiskLayer protected reads so plaintext legacy payloads are rejected when
encryptionKeyorsigningKeyis configured. AddedallowLegacyPlaintextas an explicit migration-only opt-in. - Fixed generation cleanup to stream old-generation keys into bounded batches and cap its unique-key discovery set at 10,000 entries by default.
- Changed OpenTelemetry instrumentation to export
layercache.key_hashby default. Rawlayercache.keyattributes now requireincludeRawKeyAttributes: true. - Hardened snapshot commits by rejecting symlinked target parents immediately before the final rename.
- Hardened CLI invalidation so every wildcard-only pattern such as
*,**, and?*requires--force, matching the default full-cache invalidation guard. - Hardened CLI Redis connection errors by scrubbing Redis URLs from nested error messages before printing them.
- Added a Redis invalidation warning when
RedisInvalidationBusis constructed withoutsigningSecretand a logger is provided. - Hardened the docs playground by keeping the token-bearing worker message sender outside user-code global reach, in addition to sandboxed iframe, Blob Worker, and token checks.
- Prevented plain objects from forging native structured cache-key tags and rotated structured argument keys from
j:toj2:so previously ambiguous entries are not reused. - Prevented stale single and bulk writes from repopulating invalidated keys or deleting newer
mset()values by sharing one per-key ordering boundary and checking fences after backend writes. - Bounded retained write-ordering state with configurable global, active-key, and per-key limits that reject saturation with
CacheWriteSaturationError. - Replaced reusable per-key epoch values with monotonic tokens so maintenance pruning cannot create an ABA window for stale writes.
- Fixed per-key fetch rate limiting so a ready key can preempt a later shared drain timer chosen by a throttled key.
Changed
- Updated Vite and related development-only transitive dependencies to resolve the release audit finding.
- Updated the README and benchmarking guide with a fresh reproducible 4.0.0 baseline covering direct, HTTP, payload, slow-Redis, queue-amplification, memory-pressure, invalidation, and distributed single-flight scenarios.
- Corrected benchmark TTLs to use the documented millisecond units and updated Autocannon plus its Hyperid override so the HTTP benchmark runs on current Node.js with zero npm audit findings.
- Updated API, migration, serialization, resilience, integration, architecture-decision, and security documentation for v4 miss/null semantics, finite cleanup/write limits, structured-key rotation, HTTP credential handling, CLI guard, and playground isolation boundary.
Tests
- Added regression coverage for all security fixes and hardening changes, including shared drain-timer preemption, disposal of queued rate-limited work, epoch rollover, empty serialized writes, symlinked snapshot bases, and serializer undefined behavior. The unit suite reports 672 passing tests and 24 skipped tests; the real Redis integration suite adds 25 passing tests.
- Added docs-web content assertions for the updated security-sensitive documentation.
Breaking Changes
- Public `CacheStack`, namespace, `wrap()`, `getOrSet()`, and `mget()` now return `undefined` for misses and negative-cache entries instead of `null`.
- Read‑through fetchers cache `null` as a regular value by default; set `cacheNullValues: false` to revert.
- Automatically derived structured `wrap()` keys use the `j2:` schema, making old `j:` entries cold misses that expire naturally.
- Write coordination and generation cleanup apply finite default limits and can reject saturated work with `CacheWriteSaturationError`.
Security Fixes
- Fixed implicit Express/Hono private URL caching to bypass sensitive query parameters (e.g., OAuth `client_secret`) unless a custom `keyResolver` is supplied.
- Fixed namespace clearing so `CacheNamespace.clear()` only invalidates the intended delimiter‑bound prefix, preventing sibling namespace eviction.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About flyingsquirrel0419/layercache
All releases →Beta — feedback welcome: [email protected]