Repository navigation
Conversation
The Valkey backend stored entries under the raw "METHOD URL BODY" key, but clear() deleted `KEYS searxng:*`. It never matched this cache's own entries, and on a database shared with an upstream SearXNG it deleted that instance's keys instead (audit A12). ValkeyCache now prefixes every key with "rustxng:cache:" for GET, SET and clear(). clear() walks the keyspace with SCAN ... MATCH instead of a blocking KEYS and DELs each page. Entries written by older versions use the old key format; they are no longer reachable and expire via their TTL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Give the Valkey cache backend one key namespace for store, load and delete, and make
clear()walk that namespace withSCANinstead ofKEYS, as prescribed by A12 indocs/AUDIT-2026-11-02.md.Root cause
ResponseCache::key()builds keys as"{method} {url} {body}", andValkeyCache::set()/get()used that raw key. ButValkeyCache::clear()insrc/cache.rsranKEYS searxng:*and deleted whatever matched. Stored keys never start withsearxng:, soclear()removed none of this cache's own entries — and if the same Valkey/Redis database is shared with an upstream SearXNG instance, it deleted that instance's keys instead.Changes
All in
src/cache.rs(+217/−26):rustxng:cache:, is applied insideValkeyCacheonset()andget()and matched byclear(), so all three operations agree on the key format.ResponseCache::key()and the in-memory backend are unchanged.About the prefix: the audit asks for one namespace but does not name it.
rustxng:keeps these keys clear of upstream SearXNG'ssearxng:*keys in a shared database (the collision this bug is about), and thecache:segment keepsclear()from ever wiping keys a future RustXNG feature might store in the same database. Happy to adjust if another prefix is preferred.clear()now iteratesSCAN <cursor> MATCH rustxng:cache:* COUNT 100until the cursor returns to 0, deleting each page with a single multi-keyDEL. A malformed reply or a failed command stops the walk and counts an error instead of silently ignoring it.request(), which returns a reply with its structure intact (SCAN replies are[cursor, [keys…]], which the existing flattening would lose). The previouscommand()behaviour is preserved exactly asrequest()plusValkeyReply::flatten().clear_removes_own_entries_and_leaves_other_namespaces, runs against the existing fake-RESP-server harness (no real Valkey needed). It stores two entries through the cache, seeds the fake keyspace with foreign keys (searxng:limiter,searxng:cache,other), callsclear(), and asserts its own entries are gone while all foreign keys remain. The fake SCAN returns one key per page so the cursor loop and empty pages are exercised. The wire-level key assertions incancelled_commands_do_not_reuse_pending_replieswere updated for the prefixed keys.One compatibility note: entries written by older versions under the raw, unprefixed keys become unreachable after this change. They are not deleted explicitly; each one simply expires through its TTL (5 minutes by default), the same as any other cache entry.
Verification
Linux x86_64, Rust 1.99.1:
main, fails —clear()leaves this cache's entries in place (and removes the foreignsearxng:*keys instead), which is the reported bug.cargo test --locked --lib cache: 11 passed, 0 failed (includes the new regression test).cargo test --locked --all-targets: library suite 1036 passed, 5 failed, 1 ignored. The 5 failures (aggregator::orchestration_tests::raw_content_*×3 andfetch::direct::tests::{configured_body_limit_is_enforced_and_classified, renderer_fallback_static_keeps_the_extracted_text}) fail identically on unmodifiedmainin this sandbox and are unrelated to this change; they appear to depend on the sandbox's network/fetch environment.cargo fmt --all --check: passed.cargo clippy --locked --all-targets: no warnings (CI runs it with-D warnings).Limitations
Other platforms are left to CI. No live Valkey/Redis server was available in the sandbox, so server behaviour is covered through the fake-RESP harness only.
Related
Addresses A12 in the existing audit.
AI assistance: implementation and tests.