Skip to content

Fix query result cache leaking across security contexts: include full SecurityClaims in cache key#9704

Open
nishantmonu51 wants to merge 2 commits into
mainfrom
nishant/fix-resolver-cache-security-claims
Open

Fix query result cache leaking across security contexts: include full SecurityClaims in cache key#9704
nishantmonu51 wants to merge 2 commits into
mainfrom
nishant/fix-resolver-cache-security-claims

Conversation

@nishantmonu51

Copy link
Copy Markdown
Collaborator
  • The query cache key in runtime.Resolve only hashed Claims.UserAttributes, so requests that differed only in AdditionalRules (e.g. locked filters on magic auth tokens), Permissions, or SkipChecks produced identical cache keys and could receive each other's cached results.
  • Now the full marshaled SecurityClaims is hashed into the key, matching what the security engine's computeCacheKey already does.
  • Fixing it centrally in Resolve covers all resolvers (metrics, metrics_sql, annotations, unions); the legacy runtime.Query path already included the full claims in its key and was not affected.
  • Added regression tests in metrics_security.yaml that run the same query back-to-back with identical (empty) user attributes but different security contexts; without the fix, three of the four cases fail by returning another context's cached rows.

Steps to reproduce (before this fix):

  1. Deploy a project with a metrics view on a Rill-managed table (default caching on) with a dimension such as country, and an explore dashboard on it.
  2. From the dashboard's Share flow, create two public (magic auth) URLs for the same dashboard: URL A locked to filter country = 'US', URL B locked to country = 'EU'. Both are anonymous, so both carry empty user attributes; the locked filters travel as AdditionalRules.
  3. Open URL A in an incognito window and note the totals (this populates the query cache).
  4. Immediately open URL B in another incognito window and run the same view/query.
  5. Expected: URL B shows EU-filtered numbers. Actual: URL B shows URL A's US-filtered numbers (cache hit on the identical key). Repeating in the opposite order flips which result wins.
  6. Alternative path: define two custom APIs over the same metrics query, one with skip_nested_security: true and one without; call the skipping one first, then the enforcing one with identical user attributes — the second returns the unfiltered cached result.

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

The resolver query cache key only hashed the claims' user attributes,
so requests differing only in additional security rules (e.g. locked
filters on magic auth tokens), permissions, or skipped security checks
produced identical keys and could receive each other's cached results.
Hash the full marshaled SecurityClaims instead, matching the security
engine's own computeCacheKey.
@nishantmonu51 nishantmonu51 changed the title Fix query result cache leaking across security contexts: include full \SecurityClaims\ in cache key Fix query result cache leaking across security contexts: include full SecurityClaims in cache key Jul 19, 2026
@nishantmonu51

Copy link
Copy Markdown
Collaborator Author

@codex: review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 52748ff86b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread runtime/resolver.go Outdated
// Hash the full security claims, not just the user attributes:
// permissions, additional rules (e.g. locked filters on magic auth tokens) and skipped checks
// all change the resolved security policy, and results must not be shared across them.
claimsJSON, err := json.Marshal(opts.Claims)

@pjain1 pjain1 Jul 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: not sure about the improvement but should we skip hashing userID (present in Claims) as it will prevent cross user cache result sharing if everything else is same?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

May be its fine as it is as user attributes have email which would be unique anyways, might help in embed use cases a little.

The user ID does not affect the resolved security policy: built-in rules
derive user identity from the "id" and "email" user attributes, which
remain part of the key. Excluding it lets requests that resolve to the
same policy share cached results, which helps embed use cases where the
user ID varies per session.

Addresses review feedback from @pjain1.
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