Skip to content

fix: re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name#200

Open
27Bslash6 wants to merge 2 commits into
mainfrom
fix/195-194-encryption-config
Open

fix: re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name#200
27Bslash6 wants to merge 2 commits into
mainfrom
fix/195-194-encryption-config

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Two encryption-config correctness fixes.

Closes #195. Closes #194.

#195 — keyless settings singleton froze permanently

get_settings() built CachekitConfig once and cached it process-wide. If the first build happened before CACHEKIT_MASTER_KEY was in the environment — e.g. an import-time @cache/@cache.io decorator evaluated before the app loads its secrets (python-dotenv, cloud secret managers) — it froze master_key=None forever. Setting the env var afterward had no effect: with auto-encryption that means data is cached plaintext though a key is present, and with explicit @cache.secure it surfaces as #194's confusing "master key required".

Fix: get_settings() now re-reads once the key appears (master_key is None and CACHEKIT_MASTER_KEY now set → rebuild, under the existing lock). Idempotent — after the rebuild master_key is set, so it never fires again (no per-call churn).

#194 — missing-key error named a nonexistent env var

The error and a master_key docstring told users to set REDIS_CACHE_MASTER_KEY, which is never read; the only honored variable is CACHEKIT_MASTER_KEY. Corrected both strings (encryption_wrapper.py + cache_handler.py), matching the already-correct sibling message in config/nested.py.

Scope / known residual

The self-heal fixes the singleton and any handler built after the key loads. A CacheSerializationHandler is built eagerly when @cache is applied (decoration/import) and freezes its encryption decision at __init__; a handler frozen keyless at import is not retroactively unfrozen by this change. Fully closing that (auto-encryption + import-time decorator + secrets-after-import) needs lazy encryption resolution in the handler — invasive, security-critical, deferred to its own PR. Note @cache.secure (explicit) already fails loud in that window, so only the auto-encryption path is silent.

Tests

New tests/unit/test_encryption_config_resolution.py: get_settings() re-reads when the key appears (the #195 repro), stays stable once loaded, and the missing-key error names CACHEKIT_MASTER_KEY. Full suite: 1866 passed, basedpyright 0 errors.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Encryption configuration now automatically reinitialises when the required master key environment variable becomes available after startup, eliminating the need for manual reset operations.
  • Documentation & Error Handling

    • Updated encryption error messaging to correctly reference CACHEKIT_MASTER_KEY when the master key is missing.

…ing-key env var name

#195: get_settings() cached the config once; if first built before CACHEKIT_MASTER_KEY was in the environment (e.g. an import-time cache decorator before secrets load), it froze master_key=None forever — encryption then silently never activated. get_settings() now re-reads once the key appears (under the existing lock; idempotent, no churn after).

#194: the missing-key error and a docstring named REDIS_CACHE_MASTER_KEY, which is never read; corrected both to CACHEKIT_MASTER_KEY (the only honored var), matching config/nested.py.

Closes #194. Closes #195.
@coderabbitai

coderabbitai Bot commented Jun 21, 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: d34e27b6-1d60-495a-a8a8-962624be614a

📥 Commits

Reviewing files that changed from the base of the PR and between da81c09 and e22c3f7.

📒 Files selected for processing (1)
  • src/cachekit/config/singleton.py

Walkthrough

Corrects REDIS_CACHE_MASTER_KEY to CACHEKIT_MASTER_KEY in the EncryptionError message and CacheSerializationHandler docstring. Extends get_settings() with a double-checked locking self-heal that rebuilds the CachekitConfig singleton when master_key was cached as None but CACHEKIT_MASTER_KEY is now present in the environment. Adds a new unit test module validating both behaviours.

Changes

Master key env var fix and singleton self-heal

Layer / File(s) Summary
Correct CACHEKIT_MASTER_KEY references in error messages and docs
src/cachekit/serializers/encryption_wrapper.py, src/cachekit/cache_handler.py
_setup_encryption raises EncryptionError naming CACHEKIT_MASTER_KEY, and CacheSerializationHandler.__init__ docstring references the same env var.
get_settings() self-heal under double-checked locking
src/cachekit/config/singleton.py
Adds import os and a double-checked lock block in the get_settings() fast-path that detects master_key=None with CACHEKIT_MASTER_KEY present in the environment and rebuilds the singleton via CachekitConfig.from_env().
Unit tests for self-heal and correct error message
tests/unit/test_encryption_config_resolution.py
Adds TestKeylessSingletonSelfHeals covering the None-then-set and already-present-key scenarios, and TestMissingKeyErrorNamesCorrectEnvVar asserting the EncryptionError message names CACHEKIT_MASTER_KEY and not REDIS_CACHE_MASTER_KEY.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • cachekit-io/cachekit-py#133: Updates test fixtures to call reset_settings() and prevent CACHEKIT_MASTER_KEY environment leaks between test runs, directly interacting with the singleton get_settings() and master-key resolution logic modified in this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the two main changes: singleton re-read behaviour and correction of environment variable naming.
Description check ✅ Passed The description thoroughly covers motivation, scope, and testing, though it doesn't strictly follow all template sections like explicit type-of-change checkboxes.
Linked Issues check ✅ Passed All changes directly address the objectives from #195 (singleton re-read mechanism) and #194 (corrected env var names in messages).
Out of Scope Changes check ✅ Passed All code changes are scoped to fixing #195 and #194; no unrelated modifications detected in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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/195-194-encryption-config

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

🤖 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 `@src/cachekit/config/singleton.py`:
- Around line 49-60: The code has a race condition where another thread calling
reset_settings() between the unlocked check on line 55 and the locked check on
line 59 could set _settings_instance to None, causing an AttributeError when
trying to access _settings_instance.master_key. Capture _settings_instance into
a local variable before the initial unlocked check, then use that cached
reference throughout the condition to prevent the instance from becoming None
between the two checks.
🪄 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: bd606741-8805-4d1f-81ab-56cd841ff3cf

📥 Commits

Reviewing files that changed from the base of the PR and between 9749121 and da81c09.

📒 Files selected for processing (4)
  • src/cachekit/cache_handler.py
  • src/cachekit/config/singleton.py
  • src/cachekit/serializers/encryption_wrapper.py
  • tests/unit/test_encryption_config_resolution.py

Comment thread src/cachekit/config/singleton.py
@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/cachekit/config/singleton.py 88.88% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…ncurrent-reset AttributeError)

Addresses CodeRabbit on #200: the #195 self-heal dereferenced _settings_instance.master_key in the unlocked fast path after a non-None check. A concurrent reset_settings() between the two could null the global and AttributeError (or return None). Snapshot the global to a local for the unlocked checks; re-read it under the lock (rebuilding if a peer reset or it is still keyless).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant