Repository navigation
refactor(mcp): credential-migration tools are listed only for the keys the backend admits (#3435) - #3484
refactor(mcp): credential-migration tools are listed only for the keys the backend admits (#3435)#3484vybe wants to merge 11 commits into
Conversation
…ackend admits (#3435) export_credentials, import_credentials and get_credential_encryption_key carry a per-tool canAccess allow-list {user, system} (fails closed, #848), descriptions that name who may call each, and a backend 403 returned as a typed {success:false, human_only|admin_only|not_authorized} envelope through accessDenied so the audit row reads denied (#2807). 400/404/503 stay thrown. No backend change. Mutations verified: deny-check allow-list and an unstamped envelope each turn agents.credentials.test.ts red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, with a shrink-only admin-only guard (#3435) Boots the real server in key mode: a user key lists the three credential tools, an agent key neither lists nor can call them (not found). Every other ADMIN_ONLY row is either hidden from agent sessions or in a shrink-only baseline of nine (follow-up per ruling TD-3). Dropping one tool's canAccess turns it red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s by principal (#3435) A pin of current behaviour, no backend change: import/export admit an owner or admin human and refuse every agent key with the exact human-only 403 the MCP mapper keys on (uniform 404 for a non-owner); the key route admits an admin human only (403 non-admin, agent, ops scope; 503 when the key is unset). Explicit models.User principals. Mutation: removing reject_agent_principal from the import handler turns 2 cases red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User-docs MCP list and FAQ migration entry name the caller class per tool (the FAQ gains the target-side step: set CREDENTIAL_ENCRYPTION_KEY in .env and restart before importing). The feature flow's stale export/import handler snippets now show the real dependency chain, and its MCP section and the mcp-server architecture row describe the per-tool allow-list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntence, never the body (I1) (#3435) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scoped key the allow-list admits (I2) (#3435) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plitting it (I3) (#3435) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ote (I3) (#3435) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ads (#3435) test_inject_assigned_credentials.py replaces sys.modules["services.credential_encryption"] at collection time, and the export/import handlers resolve get_credential_encryption_service through a function-local import at request time. In a shared xdist worker the stub this file installed on its import-time alias was never read, the real service ran, and every 200-path case got a 503 ("Failed to connect to agent"). The fixture now patches the module currently in sys.modules, overrides the exact get_current_user object the router's Depends() holds, and stubs the db the ownership dependency closes over. Same principals, real router and dependencies, same 25 cases; no backend change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
CI note: the first run's |
…l-mcp-tools # Conflicts: # tests/registry.json
…ever by replacing the target's key (#3435) The migration entry told operators to set the source instance's key as CREDENTIAL_ENCRYPTION_KEY on the target. On a target that already holds encrypted secrets that strands every one of them. The decrypt path already falls back to CREDENTIAL_ENCRYPTION_KEY_SECONDARY, so that is the safe slot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Resolve by running |
|
Resolve by merging |
Fixes #3435. One of five independent stale-code PRs off
dev. Draft until the live exercise below is recorded.What
The issue was filed as "two credential tools fail on every call". The diagnosis found no defect: the requests are correct, and agent-scoped keys are refused by design (import and export are owner-or-admin and human-only; the encryption-key route is admin-only). This PR makes the catalog honest about that.
export_credentials,import_credentialsandget_credential_encryption_key(src/mcp-server/src/tools/agents.ts) each gain a per-toolcanAccessallow-list of user and system sessions. An agent session no longer lists them, and calling one by name answers "not found". The previous group default listed them for user, agent and system, so this can only remove a tool from a session.human_only/admin_only/not_authorized) and recorded as denied in the audit log. A 403 without a JSON detail returns a fixed sentence; the response body is never copied. 400, 404 and 503 are thrown exactly as before.tests/unit/test_3435_credential_tool_gates.py, which pins the three backend routes' existing gates by explicit principal.No backend change:
git diff origin/dev...HEAD -- src/backend dockeris empty. Nobody gains access to anything.Rulings carried (operator, recorded on the issue 2026-10-09)
Review + security
/review(reading review, report-only): MERGEABLE, 0 critical. Hard limits verified: theaccess.tsrows keep their policy kinds (the owner strings are labels and enforce nothing), the allow-list is a filter on the registered set, a future scope defaults to hidden, and the system key is admitted by all three backend gates./cso --diff: no findings; the attack surface shrinks. Fixed after review: the non-JSON 403 fallback no longer echoes the response body (test red first), "user-scoped or system-scoped" wording, and a list-formatting fix in the user doc.Tests
npm test836 / 836; the two changed test files 36 / 36.test_3435_credential_tool_gates.py25 passed, plus the auth-wiring, admin-gate and human-only route suites — 150 passed in six files.canAccessfrom one tool, hiding a baselined tool without shrinking the baseline, advertising an undecided admin-only tool, removing the audit stamp, and removing the agent-principal rejection from the import handler each turn a test red.git merge-treeagainstdev(21b4a11): clean.Before merge
admin_only; (c) an agent-scoped key: none of the three is listed and a call by name answers "not found".export_credentials(A)→ read.credentials.encfrom A's workspace →inject_credentials(B, …)→import_credentials(B)→get_credential_status(B)lists the files.denied.src/mcp-server/src/access.ts), andtests/registry.jsonwith fix(public-chat): an anonymous session finds its own history after a reload (#3443) #3477, fix(settings): the email whitelist validates what it stores and can remove whatever it holds (#3455, #3456) #3479 and refactor(api): remove nine uncalled routes and the orphan observability store (#3434) #3483.Handoffs
require_interactiveon the encryption-key route — follow-up issue to be filed.agents.tsgrew ~50 lines), refactor(security): consolidate the eight private MCPin-toolpermission-gate copies and add a generic denial sweep #3374 (a module-local refusal mapper), refactor(reliability): MCP tools that return a failure are audited as success — withAudit only sees throws #2926 (400/404/503 are still audited as failures).🤖 Generated with Claude Code