Skip to content

Session: a memory store never needs the secret; safer encryptor cache - #10

Merged
eliasjpr merged 1 commit into
masterfrom
claude/azu-crystal-framework-plan-phqt6e-session-followup
Oct 27, 2026
Merged

eliasjpr merged 1 commit into
masterfrom
claude/azu-crystal-framework-plan-phqt6e-session-followup

Conversation

@eliasjpr

Copy link
Copy Markdown
Contributor

Follow-up to #8 and #9. Fixes these findings on the merged code:

  1. Regression: Store#encrypts_with_secret? returned encrypt_redis_data for every store. The :production, :high_security and :clustered presets turn that on, so MemoryStore + one of those presets + no SESSION_SECRET in production made Session.configure raise, and SessionHandler#call returned a 500 on every request, even though MemoryStore never encrypts. This setup worked on the previous master. Now MemoryStore#encrypts_with_secret? is false. ClusteredRedisStore and PooledRedisStore keep the base default, which is correct because they encrypt through RedisStore, and CookieStore stays true.
    • The Session.configure specs that only passed because reset_config sets a MemoryStore now use a stub store that encrypts the way RedisStore does (RedisLikeStore).
    • New specs: under each of the three presets, with CRYSTAL_ENV=production and no secret, a MemoryStore boots and the handler answers 200. Also covered: the elsif config.encrypt_redis_data branch with no store (raises; does not raise when the flag is off), and encrypts_with_secret? for the memory, cookie and Redis stores.
  2. Configuration#encryptor publishes its cache through Atomic(EncryptorCache?) (acquire/release) and reads secret once per call (validate_secret!(key)). The existing cache specs still pass. New specs check that the secret is read once and that fibers can use the encryptor while settings change.
  3. The default-secret error and warning used to suggest c.secret = "your-secure-secret" (18 bytes), which the 32-byte check now refuses. They now point to SESSION_SECRET / openssl rand -hex 32 / c.secret = ENV["SESSION_SECRET"].
  4. reset_config now restores every setting a preset or a spec changes (retry and circuit-breaker config, compression_threshold, log_errors, kdf_salt, cookie options, fail_fast_on_corruption, trust_proxy_headers, session_key) from a fresh Configuration. A spec covers this.
  5. UPGRADE.md: the expiry section now sits right after the secret section. It notes that the :development, :production, :high_security and :clustered presets already set sliding_expiration = true, and documents Store#encrypts_with_secret? for store authors (also added to docs/api/store.md; docs/security/encryption.md is updated to match).

Each new spec was first run against the code without its fix and failed there: the MemoryStore boot/handler specs raised InsecureSecretException, and the read-once, message and reset_config specs failed.

Checks (Crystal 1.22.1, with Redis):

  • crystal tool format --check src spec scripts examples: clean
  • crystal spec: 560 examples, 0 failures, 0 errors, 0 pending
  • crystal spec -Dpreview_mt on the configuration, presets, cookie_store, request_isolation and session_handler_error_handling specs: 115 examples, 0 failures
  • scripts/check_docs.cr on README.md, UPGRADE.md and docs/**/*.md: 157 snippets, 0 failed

Release note: this is meant as a patch release. The release job is currently failing because tag v1.0.32 points at a PR merge-ref commit (7440c2c), so merging this will not publish a release until that tag is fixed.


Generated by Claude Code

MemoryStore#encrypts_with_secret? is false. The :production, :high_security and
:clustered presets turn encrypt_redis_data on, and the Store default follows it,
so a MemoryStore under those presets without SESSION_SECRET failed
Session.configure and every request although it never encrypts.

Configuration#encryptor publishes its cache through Atomic(EncryptorCache?) and
reads the secret once per call. The default-secret messages suggest
SESSION_SECRET and `openssl rand -hex 32` instead of a too-short literal.

Specs: the Session.configure specs use a store that encrypts, cover the branch
without a store, and reset_config restores every setting a preset changes.
UPGRADE.md: expiry next to the secret section, presets that set
sliding_expiration, and Store#encrypts_with_secret? for store authors.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HT5gvYGR83U2M4DyA6NA6f
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@eliasjpr
eliasjpr merged commit e2ebcb3 into master Oct 27, 2026
10 checks passed
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.

2 participants