-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix(mcp): reject unsafe Deep Agents projections #10911
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 all commits
8f73e9d
27e7e58
b0b7f95
f5ec932
da6ee52
7080578
abeb166
235be35
f856804
460b9e0
aeef43c
0a6b3bc
1b5d57c
147adef
ca27909
527e895
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,25 @@ | |
|
|
||
| export const DEEPAGENTS_MCP_MAX_SERVERS = 64; | ||
|
|
||
| const DEEPAGENTS_UNSAFE_MCP_PROJECTION_CLASSIFIERS = [ | ||
| { predicate: "stat.S_ISLNK(metadata.st_mode)", type: "symbolic link" }, | ||
| { predicate: "stat.S_ISFIFO(metadata.st_mode)", type: "FIFO" }, | ||
| ] as const; | ||
| const DEEPAGENTS_UNSAFE_MCP_PROJECTION_FALLBACK_TYPE = "non-regular file"; | ||
| export const DEEPAGENTS_UNSAFE_MCP_PROJECTION_TYPES = [ | ||
| ...DEEPAGENTS_UNSAFE_MCP_PROJECTION_CLASSIFIERS.map(({ type }) => type), | ||
| DEEPAGENTS_UNSAFE_MCP_PROJECTION_FALLBACK_TYPE, | ||
| ] as const; | ||
|
|
||
| const DEEPAGENTS_MANAGED_PROJECTION_TYPE_HELPERS = [ | ||
| "def describe_managed_projection_type(metadata):", | ||
| ...DEEPAGENTS_UNSAFE_MCP_PROJECTION_CLASSIFIERS.flatMap(({ predicate, type }) => [ | ||
| ` if ${predicate}:`, | ||
| ` return ${JSON.stringify(type)}`, | ||
| ]), | ||
| ` return ${JSON.stringify(DEEPAGENTS_UNSAFE_MCP_PROJECTION_FALLBACK_TYPE)}`, | ||
| ]; | ||
|
|
||
| export const DEEPAGENTS_STRICT_JSON_HELPERS = [ | ||
| "def reject_duplicate_keys(pairs):", | ||
| " result = {}", | ||
|
|
@@ -19,6 +38,9 @@ export const DEEPAGENTS_STRICT_JSON_HELPERS = [ | |
|
|
||
| export const DEEPAGENTS_MANAGED_PROJECTION_READ_HELPERS = [ | ||
| "MANAGED_MCP_MAX_BYTES = 262144", | ||
| "class UnsafeManagedProjectionError(ValueError):", | ||
| " pass", | ||
| ...DEEPAGENTS_MANAGED_PROJECTION_TYPE_HELPERS, | ||
| "def managed_fingerprint(metadata):", | ||
| " return (metadata.st_dev, metadata.st_ino, metadata.st_size, metadata.st_mtime_ns, metadata.st_ctime_ns, metadata.st_mode, metadata.st_nlink, metadata.st_uid)", | ||
| "def managed_path_identity(path):", | ||
|
|
@@ -37,7 +59,10 @@ export const DEEPAGENTS_MANAGED_PROJECTION_READ_HELPERS = [ | |
| "def validate_managed_descriptor_path(path, descriptor):", | ||
| " opened = os.fstat(descriptor)", | ||
| " linked = os.stat(path, follow_symlinks=False)", | ||
| " safe = (stat.S_ISREG(opened.st_mode) and opened.st_uid == os.getuid() and stat.S_IMODE(opened.st_mode) == 0o600 and opened.st_nlink == 1 and (opened.st_dev, opened.st_ino) == (linked.st_dev, linked.st_ino))", | ||
| " for metadata in (opened, linked):", | ||
| " if not stat.S_ISREG(metadata.st_mode):", | ||
| " raise UnsafeManagedProjectionError(describe_managed_projection_type(metadata))", | ||
| " safe = (opened.st_uid == os.getuid() and stat.S_IMODE(opened.st_mode) == 0o600 and opened.st_nlink == 1 and (opened.st_dev, opened.st_ino) == (linked.st_dev, linked.st_ino))", | ||
| " if not safe:", | ||
| " raise ValueError('managed MCP projection has unsafe ownership, mode, type, links, or path identity')", | ||
| " return managed_fingerprint(opened)", | ||
|
|
@@ -49,6 +74,11 @@ export const DEEPAGENTS_MANAGED_PROJECTION_READ_HELPERS = [ | |
| " except FileNotFoundError:", | ||
| " assert_managed_source_stable(path, None)", | ||
| " return b'', None, None", | ||
| " except OSError:", | ||
| " linked = os.stat(path, follow_symlinks=False)", | ||
|
Collaborator
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. [P1] Preserve every unsafe type when the initial open fails This fallback only converts symlinks to
Contributor
Author
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. Fixed the in-scope FIFO path in 235be35. The fallback now classifies every non-regular lstat result with describe_managed_projection_type, and the public command-boundary regression uses a mode-000 FIFO while still requiring exit 2, type-specific stderr, and empty stdout. I did not add the unlink-between-open/stat case because removal races are explicitly excluded from #10754. |
||
| " if not stat.S_ISREG(linked.st_mode):", | ||
| " raise UnsafeManagedProjectionError(describe_managed_projection_type(linked)) from None", | ||
| " raise", | ||
| " try:", | ||
| " before = os.fstat(descriptor)", | ||
| " validate_managed_descriptor_path(path, descriptor)", | ||
|
|
||
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.
[P1] Fail closed if the rejected symlink disappears during classification
After
os.open(...O_NOFOLLOW)has already rejected the final symlink, an untrusted sandbox process can unlink it before this follow-upos.stat. ThatFileNotFoundErrorescapes into the outer missing-projection handler, which printsabsentand exits 0; a deterministic command-level reproduction produced exactly that result. Preserve the original no-follow failure or raise the typed unsafe error when this lookup disappears or changes, and cover the transition with a regression test.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.
Leaving this unchanged because it is the removal race explicitly excluded from #10754. If the entry disappears between the no-follow open failure and fallback classification, the observable final state is absent and retains the existing exit-0 behavior. Closing that interleaving would require broader race semantics outside this PR.