-
Notifications
You must be signed in to change notification settings - Fork 0
feat(governance): unify policy engine on Capability + migrate MCP #495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
0306f26
7f6a854
511f30c
22fdab7
0b9acbb
f813df0
627392a
3dedaa2
7cfd3cb
a00b9ab
c771e7f
f7e8b2b
f77a13f
36b86e3
88c0d97
3834ce9
eba2232
1f2b4d7
0007629
29b7a48
5e4a1c2
56db109
d13703f
c8b1e00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Codex P1 on #495 (round 9): copy_file(source=".env", destination="/tmp/x") read a credentials file and duplicated its contents into a non-sensitive location. With my round-7 model, copy emitted FileWrite(dst) - WRITE risk - which auto-approves in accept-edits mode. Pre-#495 MCP tools prompted via the EXECUTE fallback, so this was a regression specifically introduced by the CapabilityAware migration. Re-classify copies-from-sensitive-source as FileDelete(src). The op doesn't actually delete the source; the classification is intentionally over-conservative so the structural denial layer refuses the operation regardless of where it lands. Audit log shows @type=FileDelete for what is technically a read+write; the toolName field still carries mcp__<server>__copy_file so operators can correlate. Unified disambiguation order for move/copy ops: 1. dst sensitive -> FileWrite(dst), denied 2. src present AND (isMove OR src sensitive) -> FileDelete(src) - moves (any src): DANGEROUS prompt for the delete - copies from sensitive src: structurally denied as exfil 3. dst present (safe + safe copy) -> FileWrite(dst), WRITE risk OK Two existing tests retargeted - their old assertions pinned the wrong behaviour: - copyFromSensitiveSourceDoesNotInferDelete (was: expect McpInvoke) -> copyFromSensitiveSourceWithoutDestinationInfersFileDelete - copyFromSensitiveSourceWithSafeDestinationStaysAsFileWrite -> copyFromSensitiveSourceToSafeDestinationIsStructurallyDeniedAsExfil (plus an end-to-end assertion that DefaultPermissionPolicy denies the resulting FileDelete in auto-accept mode) 38 tests pass in McpToolBridgeTest. Full build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -254,30 +254,38 @@ private Capability inferFileCapability(JsonNode args) { | |
| // "delete" target (for moves only β copies leave the source). | ||
| // Disambiguation order: | ||
| // 1. dst is sensitive β FileWrite(dst). Catches "move/copy to | ||
| // .env" β destination-write attack. | ||
| // 2. Op is a move (not copy) and src resolves β FileDelete(src). | ||
| // Two purposes: | ||
| // a. Catches "move .env away" β source-delete attack the | ||
| // destination-first model missed (Codex P1 follow-up #2 | ||
| // .env" β the destination-write attack. | ||
| // 2. src resolves AND (op is a move OR src is sensitive) β | ||
| // FileDelete(src). Two distinct purposes folded together: | ||
| // a. For MOVES (regardless of src sensitivity): the | ||
| // source really is removed, and FileDelete's DANGEROUS | ||
| // risk prompts in accept-edits where FileWrite would | ||
| // auto-approve. Pre-#495 MCP moves prompted via | ||
| // EXECUTE risk; this preserves that floor (Codex P2 | ||
| // on #495). | ||
| // b. Preserves DANGEROUS risk on the move semantics so even | ||
| // benign moves don't auto-approve in accept-edits mode. | ||
| // Pre-#495 MCP moves prompted via EXECUTE risk; FileWrite | ||
| // is WRITE which IS auto-approved in accept-edits β that | ||
| // would silently delete the source. FileDelete is | ||
| // DANGEROUS, which is the closest match (Codex P2 | ||
| // on #495). | ||
| // 3. Pure copies (no source side-effect) β FileWrite(dst). Risk | ||
| // is the genuine WRITE risk; auto-approval in accept-edits | ||
| // is fine because the source is intact. | ||
| // b. For COPIES from a sensitive source: copies don't | ||
| // actually delete the source, but duplicating a | ||
| // credentials file to a non-sensitive location IS | ||
| // exfiltration. Pre-#495 the same call prompted via | ||
| // the EXECUTE fallback; emitting FileDelete here | ||
| // triggers the structural denial via the sensitive | ||
| // source path (Codex P1 on #495 β "preserve prompts | ||
| // for copies from sensitive sources"). Audit log | ||
| // shows @type=FileDelete for a copy β slightly | ||
| // misleading but safer than missing the denial; the | ||
| // toolName field still carries | ||
| // mcp__<server>__copy_file so operators can correlate. | ||
| // 3. Pure copy with non-sensitive src (dst is safe per step 1) | ||
| // β FileWrite(dst). Genuine WRITE risk; auto-approval in | ||
| // accept-edits is fine. | ||
| Path dst = safePath(extractField(args, DESTINATION_FIELDS)); | ||
| Path src = safePath(extractField(args, SOURCE_FIELDS)); | ||
| boolean isMove = !COPY_VERB.matcher(name).matches(); | ||
|
|
||
| if (dst != null && DefaultPermissionPolicy.isSensitivePath(dst)) { | ||
| return new Capability.FileWrite(dst, WriteMode.OVERWRITE); | ||
| } | ||
| if (isMove && src != null) { | ||
| if (src != null && (isMove || DefaultPermissionPolicy.isSensitivePath(src))) { | ||
| return new Capability.FileDelete(src); | ||
| } | ||
| if (dst != null) return new Capability.FileWrite(dst, WriteMode.OVERWRITE); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the MCP method is a copy and the destination is non-sensitive, this returns Useful? React with πΒ / π. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an MCP
move_file/renamehas a destination and both paths are non-sensitive, this emitsFileWrite, whoseWRITErisk is auto-approved inaccept-editsmode (DefaultPermissionPolicyapproves READ/WRITE). I checked the dispatcher path: MCP tools used to default toEXECUTEinStreamingAgentHandler, so the same move previously prompted; nowmove_file(source="/tmp/a", destination="/tmp/b")can silently remove the source even thoughCapability.FileDeleteis intentionallyDANGEROUSto avoid auto-approving deletions. Model non-copy moves as requiring the delete-side risk, or otherwise keep them at MCP/execute risk when they remove a source.Useful? React with πΒ / π.