Skip to content

fix: reject non-finite (NaN/inf) TTL to prevent immortal cache entries#174

Open
27Bslash6 wants to merge 2 commits into
mainfrom
fix/nonfinite-ttl-guard
Open

fix: reject non-finite (NaN/inf) TTL to prevent immortal cache entries#174
27Bslash6 wants to merge 2 commits into
mainfrom
fix/nonfinite-ttl-guard

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #158. A NaN or inf TTL slipped past every TTL guard because they all use a < comparison and IEEE-754 comparisons against NaN/inf are False. The result was a cache entry whose expiry never triggers — stale data served indefinitely (DATA IS SACRED).

Changes

All three TTL guard sites now reject non-finite values:

  • config/decorator.py: DecoratorConfig validation rejects a non-finite ttl up front via math.isfinite, before the existing < 0 check — so @cache(ttl=float("nan")) fails loud at decoration.
  • l1_cache.py: put() skips caching when the computed expiry is non-finite (alongside the existing too-short-TTL guard), so no immortal L1 entry can form from a NaN/inf redis_ttl or expires_at.
  • object_cache.py: put() rejects a non-finite ttl alongside the existing ttl < 1 check.

Tests

Unit tests (run on PRs) cover all three sites with both NaN and inf:

  • test_decorator_config.py: non-finite ttl raises at validation
  • test_l1_memory_bounds.py: non-finite redis_ttl/expires_at are not stored
  • test_object_cache.py: non-finite ttl raises

Verification

  • ruff clean, basedpyright 0 errors, 52 unit tests pass across the three touched test files.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened TTL validation across caching so non-finite values (NaN, ±inf) are rejected in addition to existing negative/zero checks, preventing invalid cache entries.
  • Tests

    • Added unit tests covering non-finite TTL handling for decorator config, L1 cache and object cache to ensure correct rejection and safe memory accounting.

A NaN or inf TTL slipped past every TTL guard because all of them use a `<`
comparison, and NaN/inf comparisons are False. The result was an entry whose
expiry never triggers, serving stale data indefinitely (DATA IS SACRED).

- config/decorator.py: DecoratorConfig validation now rejects a non-finite ttl
  up front (math.isfinite) with a clear error, before the existing < 0 check.
- l1_cache.py: put() skips caching when the computed expiry is non-finite (in
  addition to the existing too-short-TTL guard), so no immortal L1 entry forms.
- object_cache.py: put() rejects a non-finite ttl alongside the existing ttl < 1
  check.

Tests cover all three sites with NaN and inf.

Fixes #158
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 371e812b-6bf8-4169-b921-e9beea09623a

📥 Commits

Reviewing files that changed from the base of the PR and between 8978c10 and 5721515.

📒 Files selected for processing (3)
  • tests/unit/config/test_decorator_config.py
  • tests/unit/test_l1_memory_bounds.py
  • tests/unit/test_object_cache.py

Walkthrough

This pull request adds non-finite TTL validation across three cache layers: DecoratorConfig, L1Cache, and ObjectCache. Each layer imports math and applies isfinite() checks to reject NaN and infinity at configuration, put operations, and expiry computation, preventing immortal cache entries.

Changes

Non-finite TTL validation across cache layers

Layer / File(s) Summary
DecoratorConfig TTL finite-value validation
src/cachekit/config/decorator.py, tests/unit/config/test_decorator_config.py
DecoratorConfig._validate_config() now imports math and validates ttl is finite before checking non-negativity, raising ValueError for NaN/inf. Parametrised test verifies rejection of both values.
L1Cache expiry guard for non-finite values
src/cachekit/l1_cache.py, tests/unit/test_l1_memory_bounds.py
L1Cache.put() imports math and updates the expiry guard to reject non-finite effective expiry times. New test class verifies non-finite redis_ttl and expires_at values are not stored and leave memory accounting at zero.
ObjectCache put TTL finite-value validation
src/cachekit/object_cache.py, tests/unit/test_object_cache.py
ObjectCache.put() imports math and enforces finite TTL before the >= 1 check, updating the error message to reference "finite number" and using repr formatting. Parametrised test verifies NaN and inf are rejected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: rejecting non-finite TTL values (NaN/inf) to prevent immortal cache entries.
Description check ✅ Passed The PR description covers the problem, motivation, changes across all three sites, tests added, and verification results. All key sections are present and complete.
Linked Issues check ✅ Passed All coding requirements from issue #158 are met: non-finite TTLs are rejected in config validation, skipped in L1Cache.put, and rejected in ObjectCache.put, with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes directly address issue #158's objectives of preventing non-finite TTLs from bypassing guards and creating immortal entries. No out-of-scope changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/nonfinite-ttl-guard

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/config/test_decorator_config.py`:
- Around line 97-101: The parameterized test test_non_finite_ttl_rejected
currently checks float("nan") and float("inf") but misses negative infinity;
update the param list for bad_ttl in that test (the DecoratorConfig(ttl=bad_ttl)
assertion) to include negative infinity (e.g., float("-inf") or -float("inf"))
so the test asserts both +inf and -inf are rejected as non-finite TTLs.

In `@tests/unit/test_l1_memory_bounds.py`:
- Around line 71-79: The parameterized tests
test_non_finite_redis_ttl_not_stored and test_non_finite_expires_at_not_stored
only cover float("nan") and float("inf"); update both
`@pytest.mark.parametrize`("bad_ttl", [...]) decorators to also include negative
infinity (e.g. float("-inf")) so the tests cover ±inf; locate the decorators
above the functions test_non_finite_redis_ttl_not_stored and
test_non_finite_expires_at_not_stored and add float("-inf") to the list of
bad_ttl cases.

In `@tests/unit/test_object_cache.py`:
- Around line 76-81: Update the parameterized inputs for
test_non_finite_ttl_raises so it includes negative infinity as well as NaN and
positive infinity: modify the `@pytest.mark.parametrize` call that sets "bad_ttl"
in function test_non_finite_ttl_raises to include float("-inf") along with
float("nan") and float("inf"), ensuring the test rejects NaN/±inf TTLs when
calling ObjectCache.put("k", "value", ttl=bad_ttl).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a41184f3-9ddf-4cf7-8ed9-78b416948b96

📥 Commits

Reviewing files that changed from the base of the PR and between 82d0417 and 8978c10.

📒 Files selected for processing (7)
  • .secrets.baseline
  • src/cachekit/config/decorator.py
  • src/cachekit/l1_cache.py
  • src/cachekit/object_cache.py
  • tests/unit/config/test_decorator_config.py
  • tests/unit/test_l1_memory_bounds.py
  • tests/unit/test_object_cache.py

Comment thread tests/unit/config/test_decorator_config.py Outdated
Comment thread tests/unit/test_l1_memory_bounds.py Outdated
Comment thread tests/unit/test_object_cache.py Outdated
@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/cachekit/config/decorator.py 66.66% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Non-finite (NaN/inf) TTL creates an immortal L1 entry; unvalidated X-TTL to SaaS

1 participant