Skip to content

fix(secrets): key the single-secret cache on every request parameter - #64

Open
RishavTiwari25 wants to merge 1 commit into
Infisical:mainfrom
RishavTiwari25:fix/secret-cache-key
Open

RishavTiwari25 wants to merge 1 commit into
Infisical:mainfrom
RishavTiwari25:fix/secret-cache-key

Conversation

@RishavTiwari25

Copy link
Copy Markdown

Problem

With caching on (the default, cache_ttl=60), secrets.get_secret_by_name can return the wrong secret.

get_secret_by_name keyed its cache entry on project_id, environment_slug, secret_path and secret_name only:

  • Projects addressed by slug share entries. With project_slug=... the key has project_id=None, so API_KEY in dev / of project A and of project B is one cache entry. The second call returns project A's value for project B.
  • Read options are ignored. version, view_secret_value, expand_secret_references and include_imports are not in the key. After reading version="1" or view_secret_value=False, a normal read within the TTL returns the old version or the masked value.
  • Update/delete by slug leave stale entries. The mutation methods unset a key built from their own project_id. A secret read with project_id and then updated or deleted with project_slug stays cached with its old value.

Fix

  • get_secret_by_name builds its key from the secret name plus the full request params, like list_secrets already does.
  • Since one secret can now be cached under several keys (id or slug, read options), create_secret_by_name, update_secret_by_name and delete_secret_by_name drop every cached single secret (invalidate_operation(CACHE_KEY_SINGLE_SECRET)) together with the cached lists, which they already dropped. create_secret_by_name no longer pre-fills the cache with the create response; the next read fetches it once.

Tests

The repo had no unit tests, so this adds tests/test_secrets_cache.py, which runs V3RawSecrets against a real SecretsCache and a mocked InfisicalRequests:

  • two reads that differ only in project slug, version, view_secret_value, expand_secret_references or include_imports both hit the API and get their own value (5 cases);
  • identical reads are still served from the cache;
  • update and delete by project_slug invalidate a secret cached by project_id.

7 of the 8 tests fail on main and all pass with the fix (pytest tests). flake8 . --select=E9,F63,F7,F82 (the CI check) is clean, and so is plain flake8 tests.

get_secret_by_name built its cache key from project_id, environment,
path and name only. With project_slug (project_id None), the same secret
name in two projects shared one entry, so the second project got the first
project's value. version, view_secret_value, expand_secret_references and
include_imports were also ignored: a masked value or an old version was
returned for later default reads.

The key now uses the request parameters, like list_secrets. Because a
secret can be cached under several keys (id or slug, read options),
create, update and delete now drop all cached single secrets along with
the lists; before, update/delete by project_slug left the entry cached by
project_id in place.
@infisical-cla-app

Copy link
Copy Markdown

📝 Contributor License Agreement required

Before this PR can merge, every contributor must sign the Infisical CLA.
Signing is quick: sign in with GitHub, review the CLA, and accept.

👉 Sign the CLA

Still needs to sign:

Once everyone has signed, the check updates automatically — no need to close and reopen the PR.

@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[Medium impact] The change reaches secret reads and writes made through this SDK.

The PR appears safe to merge, with non-blocking gaps in test coverage and CI execution.

Findings

  1. P2 CI skips the new tests ▶
  2. P2 Create clearing has no test ▶

Summary

Single-secret cache keys now include the secret name and every request parameter. Create, update, and delete clear all cached single secrets alongside cached lists.

  • Each single-secret request gets its own cache entry.
  • Secret writes clear every single-secret cache entry.

Reviews (1) · Last reviewed commit: "fix(secrets): key the single-secret cach..." · Reviewed by Greptile

({"project_id": "p1", "include_imports": False}, {"project_id": "p1"}),
],
)
def test_get_secret_by_name_does_not_share_cache_across_requests(cache, first, second):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 CI skips the new tests

The pull-request workflow still has its pytest step commented out, so these new tests never run in CI. Enable that step now that tests exist. Otherwise, later changes can break secret caching while CI stays green.

assert requests.get.call_count == 1


@pytest.mark.parametrize("mutation", ["update_secret_by_name", "delete_secret_by_name"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Create clearing has no test

These tests cover only update_secret_by_name and delete_secret_by_name, although create_secret_by_name also changes from filling the cache to clearing it. Add a case that caches one secret, creates another, and checks that rereading the first hits the API. Removing the new create invalidation call would currently leave every test green.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

1 participant