Skip to content

🐛 fix(cache): structured default cache keys, and controls#4224

Merged
ReneWerner87 merged 22 commits intomainfrom
implement-structured-cache-key-builder
Apr 23, 2026
Merged

🐛 fix(cache): structured default cache keys, and controls#4224
ReneWerner87 merged 22 commits intomainfrom
implement-structured-cache-key-builder

Conversation

@gaby
Copy link
Copy Markdown
Member

@gaby gaby commented Apr 20, 2026

Motivation

  • Harden cache key generation to avoid accidental collisions from path-only keys (e.g. /?id=1 vs /?id=2) while keeping deterministic, bounded key sizes.
  • Provide explicit configuration to control which request dimensions partition the cache (query, headers, cookies, and Vary).
  • Improve Vary handling and make caching behavior safer for authorization and streaming scenarios.

Description

  • Introduced a structured default key generator defaultKeyGenerator that builds keys from request method (partitioned internally), path, canonicalized query string, configured header subset (KeyHeaders), and explicit cookie subset (KeyCookies), with long segments hashed via boundKeySegment.
  • Added new Config fields: DisableQueryKeys, KeyHeaders, KeyCookies, and DisableVaryHeaders, removed the old Methods list in favor of caching only GET/HEAD by default, and made KeyGenerator nil by default to use the structured generator.
  • Added canonicalization helpers for query strings, header and cookie subsets, normalization (normalizeKeyDimensions), and protections for long key segments; updated Vary handling to respect DisableVaryHeaders and avoid storing/using Vary manifests when disabled.
  • Updated middleware, tests, and docs to reflect new defaults and options, including examples showing custom KeyGenerator, guidance on default key behavior, and a note in Ctx/Res interfaces documenting that End() does not work in streaming modes.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Cache middleware now uses structured, method-prefixed cache keys (path, canonical query, selected headers/cookies) with canonicalization, size limits and hashing fallbacks; adds config toggles for query and Vary inclusion; expands tests and docs; and adds GoDoc notes that Ctx.End() / Res.End() are incompatible with streaming modes.

Changes

Cohort / File(s) Summary
Interface docs
ctx_interface_gen.go, res_interface_gen.go
Added GoDoc notes: End() is not functional in streaming modes (e.g., fasthttp HijackConn, Fiber SendStream) and ctx.Conn() may be nil; no signature changes.
Cache core implementation
middleware/cache/cache.go
Introduced defaultKeyGenerator (method-prefixed structured keys), canonical query parsing with strict limits and hashing fallback, delimiter escaping, bounded segment hashing, parseVary limits, and DisableVaryHeaders gating. Also refined Cache-Control directive matching to accept tab OWS.
Cache configuration
middleware/cache/config.go
KeyGenerator default set to nil (structured generator installed when unset); added KeyHeaders, KeyCookies, DisableQueryKeys, DisableVaryHeaders; Methods semantics normalized (nil = default methods, empty = disable).
Cache tests
middleware/cache/cache_test.go, middleware/cache/cache_security_test.go
Updated tests to assert new dimensioned keys and added comprehensive security/stress tests covering delimiter escaping, query limits, Vary abuse, malformed queries, path hashing, header/cookie partitioning, and concurrency.
Docs & migration
docs/middleware/cache.md, docs/whats_new.md
Documented new default key composition, new config options (DisableQueryKeys, KeyHeaders, KeyCookies, DisableVaryHeaders, Methods semantics), migration guidance, and updated examples.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CacheMW as Cache Middleware
  participant KeyGen as defaultKeyGenerator
  participant Store as Cache Store
  participant Origin as Origin Handler

  Client->>CacheMW: Request(method, path, query, headers, cookies)
  CacheMW->>KeyGen: Build key (method|path|query|headers|cookies)
  KeyGen-->>CacheMW: Structured key (escaped/hashed segments)
  CacheMW->>Store: Lookup(key [, vary-manifest])
  alt cache hit & valid
    Store-->>CacheMW: cached response
    CacheMW-->>Client: response (hit)
  else miss or uncacheable
    CacheMW->>Origin: Call handler
    Origin-->>CacheMW: response (+Vary header)
    CacheMW->>Store: Store response (partition by Vary if enabled)
    CacheMW-->>Client: response (miss)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🧹 Updates, 📒 Documentation

Suggested reviewers

  • sixcolors
  • efectn

Poem

🐰 I hopped through keys both safe and neat,
Method-first paths and queries made sweet,
Pipes escaped, long strings hashed away,
Vary optional — caches hum and play,
Streams bound their dance while caches keep the tray. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% 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 references the main cache middleware overhaul (structured default cache keys and configuration controls), which aligns with the substantial changes across cache.go, config.go, and documentation.
Description check ✅ Passed The PR description comprehensively addresses motivation, core implementation details (structured key generator, new Config fields, Vary handling improvements), and mentions documentation and test updates, covering most required template sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch implement-structured-cache-key-builder

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ReneWerner87 ReneWerner87 added this to v3 Apr 20, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Apr 20, 2026
@gaby gaby changed the title cache: structured default cache keys, key-dimension options, Vary controls, docs and tests 🐛 fix(cache): structured default cache keys, and controls Apr 20, 2026
@gaby gaby moved this to In Progress in v3 Apr 20, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

❌ Patch coverage is 84.37500% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.17%. Comparing base (9fd194f) to head (050ff1f).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
middleware/cache/cache.go 82.43% 12 Missing and 14 partials ⚠️
middleware/cache/config.go 90.90% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4224      +/-   ##
==========================================
- Coverage   91.24%   91.17%   -0.08%     
==========================================
  Files         123      123              
  Lines       11910    12076     +166     
==========================================
+ Hits        10867    11010     +143     
- Misses        655      668      +13     
- Partials      388      398      +10     
Flag Coverage Δ
unittests 91.17% <84.37%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the cache middleware's key generation strategy by moving from a path-only approach to a structured key that includes the request method, canonicalized query strings, and selected headers or cookies. It introduces several new configuration options like KeyHeaders, KeyCookies, DisableQueryKeys, and DisableVaryHeaders while removing the configurable Methods option in favor of a fixed GET and HEAD restriction. Feedback suggests improving performance by using a sync.Pool for buffer allocations in the key generator and optimizing the normalization of key dimensions. Additionally, it was noted that the method check condition could be simplified for better readability.

Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/cache.go
Comment thread middleware/cache/config.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the cache middleware by switching the default cache key from path-only to a structured, deterministic key derived from multiple request dimensions, while adding config controls to tune key partitioning and Vary handling. It also updates tests and documentation to reflect the new defaults and clarifies End() behavior in streaming scenarios.

Changes:

  • Introduces structured default cache key dimensions (method partitioning, path, canonical query, selected headers/cookies) and adds config toggles (DisableQueryKeys, KeyHeaders, KeyCookies, DisableVaryHeaders).
  • Restricts cache participation to GET/HEAD requests and updates Vary manifest usage based on DisableVaryHeaders.
  • Updates cache tests and docs to match the new behavior and adds interface documentation notes about End() and streaming.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
middleware/cache/cache.go Implements the new structured default key generation and conditional Vary behavior; changes method eligibility to GET/HEAD only.
middleware/cache/config.go Adds new key-dimension config fields, updates defaults, and normalizes key dimension lists.
middleware/cache/cache_test.go Updates tests to align with new key semantics and GET/HEAD-only caching; adds new key-dimension coverage.
docs/middleware/cache.md Documents the new default key behavior and new config options.
docs/whats_new.md Notes the breaking/default behavior changes for cache keys and request method handling.
ctx_interface_gen.go Adds a note that End() doesn’t work in streaming modes.
res_interface_gen.go Adds a note that End() doesn’t work in streaming modes.

Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/cache.go
Comment thread middleware/cache/cache.go
Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/config.go Outdated
Comment thread middleware/cache/config.go Outdated
github-actions[bot]

This comment was marked as outdated.

@gaby
Copy link
Copy Markdown
Member Author

gaby commented Apr 21, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 21, 2026

@copilot apply changes based on the comments in this thread

Applied the requested cache review fixes in f56bfa1. The update bounds long default path segments, preserves case-sensitive KeyCookies, keeps Vary: * uncacheable even when DisableVaryHeaders is set, and adds regression coverage for those cases.

Copilot finished work on behalf of gaby April 21, 2026 02:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the cache middleware’s default cache-key generation to avoid path-only collisions and adds explicit configuration knobs to control which request dimensions partition cached responses, with corresponding test and documentation updates.

Changes:

  • Introduces a structured default cache key (method + path + canonical query + selected headers/cookies), plus key-dimension normalization and key-segment bounding.
  • Changes default caching behavior to only apply to GET/HEAD and adds options to disable query keying and Vary-based partitioning.
  • Updates tests and docs to reflect the new defaults and configuration surface.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
middleware/cache/cache.go Implements structured default key generation, GET/HEAD-only caching, and conditional Vary-manifest handling.
middleware/cache/config.go Adds new config fields and normalizes header/cookie key dimensions; wires default key generator when KeyGenerator is nil.
middleware/cache/cache_test.go Updates existing expectations for new key format/defaults and adds coverage for key-dimension behavior and bounded segments.
docs/middleware/cache.md Documents the new default key behavior and new configuration fields; updates examples/config table.
docs/whats_new.md Notes the cache-key hardening and new cache configuration options in release notes.
ctx_interface_gen.go Regenerates interface docs to include the End() streaming caveat.
res_interface_gen.go Regenerates interface docs to include the End() streaming caveat.

Comment thread middleware/cache/config.go Outdated
Comment thread middleware/cache/cache.go Outdated
@gaby
Copy link
Copy Markdown
Member Author

gaby commented Apr 21, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the cache middleware’s default key generation to avoid collisions (especially from query strings), adds config knobs for controlling which request dimensions partition cache entries, and updates docs/tests accordingly.

Changes:

  • Replaced path-only default cache keys with a structured key built from method + path + canonical query + selected headers/cookies (with bounded segments and hashing).
  • Added configuration controls for query/header/cookie partitioning and for disabling Vary-based partitioning.
  • Expanded/updated test suite and documentation to reflect safer defaults and new options.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
middleware/cache/cache.go Implements structured default key generation, canonicalization helpers, and Vary handling controls.
middleware/cache/config.go Introduces new key-dimension config fields and normalization helpers; updates defaults.
middleware/cache/cache_test.go Updates existing tests for new key format/defaults and adds key-dimension tests.
middleware/cache/cache_security_test.go Adds security-focused tests for DoS bounds and key-dimension behaviors.
docs/middleware/cache.md Documents new defaults and config options for key partitioning and Vary behavior.
docs/whats_new.md Notes the breaking/default behavior changes and new configuration surface.
ctx_interface_gen.go Adds clarification to End() about streaming-mode limitations.
res_interface_gen.go Adds clarification to End() about streaming-mode limitations.

Comment thread middleware/cache/cache.go
Comment thread middleware/cache/config.go
Comment thread middleware/cache/cache_security_test.go
Comment thread middleware/cache/cache.go
Comment thread middleware/cache/cache.go
@gaby
Copy link
Copy Markdown
Member Author

gaby commented Apr 23, 2026

@claude[agent] apply changes based on the comments in this thread

@Claude
Copy link
Copy Markdown
Contributor

Claude AI commented Apr 23, 2026

Pull request overview

This PR hardens the cache middleware’s default key generation to avoid collisions (especially from query strings), adds config knobs for controlling which request dimension...

All security issues have been addressed in commit 047de64:

DoS Protection (Issue #1): Added pre-scan to canonicalQueryString that counts ampersands before calling url.ParseQuery, preventing CPU/memory exhaustion from excessive parameters.

Delimiter Injection (Issues #4 & #5): Implemented escapeKeyDelimiters function that escapes pipe (→ \p), colon (→ \c), and backslash (→ \\) characters in header and cookie values to prevent cache key collisions.

Documentation (Issue #2): Added clarification to KeyHeaders documentation that header names are normalized to lowercase.

Test Coverage (Issue #3): Added three new comprehensive tests:

  • Test_Cache_Security_MultiDimensionInjection - tests multiple headers/cookies with various delimiter combinations
  • Test_Cache_Security_BackslashEscaping - tests backslash escape sequences
  • Test_Cache_Security_DelimiterCollisionPrevention - verifies escaped delimiters don't collide

All tests pass and linter checks are clean.

@gaby gaby marked this pull request as ready for review April 23, 2026 10:55
@gaby gaby requested a review from a team as a code owner April 23, 2026 10:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
middleware/cache/cache.go (1)

595-604: Simplify redundant DisableVaryHeaders gating.

shouldStoreVaryManifest already includes !cfg.DisableVaryHeaders, so the repeated conjunctions on lines 596, 600, and 845 are redundant. Collapsing them improves readability and keeps the invariant in one place.

♻️ Proposed simplification
-		shouldStoreVaryManifest := !cfg.DisableVaryHeaders && len(varyNames) > 0
-		if !cfg.DisableVaryHeaders && len(varyNames) > 0 {
+		shouldStoreVaryManifest := !cfg.DisableVaryHeaders && len(varyNames) > 0
+		if shouldStoreVaryManifest {
 			if key == baseKey {
 				key += buildVaryKey(varyNames, &c.Request().Header)
 			}
 		} else if !cfg.DisableVaryHeaders && hasVaryManifest {

And at line 845:

-		if !cfg.DisableVaryHeaders && shouldStoreVaryManifest {
+		if shouldStoreVaryManifest {
 			if err := storeVaryManifest(reqCtx, manager, manifestKey, varyNames, storageExpiration); err != nil {
 				return err
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@middleware/cache/cache.go` around lines 595 - 604, The gating on
cfg.DisableVaryHeaders is redundant because shouldStoreVaryManifest already
embeds !cfg.DisableVaryHeaders; update the conditions to use
shouldStoreVaryManifest instead of repeating !cfg.DisableVaryHeaders &&
len(varyNames) > 0 (affecting the block that mutates key when key == baseKey and
the else-if that checks hasVaryManifest). Replace those repeated conjunctions
with shouldStoreVaryManifest where appropriate (referencing variables:
shouldStoreVaryManifest, varyNames, key, baseKey, buildVaryKey) and keep the
stale-manifest deletion branch to only run when !shouldStoreVaryManifest &&
hasVaryManifest (using manager.del, manifestKey, maskKey) so logic and behavior
remain unchanged but the DisableVaryHeaders check is centralized.
middleware/cache/config.go (1)

129-164: Closure in configDefault captures a pointer to the local cfg, not the returned copy.

cfg := config[0]
// ...
if cfg.KeyGenerator == nil {
    cfg.KeyGenerator = func(c fiber.Ctx) string {
        return defaultKeyGenerator(c, &cfg) // &cfg == address of the local, heap-escaped
    }
}
return cfg // struct copy; closure still references the original

In practice this is fine because callers don't mutate the returned Config after configDefault, and at return time the local and the copy are value-equal. But it is a subtle footgun: if a caller ever does cfg := configDefault(...); cfg.KeyHeaders = ..., the default key generator will keep using the pre-mutation values. Consider either documenting this or snapshotting KeyHeaders/KeyCookies explicitly in the closure (e.g. keyHeaders := cfg.KeyHeaders; keyCookies := cfg.KeyCookies; ... defaultKeyGenerator(c, &snapshotCfg)), so the generator is clearly bound to the config observed at construction time.

Non-blocking — optional hardening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@middleware/cache/config.go` around lines 129 - 164, configDefault's closure
captures the local cfg variable so subsequent mutations to the returned Config
won't be reflected as expected; fix by snapshotting the config before creating
the closure: create a local copy (e.g. snapshot := cfg) and set KeyGenerator to
use &snapshot (or capture individual slices like keyHeaders := cfg.KeyHeaders,
keyCookies := cfg.KeyCookies and pass those) instead of &cfg, and apply this
change in both the "no config provided" branch and the "override default config"
branch where KeyGenerator is assigned, ensuring the closure binds to the
immutable snapshot observed at construction time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@middleware/cache/cache_security_test.go`:
- Around line 54-89: The test Test_Cache_Security_DoS_ExcessiveQueryBuffer
currently builds a query string that stays below the guard maxQueryBufferSize
and therefore doesn't exercise the fallback in canonicalQueryString; increase
the payload so the post-escape query length exceeds maxQueryBufferSize
(reference canonicalQueryString and maxQueryBufferSize) by enlarging
specialChars (e.g., use a much larger strings.Repeat count such as 200+ or 500+)
and/or increasing the number of queryParams (e.g., 50+), then keep the same
assertions to verify the code hits the buffer-limit path and still returns
correct cache headers and a single handler invocation in the hit case.

In `@middleware/cache/cache.go`:
- Around line 1278-1314: The path segment appended in defaultKeyGenerator
currently uses boundKeySegment(c.Path()) without escaping delimiter characters;
update defaultKeyGenerator to pass the bounded path through escapeKeyDelimiters
(i.e., use escapeKeyDelimiters(boundKeySegment(c.Path()))) before appending so
path characters like '|' or ':' cannot collide with the key structure—mirror how
canonicalHeaderSubset and canonicalCookieSubset are used for headers/cookies to
keep escaping consistent with canonicalQueryString and avoid structural
ambiguity.

---

Nitpick comments:
In `@middleware/cache/cache.go`:
- Around line 595-604: The gating on cfg.DisableVaryHeaders is redundant because
shouldStoreVaryManifest already embeds !cfg.DisableVaryHeaders; update the
conditions to use shouldStoreVaryManifest instead of repeating
!cfg.DisableVaryHeaders && len(varyNames) > 0 (affecting the block that mutates
key when key == baseKey and the else-if that checks hasVaryManifest). Replace
those repeated conjunctions with shouldStoreVaryManifest where appropriate
(referencing variables: shouldStoreVaryManifest, varyNames, key, baseKey,
buildVaryKey) and keep the stale-manifest deletion branch to only run when
!shouldStoreVaryManifest && hasVaryManifest (using manager.del, manifestKey,
maskKey) so logic and behavior remain unchanged but the DisableVaryHeaders check
is centralized.

In `@middleware/cache/config.go`:
- Around line 129-164: configDefault's closure captures the local cfg variable
so subsequent mutations to the returned Config won't be reflected as expected;
fix by snapshotting the config before creating the closure: create a local copy
(e.g. snapshot := cfg) and set KeyGenerator to use &snapshot (or capture
individual slices like keyHeaders := cfg.KeyHeaders, keyCookies :=
cfg.KeyCookies and pass those) instead of &cfg, and apply this change in both
the "no config provided" branch and the "override default config" branch where
KeyGenerator is assigned, ensuring the closure binds to the immutable snapshot
observed at construction time.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1ee35d84-12ee-4cf2-99a9-646e82690839

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd194f and 047de64.

📒 Files selected for processing (8)
  • ctx_interface_gen.go
  • docs/middleware/cache.md
  • docs/whats_new.md
  • middleware/cache/cache.go
  • middleware/cache/cache_security_test.go
  • middleware/cache/cache_test.go
  • middleware/cache/config.go
  • res_interface_gen.go

Comment thread middleware/cache/cache_security_test.go
Comment thread middleware/cache/cache.go
- Restore configurable Methods field (default: GET, HEAD) to replace hardcoded method check, with uppercase normalization and nil vs empty-slice semantics (nil = default, [] = disable caching)
- Fix escapeKeyDelimiters fast-path bug: backslash was not checked, allowing collisions between literal "\p" and escaped "|"
- Fix path delimiter injection: escape pipe/colon/backslash in request path before boundKeySegment to prevent crafted paths from manipulating cache key structure
- Optimize canonicalQueryString: add single-param fast path (skips url.ParseQuery/sort) and use sync.Pool for output buffer
- Simplify string conversions: replace utils.CopyString(utils.UnsafeString(buf)) with string(buf) and utils.UnsafeBytes(boundKeySegment(...)) with direct string append
- Add comprehensive tests: Methods config (POST caching, bypass, empty-slice, lowercase normalization), escapeKeyDelimiters unit regression test with collision-pair verification
- Update docs: Methods field in config table, default config, and migration guide
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@middleware/cache/config.go`:
- Around line 168-177: The cfg.Methods slice is being mutated in place causing
caller-side side effects; before normalizing HTTP method names in the config
initialization (the cfg.Methods handling that currently assigns
ConfigDefault.Methods or uppercases in-place), make a defensive copy of
cfg.Methods when it is non-nil (e.g., newSlice := append([]string(nil),
cfg.Methods...)) and operate on that copy so the caller's slice isn't modified,
and use utilsstrings.ToUpper instead of strings.ToUpper to match existing
helpers (ensure ConfigDefault.Methods stays unchanged when cfg.Methods is nil).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: abbe9c4c-7361-4b4f-bcd4-6d382c5158d4

📥 Commits

Reviewing files that changed from the base of the PR and between 047de64 and 9a0d12c.

📒 Files selected for processing (6)
  • docs/middleware/cache.md
  • docs/whats_new.md
  • middleware/cache/cache.go
  • middleware/cache/cache_security_test.go
  • middleware/cache/cache_test.go
  • middleware/cache/config.go
✅ Files skipped from review due to trivial changes (1)
  • middleware/cache/cache_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/whats_new.md
  • middleware/cache/cache_security_test.go

Comment thread middleware/cache/config.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
middleware/cache/config.go (1)

167-176: ⚠️ Potential issue | 🟡 Minor

Caller's Methods slice is still mutated in place.

The utilsstrings.ToUpper swap was applied, but the defensive copy wasn't. Since cfg := config[0] at line 153 is a shallow copy, cfg.Methods shares the backing array with the caller's slice, so the loop at lines 173–175 writes back into the caller's storage. A caller passing []string{"get","head"} will observe it mutated to ["GET","HEAD"] after cache.New(...).

🛠️ Proposed fix
 	if cfg.Methods == nil {
 		cfg.Methods = ConfigDefault.Methods
 	} else {
 		// Normalize method names to uppercase (HTTP methods are case-sensitive
-		// and c.Method() returns uppercase, e.g. "GET" not "get")
-		for i, m := range cfg.Methods {
-			cfg.Methods[i] = utilsstrings.ToUpper(m)
-		}
+		// and c.Method() returns uppercase, e.g. "GET" not "get").
+		// Copy first to avoid mutating the caller's slice.
+		normalized := make([]string, len(cfg.Methods))
+		for i, m := range cfg.Methods {
+			normalized[i] = utilsstrings.ToUpper(m)
+		}
+		cfg.Methods = normalized
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@middleware/cache/config.go` around lines 167 - 176, The Methods slice is
being mutated in place (cfg.Methods) when normalizing to uppercase, which also
mutates the caller's slice because cfg := config[0] is a shallow copy; to fix,
make a defensive copy of cfg.Methods before normalizing (allocate a new
[]string, copy elements, assign back to cfg.Methods), preserve the special-case
nil => ConfigDefault.Methods behavior, and then run utilsstrings.ToUpper on the
copied slice (reference symbols: cfg.Methods, ConfigDefault.Methods,
utilsstrings.ToUpper, config[0]).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@middleware/cache/config.go`:
- Around line 167-176: The Methods slice is being mutated in place (cfg.Methods)
when normalizing to uppercase, which also mutates the caller's slice because cfg
:= config[0] is a shallow copy; to fix, make a defensive copy of cfg.Methods
before normalizing (allocate a new []string, copy elements, assign back to
cfg.Methods), preserve the special-case nil => ConfigDefault.Methods behavior,
and then run utilsstrings.ToUpper on the copied slice (reference symbols:
cfg.Methods, ConfigDefault.Methods, utilsstrings.ToUpper, config[0]).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ceb63f3d-bfd9-44af-bc2f-1e92517ffe42

📥 Commits

Reviewing files that changed from the base of the PR and between 4d47f7a and c12e753.

📒 Files selected for processing (1)
  • middleware/cache/config.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@middleware/cache/cache_test.go`:
- Line 5454: In middleware/cache/cache_test.go replace the invalid token in the
failing test tuple {"partial match", "no-cach", "no-cache", false} with a valid
word that should still not match "no-cache" (e.g., change "no-cach" to
"no-catch"); update the second element of that test case so the test string is a
real token but remains unequal to "no-cache".

In `@middleware/cache/cache.go`:
- Around line 1338-1340: The fast-path and fallback returns in
middleware/cache/cache.go currently return raw query text (e.g., the
single-parameter fast path that calls boundKeySegment(query) and the parse/limit
fallback branches), which bypasses delimiter escaping and canonicalization;
update the code so that before returning any query-derived segment you run the
same canonicalization/escaping routine used for long-query segments (i.e., pass
the query through the canonicalize/escape function) and then call
boundKeySegment or return that escaped value—apply this change to the single-key
fast path, the parse/limit fallback branches, and any other places where raw
query is returned to ensure no collisions with hashed segments.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: dcb01800-7631-4467-b519-92c37542fb92

📥 Commits

Reviewing files that changed from the base of the PR and between c12e753 and cc62dcb.

📒 Files selected for processing (3)
  • docs/middleware/cache.md
  • middleware/cache/cache.go
  • middleware/cache/cache_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/middleware/cache.md

Comment thread middleware/cache/cache_test.go Outdated
Comment thread middleware/cache/cache.go Outdated
@ReneWerner87 ReneWerner87 merged commit d55a9de into main Apr 23, 2026
22 of 23 checks passed
@ReneWerner87 ReneWerner87 deleted the implement-structured-cache-key-builder branch April 23, 2026 14:57
@github-project-automation github-project-automation Bot moved this from In Progress to Done in v3 Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

🐛 [Bug]: Cache middleware hasDirective misses directives followed by space, tab, or '='

5 participants