fix(security): harden project settings trust boundary + MCP sanitization (#789)

* fix(security): harden project settings trust boundary + MCP sanitization

- Sanitize MCP tool result text with recursivelySanitizeUnicode() to prevent
  Unicode injection via malicious MCP servers (tool definitions and prompts
  were already sanitized, but tool call results were not)
- Read sandbox.enabled only from trusted settings sources (user, local, flag,
  policy) — exclude projectSettings to prevent malicious repos from silently
  disabling the sandbox via .claude/settings.json
- Disable git hooks in plugin marketplace clone/pull/submodule operations
  with core.hooksPath=/dev/null to prevent code execution from cloned repos
- Remove ANTHROPIC_FOUNDRY_API_KEY from SAFE_ENV_VARS to prevent credential
  injection from project-scoped settings without trust verification
- Add ssrfGuardedLookup to WebFetch HTTP requests to block DNS rebinding
  attacks that could reach cloud metadata or internal services

Security: closes trust boundary gap where project settings could override
security-critical configuration. Follows the existing pattern established
by hasAllowBypassPermissionsMode() which already excludes projectSettings.

Co-authored-by: auriti <auriti@users.noreply.github.com>

* fix(security): remove unauthenticated file-based permission polling

Remove the legacy file-based permission polling from useSwarmPermissionPoller
that read from ~/.claude/teams/{name}/permissions/resolved/ — an unauthenticated
directory where any local process could forge approval files to auto-approve
tool uses for swarm teammates.

The file polling was dead code:
- The useSwarmPermissionPoller() hook was never mounted by any component
- resolvePermission() (the file writer) was never imported outside its module
- Permission responses are delivered exclusively via the mailbox system:
  Leader: sendPermissionResponseViaMailbox() → writeToMailbox()
  Worker: useInboxPoller → processMailboxPermissionResponse()

Changes:
- Remove file polling loop, processResponse(), and React hook imports from
  useSwarmPermissionPoller.ts (now a pure callback registry module)
- Mark 7 file-based functions as @deprecated in permissionSync.ts
- Add 4 regression tests verifying the removal

No exported functions removed — only deprecated. All 5 consumer modules
verified: they import only mailbox-based functions that remain unchanged.

---------

Co-authored-by: auriti <auriti@users.noreply.github.com>
This commit is contained in:
Juan Camilo Auriti
2026-04-21 12:28:03 +02:00
committed by GitHub
parent a6a3de5ac1
commit ae3b723f3b
8 changed files with 260 additions and 133 deletions

View File

@@ -2524,7 +2524,7 @@ export async function transformResultContent(
return [
{
type: 'text',
text: resultContent.text,
text: recursivelySanitizeUnicode(resultContent.text) as string,
},
]
case 'audio': {
@@ -2569,7 +2569,9 @@ export async function transformResultContent(
return [
{
type: 'text',
text: `${prefix}${resource.text}`,
text: recursivelySanitizeUnicode(
`${prefix}${resource.text}`,
) as string,
},
]
} else if ('blob' in resource) {