Skip to content

[Bug bounty] Workspace-scoped secrets are not isolated by workspaceRoot; secret.add / secret.list pool all secrets under one session workspaceId #253

Description

@mobster31

Bounty eligibility

  • I have signed up at monk.io with this GitHub account (@mobster31)
  • I have used the product (added and listed workspace-scoped secrets across two different project roots via the plugin's secret tools, as part of managing per-project app secrets)
  • I have starred this repository

Stage

configure / manage secrets

Coding agent used

Claude Code (Monk plugin v0.1.52)

Summary

secret.add and secret.list accept a workspaceRoot argument, and every stored secret persists a per-secret workspaceRoot field — both of which imply that scope: "workspace" secrets are isolated per project directory. They are not.

Every scope: "workspace" secret created within an agent session is written under a single, session-bound workspaceId, regardless of the workspaceRoot passed. Consequently, workspace-scoped secrets belonging to different project roots are pooled together and cross-list: listing secrets while pointing at workspace A returns secrets that were added for workspace B, and vice-versa. The workspaceRoot argument is silently accepted but never used to resolve/scope the operation, and the persisted workspaceRoot metadata ends up inconsistent with the workspaceId the secret is actually stored under.

Repro steps (via the plugin tools only)

Let A and B be two unrelated project directories (in my run: A = C:\Users\<me>\Desktop\Data, the session's default root, and B = C:\Users\<me>\.hermes\monk-test-app).

  1. Add a workspace secret without an explicit root (uses session default root A):
    secret.add { name: "hpx", value: "m2", scope: "workspace" }
  2. Add another workspace secret with an explicit, different root B:
    secret.add { name: "iso_probe_mta", value: "mta1", scope: "workspace", workspaceRoot: "<B>" }
  3. List with workspaceRoot = B:
    secret.list { scope: "workspace", workspaceRoot: "<B>" }
    → returns both hpx (added under A) and iso_probe_mta.
  4. List with workspaceRoot = A:
    secret.list { scope: "workspace", workspaceRoot: "<A>" }
    → also returns both.

Expected: list(B) returns only secrets added for B; list(A) only those for A.
Actual: both lists return the full pooled set of the user's workspace secrets.

Reproduced / cross-verified (three independent sources agree)

1) Tool outputsecret.list with workspaceRoot=A and with workspaceRoot=B returned the identical full set. Four test secrets were added (hpx alpha beta, hpx, HPX under root A; iso_probe_mta under root B); both list calls returned all four:

{ "secrets": [
  { "name": "hpx alpha beta", "scope": "workspace", "source": "manual", ... },
  { "name": "hpx",            "scope": "workspace", "source": "manual", ... },
  { "name": "HPX",            "scope": "workspace", "source": "manual", ... },
  { "name": "iso_probe_mta",  "scope": "workspace", "source": "manual", ... }
] }

2) Local vault index (~/.monk/agent/store/global/credentials/index%<base64 userId>.json) — four entries; three carry "workspaceRoot": "...\\Desktop\\Data" and one carries "workspaceRoot": "...\\.hermes\\monk-test-app", yet all four share the same "workspaceId": "ws_a6336bf25581d5f5e78d8155":

{ "name": "hpx",           "scope": "workspace", "workspaceId": "ws_a6336bf25581d5f5e78d8155", "workspaceRoot": "C:\\Users\\<me>\\Desktop\\Data", ... }
{ "name": "iso_probe_mta", "scope": "workspace", "workspaceId": "ws_a6336bf25581d5f5e78d8155", "workspaceRoot": "C:\\Users\\<me>\\.hermes\\monk-test-app", ... }

3) Audit log (~/.monk/agent/store/audit/secret-access.json) — all four "Store secret via secret.add" write records, including the one added with workspaceRoot=...\monk-test-app, are logged under the same workspaceId:

{ "operation": "write", "scope": "workspace", "workspaceId": "ws_a6336bf25581d5f5e78d8155", "keyHash": "154e3490", ... }
{ "operation": "write", "scope": "workspace", "workspaceId": "ws_a6336bf25581d5f5e78d8155", "keyHash": "92dd85ed", ... }   // iso_probe_mta, added with workspaceRoot=...\monk-test-app

Root cause (as far as visible from persisted state)

The workspaceId used to key workspace-scoped secrets is bound once to the agent session and reused for every secret operation. The workspaceRoot argument is copied into the per-secret metadata but is never used to resolve/compute the workspace, so it can neither scope reads nor isolate writes. The platform workspace registry (~/.monk/agent/store/global/platform-cache/workspaces_*.json) lists monk-test-app as its own distinct project, so the two roots are genuinely separate workspaces being collapsed onto one id.

Impact

  • Workspace-scope isolation — advertised through the workspaceRoot parameter and the per-secret workspaceRoot field — does not hold. A secret intended for project B is visible to a session operating in project A (same user). This defeats the purpose of a per-workspace secret scope.
  • The persisted workspaceRoot field is self-contradictory with the workspaceId it is stored under, so any tooling (or operator) that trusts that field is misled about which project a secret belongs to.
  • Open question I did not verify (flagging for maintainers): the cluster scopes (org/account/project/env) write to the shared Monk KV store. If they key by workspace with the same workspaceRoot-insensitive logic, the impact would extend beyond a single user's local vault to cross-project secret exposure on the platform. I tested only local workspace scope; this is worth checking.

Expected behavior

workspace scope should isolate by the resolved workspace: either honor workspaceRoot (resolve it to the correct workspaceId and scope reads/writes to it), or reject / warn when the provided workspaceRoot does not match the bound session workspace. It should not silently accept the argument, persist contradictory workspaceRoot metadata, and pool every workspace secret under one session id.

Actual behavior

workspaceRoot is accepted but ignored; all scope: "workspace" secrets in a session share one workspaceId and cross-list across different project roots.

Environment

  • OS: Windows 11
  • Monk CLI/daemon: v3.21.1, build 0ecb47b4
  • Monk plugin (Claude Code): v0.1.52

Duplicate check

Searched open/closed issues for "secret workspace", "secret isolation", "workspace secrets", "secret scope", and "workspaceRoot". Found a diagnostics-path issue (#134, MANIFEST LOAD/DIRS paths outside workspaceRoot) and several "reports success when it fails" issues (#201/#202/#203/#204/#243), but none covering secret workspace-scope isolation or workspaceRoot not being honored by the secret tools.

Payout address

(N/A — cash prize is per contest ranking per the published rules, not per-bug on-chain payout; leaving this blank.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions