import { homedir } from 'os'; import React from 'react'; import { logEvent } from 'src/services/analytics/index.js'; import { setSessionTrustAccepted } from '../../bootstrap/state.js'; import type { Command } from '../../commands.js'; import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'; import { Box, Link, Text } from '@anthropic/ink'; import { useKeybinding } from '../../keybindings/useKeybinding.js'; import { getMcpConfigsByScope } from '../../services/mcp/config.js'; import { BASH_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/BashTool/toolName.js'; import { checkHasTrustDialogAccepted, saveCurrentProjectConfig } from '../../utils/config.js'; import { getCwd } from '../../utils/cwd.js'; import { getFsImplementation } from '../../utils/fsOperations.js'; import { gracefulShutdownSync } from '../../utils/gracefulShutdown.js'; import { Select } from '../CustomSelect/index.js'; import { PermissionDialog } from '../permissions/PermissionDialog.js'; import { getApiKeyHelperSources, getAwsCommandsSources, getBashPermissionSources, getDangerousEnvVarsSources, getGcpCommandsSources, getHooksSources, getOtelHeadersHelperSources, } from './utils.js'; type Props = { onDone(): void; commands?: Command[]; }; export function TrustDialog({ onDone, commands }: Props): React.ReactNode { const { servers: projectServers } = getMcpConfigsByScope('project'); // In all cases, we generally check only the project-level and // project-local-level settings, which we assume that users do not configure // directly compared to user-level settings. // Check for MCPs const hasMcpServers = Object.keys(projectServers).length > 0; // Check for hooks const hooksSettingSources = getHooksSources(); const hasHooks = hooksSettingSources.length > 0; // Check whether code execution is allowed in permissions and slash commands const bashSettingSources = getBashPermissionSources(); // Check for apiKeyHelper which executes arbitrary commands const apiKeyHelperSources = getApiKeyHelperSources(); const hasApiKeyHelper = apiKeyHelperSources.length > 0; // Check for AWS commands which execute arbitrary commands const awsCommandsSources = getAwsCommandsSources(); const hasAwsCommands = awsCommandsSources.length > 0; // Check for GCP commands which execute arbitrary commands const gcpCommandsSources = getGcpCommandsSources(); const hasGcpCommands = gcpCommandsSources.length > 0; // Check for otelHeadersHelper which executes arbitrary commands const otelHeadersHelperSources = getOtelHeadersHelperSources(); const hasOtelHeadersHelper = otelHeadersHelperSources.length > 0; // Check for dangerous environment variables (not in SAFE_ENV_VARS) const dangerousEnvVarsSources = getDangerousEnvVarsSources(); const hasDangerousEnvVars = dangerousEnvVarsSources.length > 0; const hasSlashCommandBash = commands?.some( command => command.type === 'prompt' && command.loadedFrom === 'commands_DEPRECATED' && (command.source === 'projectSettings' || command.source === 'localSettings') && command.allowedTools?.some((tool: string) => tool === BASH_TOOL_NAME || tool.startsWith(BASH_TOOL_NAME + '(')), ) ?? false; const hasSkillsBash = commands?.some( command => command.type === 'prompt' && (command.loadedFrom === 'skills' || command.loadedFrom === 'plugin') && (command.source === 'projectSettings' || command.source === 'localSettings' || command.source === 'plugin') && command.allowedTools?.some((tool: string) => tool === BASH_TOOL_NAME || tool.startsWith(BASH_TOOL_NAME + '(')), ) ?? false; const hasAnyBashExecution = bashSettingSources.length > 0 || hasSlashCommandBash || hasSkillsBash; const hasTrustDialogAccepted = checkHasTrustDialogAccepted(); React.useEffect(() => { const isHomeDir = homedir() === getCwd(); logEvent('tengu_trust_dialog_shown', { isHomeDir, hasMcpServers, hasHooks, hasBashExecution: hasAnyBashExecution, hasApiKeyHelper, hasAwsCommands, hasGcpCommands, hasOtelHeadersHelper, hasDangerousEnvVars, }); }, [ hasMcpServers, hasHooks, hasAnyBashExecution, hasApiKeyHelper, hasAwsCommands, hasGcpCommands, hasOtelHeadersHelper, hasDangerousEnvVars, ]); function onChange(value: 'enable_all' | 'exit') { if (value === 'exit') { gracefulShutdownSync(1); return; } const isHomeDir = homedir() === getCwd(); logEvent('tengu_trust_dialog_accept', { isHomeDir, hasMcpServers, hasHooks, hasBashExecution: hasAnyBashExecution, hasApiKeyHelper, hasAwsCommands, hasGcpCommands, hasOtelHeadersHelper, hasDangerousEnvVars, }); if (isHomeDir) { // For home directory, store trust in session memory only (not persisted to disk) // This allows hooks and other trust-requiring features to work during this session // while preserving the security intent of not permanently trusting home dir setSessionTrustAccepted(true); } else { saveCurrentProjectConfig(current => ({ ...current, hasTrustDialogAccepted: true, })); } // Do NOT write MCP server settings here. handleMcpjsonServerApprovals in // interactiveHelpers.tsx runs right after this dialog and shows the per-server approval // UI. Writing enabledMcpjsonServers/enableAllProjectMcpServers here would // mark every server 'approved' and silently skip that dialog. See #15558. onDone(); } // Default onExit is useApp().exit() → Ink.unmount(), which tears down the // React tree but never calls onDone(). showSetupScreens() in // interactiveHelpers.tsx awaits a Promise that only resolves via onDone, // so the default would hang the await forever. With keybinding // customization enabled, the chokidar watcher (persistent: true) keeps the // event loop alive and the process freezes. Explicitly exit 1 like "No". const exitState = useExitOnCtrlCDWithKeybindings(() => gracefulShutdownSync(1)); // Use configurable keybinding for ESC to cancel/exit useKeybinding( 'confirm:no', () => { gracefulShutdownSync(0); }, { context: 'Confirmation' }, ); // Automatically resolve the trust dialog if there is nothing to be shown. if (hasTrustDialogAccepted) { setTimeout(onDone); return null; } return ( {getFsImplementation().cwd()} Is this a project you trust? (Your own code, a well-known open source project, or work from your team). Once trusted, Claude Code can read, edit, and run commands in this folder. Security guide