Skip to content

fix(security): add bws_cache.json to file_safety read guard - #32092

Closed
AhmetArif0 wants to merge 1 commit into
NousResearch:mainfrom
AhmetArif0:fix/bws-cache-read-guard
Closed

fix(security): add bws_cache.json to file_safety read guard#32092
AhmetArif0 wants to merge 1 commit into
NousResearch:mainfrom
AhmetArif0:fix/bws-cache-read-guard

Conversation

@AhmetArif0

Copy link
Copy Markdown
Contributor

Summary

PR #31968 introduced a Bitwarden Secrets Manager disk cache at <hermes_home>/cache/bws_cache.json to avoid re-fetching secrets across back-to-back CLI invocations. The cache stores plaintext secret values (API keys, database passwords, etc. pulled from BSM projects).

get_read_block_error() in agent/file_safety.py was not updated to include this path, leaving the agent able to read it directly via the read_file tool — the same tool that already cannot read auth.json, .anthropic_oauth.json, or mcp-tokens/*.

Fix

Add os.path.join("cache", "bws_cache.json") to credential_file_names in get_read_block_error(), using the same exact-file match pattern already in place for auth.json, .anthropic_oauth.json, and auth/google_oauth.json. Both HERMES_HOME and the global Hermes root are covered, consistent with the existing per-profile + root-level guard shape (#15981).

Other files under cache/ (images, documents, audio) are unaffected — the check is an exact-file match, not a prefix match.

 credential_file_names = (
     "auth.json",
     "auth.lock",
     ".anthropic_oauth.json",
     ".env",
     "webhook_subscriptions.json",
     os.path.join("auth", "google_oauth.json"),
+    # Bitwarden Secrets Manager disk cache: stores plaintext secret values
+    # to avoid re-fetching across back-to-back CLI invocations. The file
+    # was introduced by #31968 but not added to this guard.
+    os.path.join("cache", "bws_cache.json"),
 )

Test plan

  • cache/bws_cache.json under HERMES_HOME → BLOCKED
  • Error message mentions credential (consistent with existing messages)
  • auth.json, .anthropic_oauth.json, .env, webhook_subscriptions.json, mcp-tokens/* → still BLOCKED (no regressions)
  • cache/images/photo.png, cache/documents/report.pdf, cache/audio/clip.mp3 → still ALLOWED
  • bws_cache.json outside HERMES_HOME → ALLOWED (per-location gate, not a basename block)
  • 38/38 existing test_file_safety.py + test_file_safety_credentials.py tests pass

The Bitwarden Secrets Manager disk cache introduced in NousResearch#31968 stores
plaintext secret values at <hermes_home>/cache/bws_cache.json to avoid
re-fetching across back-to-back CLI invocations. The file was not added
to get_read_block_error()'s credential_file_names list, leaving the
agent able to read it directly via the read_file tool.

Add os.path.join("cache", "bws_cache.json") to credential_file_names
so both HERMES_HOME and the global root are covered, matching the
existing pattern used for auth.json, .anthropic_oauth.json, etc.

Other files under cache/ (images, documents, audio) are unaffected —
the check is an exact-file match, not a prefix match.

Verified: 11/11 exploit/regression scenarios pass; 38/38 existing
file_safety tests pass.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 25, 2026

@hclsys hclsys left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified the fix against main. Correct and tight:

  • Path is realagent/secret_sources/bitwarden.py:431 writes the cache to <hermes_home>/cache/bws_cache.json (_DISK_CACHE_BASENAME = "bws_cache.json", :86), so os.path.join("cache", "bws_cache.json") matches the actual on-disk layout.
  • Matching works for the nested path — the credential loop does (hd / name).resolve() then resolved == blocked (file_safety.py:253-258); the nested-join entry resolves to the exact absolute file, exactly like the existing os.path.join("auth", "google_oauth.json") entry. No allowlist/SAFE_ROOTS short-circuits cache/ ahead of this branch (only the skills .hub deny runs earlier), so the guard takes effect.
  • Temp file is a non-issue — the writer uses mkstemp(prefix=".bws_cache_", suffix=".tmp") + chmod 0600 + atomic os.replace (bitwarden.py:155-162), so the plaintext temp has an unpredictable name and a sub-ms window; not a realistic read_file target, and the "defense-in-depth, not a boundary" framing already covers it.

One consistency observation, not a blocker: this is an exact-file entry. If BSM ever grows a second cache artifact under cache/ (per-project file, rotation, etc.), this won't cover it — the same enumeration-vs-prefix gap that motivated #32090 to switch credential dirs to a whole-tree deny. Given cache/ legitimately holds non-secret files too, exact-match is the right call here; just flagging that the basename is now load-bearing if the cache layout changes.

LGTM.

@teknium1

Copy link
Copy Markdown
Contributor

Salvaged in PR #34421 → merged to main. cache/bws_cache.json is now in the file_safety read-block list. Other cache files (images, audio, documents) remain readable since the check is exact-name not prefix. Authorship preserved via rebase-merge.

Thanks — this closed a real defense-in-depth gap from #31968.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants