Skip to content

Security: MarlonJD/apple-debug-mcp

Security

docs/SECURITY.md

Security

Apple Debug MCP is a privileged local developer tool. It can start debuggers and, when explicitly enabled, alter an authorized target. The default process is read-only discovery and inspection; every state-changing boundary is opt-in and scoped.

Trust boundaries

  • The MCP client is the local caller and may start the server through stdio or connect to the authenticated loopback daemon endpoint.
  • The server inherits the caller’s macOS identity and developer-tool permissions.
  • Xcode, LLDB, Simulator, CoreDevice, and unified logging are external authority-bearing tools.
  • Checkpoint artifacts are local evidence files; replay relaunches only the previously authorized local macOS launch target and does not restore arbitrary process or kernel state.
  • Kernel-lab sessions are a separate user-authorized two-machine boundary; KDK, KDP, SIP, boot security, and target preparation remain outside MCP automation.
  • A physical iOS device and its development-signed application are separate trust targets.
  • Stock App Store applications are outside the supported target boundary.

Security invariants

Boundary Invariant Enforcer Verification
MCP tools Unknown tools fail closed ToolCatalog.call make check
Toolchain Only fixed executable paths and explicit argument arrays are used ToolchainProbe, adapters CapabilitiesTests and code review
Artifact files Mach-O and crash inputs are bounded; binary diff and symbolication resolve only an explicitly supplied absolute regular file, .app main executable, or direct .dSYM DWARF payload MachOInspector, AppleArtifactLayoutResolver, CrashReportAnalyzer, AppleBinaryDiffService, SymbolicationService MachOIdentityTests, AppleArtifactLayoutTests, CrashSymbolicationIdentityTests, symbolication-crash-smoke
Target launch Requires APPLE_DEBUG_ALLOW_TARGET_LAUNCH=1 and a regular target DebugPolicy.validateLaunchTarget DebugSessionTests, macOS fixture smoke
Target attach Requires APPLE_DEBUG_ALLOW_TARGET_ATTACH=1 and a positive process ID DebugPolicy.validateAttach DebugSessionTests, iOS Simulator smoke
Expression evaluation Requires APPLE_DEBUG_ALLOW_EVALUATE=1 and a 16 KiB expression limit DebugPolicy.validateEvaluate DebugSessionTests and fixture smoke
Memory write Requires APPLE_DEBUG_ALLOW_MEMORY_WRITE=1 and a 4096-byte limit DebugPolicy.validateMemoryWrite DebugSessionTests
Variable write Requires APPLE_DEBUG_ALLOW_VARIABLE_WRITE=1 and bounded name/value fields DebugPolicy.validateVariableWrite DebugSessionTests
Debugger commands MCP exposes named DAP requests, not arbitrary shell execution ToolCatalog, DebugSessionManager Tool schema review and make check
Assembly generation Assembly input is bounded, self-contained, and compiled to a temporary object with fixed clang arguments; generated bytes are inert until a separate memory-write grant is used AppleAssemblerService assembler-smoke and AppleAssemblerTests
Assembly patching Code bytes can be written only through expected-byte transactional patch/rollback and APPLE_DEBUG_ALLOW_MEMORY_WRITE=1 apple_debug_patch_assembly, DebugSessionManager.patchMemory macOS fixture smoke
Runtime diagnostics Heap/leaks/malloc-history/sample commands use fixed allowlisted modes and attach authorization; no arbitrary flags or shell execution are accepted RuntimeDiagnosticsService runtime-diagnostics-smoke
Reverse and kernel boundaries Unsupported reverse/time-travel and kernel memory operations are reported fail-closed; the server does not emulate or bypass Apple security controls ReverseExecutionService, AppleKernelCapabilityService reverse-capability-smoke
Checkpoint replay Checkpoints are bounded, non-overwriting JSON artifacts; replay requires a prior local launch configuration and stops at a recorded source location without claiming exact state restoration CheckpointReplayManager, DebugSessionManager ReplayTests, replay-smoke
Kernel lab Remote KDP uses a fixed target-create/symbols/kdp-remote sequence, an explicit APPLE_DEBUG_ALLOW_KERNEL_LAB=1 grant, validated paths/host, and read-only DAP inspection; memory writes and arbitrary commands are unavailable KernelLabSessionManager, KernelLabService AppleKernelCapabilitiesTests, reverse-capability-smoke, external KDK lab evidence when available
Static binary analysis CFG/shared-cache parsers read bounded regular files and never execute or rewrite them; dyld runtime cross-references are direct-pointer evidence and chained fixups are metadata-only AppleControlFlowService, AppleDyldSharedCacheService control-flow-smoke and dyld unit test
Memory snapshots vmmap snapshots write only to an explicit non-existing JSON path and require target attach authorization AppleMemoryMapService memory-map-smoke
Simulator environment Fixed simctl operations validate enum values, paths, payload sizes, and mutation policy; arbitrary spawn/shell is not exposed AppleSimulatorEnvironmentService simulator-environment-smoke
Repro bundles Evidence capture writes to an explicit new directory with bounded copied trace/crash inputs and no device erase/pairing AppleReproBundleService repro-bundle-smoke
Plugin boundary External JSON manifests are read-only metadata; no arbitrary dylib or executable plugin is loaded by MCP AppleDebugPluginManifestService, AppleDebugPluginRegistry plugin-smoke
Plugin host Candidate XPC service executables must pass codesign audit and optional team-ID matching; the production transport connects to an independently signed App Sandbox XPC service with explicit grant, timeout, and bounded payloads. The profile transport is legacy-only and explicit ApplePluginHostService, AppleDebugPluginXPCProtocol plugin-xpc-smoke and plugin-smoke
Session cleanup Failed launch, explicit close, and server shutdown terminate only owned adapters DebugSessionManager, LLDBDAPSession.stop Session tests and fixture smoke
Menu bar supervisor The menu bar app launches only the bundled apple-debug-mcp --daemon executable, owns its lifecycle, writes output to the user log directory, and surfaces only the authenticated loopback endpoint MCPServerController, AppleDebugDaemonEndpoint, signed app bundle, SMAppService.mainApp make mcp-daemon-smoke, script/build_and_run.sh --verify, package inspection, and menu bar UI smoke
Simulator mutation Known UDID and APPLE_DEBUG_ALLOW_SIMULATOR_MUTATION=1 are required SimulatorService.mutate AppleSimulatorTests, iOS smoke
Arbitrary Simulator UI probe Generated XCUITest project may launch/interact only with the explicitly supplied installed bundle ID and selected Simulator; no target app source or binary is modified SimulatorUIService, fixed generated project, mutation gate ios-arbitrary-ui-smoke
Coordinate UI actions Coordinate taps/presses/swipes are normalized, bounded, mutation-gated XCUITest events; they do not bypass accessibility, app sandbox, or target entitlements SimulatorUIService.validate, generated XCTest runner ios-ui-tree-smoke, ios-arbitrary-ui-smoke
Physical devices Install/launch/process mutation/sysdiagnose require APPLE_DEBUG_ALLOW_DEVICE_MUTATION=1, a known identifier, paired/available development state, a successful CoreDevice tunnel refresh when needed, and an existing signed app AppleDeviceService.mutate, refreshCoreDeviceTunnels AppleDeviceTests, live inventory, and authorized physical smoke
Physical-device LLDB Session creation requires APPLE_DEBUG_ALLOW_DEVICE_DEBUG=1; CoreDevice sessions use UUID selection plus named device process attach --pid commands, optional signed .app symbol loading, and bounded initial-stop synchronization; legacy xcdevice UDIDs additionally require APPLE_DEBUG_ALLOW_DEVICE_MUTATION=1, a signed .app path, and the installed ios-deploy tool. The legacy path owns a generated LLDB-Python bridge and neither path loads arbitrary user scripts DebugSessionManager.create, LegacyDeviceDebugTransport, LLDBDAPSession DebugSessionTests, AppleDeviceTests, LegacyDeviceDebugTransportTests, authorized physical smoke
Xcode builds Require APPLE_DEBUG_ALLOW_XCODE_BUILD=1 and explicit project/scheme/configuration/destination XcodeService.build AppleXcodeTests
Unified logs Duration is bounded, predicates are single-line/limited, and output is capped at 2 MB AppleLogService AppleLogsTests
Network The daemon binds only to 127.0.0.1, publishes a random bearer token in a user-only endpoint file, validates localhost origin/host, and caps HTTP request bodies; it does not expose LAN/TLS/remote access AppleDebugMCPDaemonServer, AppleDebugDaemonEndpoint, NIO adapter make mcp-daemon-smoke and endpoint permission tests
Secrets No credentials or tokens are stored in the repository Repository contract make check and review

Symbolication and artifact identity

Symbolication is fail-closed and non-executing. The caller supplies every artifact path; the resolver requires an absolute path, canonicalizes it, and allows a bundle child or symlink only when its canonical target remains inside the supplied .app or .dSYM root. The resolver inspects the app main executable or a direct regular DWARF entry only. It does not recurse into frameworks, search arbitrary symbol roots, or select a payload from an image name.

Each thin Mach-O slice must contain exactly one non-zero LC_UUID, and the normalized UUID is paired with the exact CPU subtype architecture. Crash Binary Images and .ips image records must provide the same identity. An executable/app image and a dSYM are complementary providers; when no executable/app is supplied, one exact dSYM may serve as the identity and debug provider. Multiple distinct providers in one role are ambiguous. Wrong UUID, wrong architecture, missing debug data, ambiguous providers, malformed layouts, and changed file identity remain typed non-success results. The payload path, device/inode when available, size, and modification time are snapshotted and revalidated before atos.

Crash locations are either absolute or image-relative. .ips image-relative offsets use the explicitly indexed image and checked base + offset; text reports map an absolute frame to exactly one Binary Images range. Preferred __TEXT VM address, runtime base, slide, and atos input remain separate values. Overflow, underflow, missing load data, and out-of-range addresses are rejected before tool invocation. atos is fixed to /usr/bin/xcrun with an argument array, a 4 KiB batch diagnostic cap, 5 seconds per batch, 30 seconds cumulative, at most 32 batches, and no invocation for known pre-resolution identity/address failures.

Adaptive verification boundary

Post-fix verification is casebook orchestration, not a generic MCP execution surface. scripts/adaptive_verification.py defaults to quick (one baseline and one candidate, 5 seconds per attempt, 15 seconds cumulative); standard is at most two plus two at 15/60 seconds and strict is at most three plus three at 30/180 seconds. A monotonic deadline is passed into each callback, including fresh-state and cleanup operations. Automatic escalation is limited to typed timing, concurrency, state-leakage, inconsistent-observation, or known- flakiness evidence; explicit levels are recorded as policy choices.

The runner never treats an arbitrary replay manifest as identity. Reuse requires a computed executable SHA-256, Mach-O UUID/architecture, build/source, environment, target, and scenario/oracle match. A decisive candidate or guardrail regression is failed even if cleanup fails afterward. Missing, stale, contaminated, conflicting, exhausted, or non-fresh evidence without a decisive regression is inconclusive; verified requires reliable baseline, candidate thresholds, guardrails, fresh/restored/notRequired state, and cleanup.

Physical-device boundary

The device adapter reports CoreDevice pairing/tunnel state and legacy xcdevice availability before any mutation. A paired CoreDevice may require the read-only lock-state probe to activate its tunnel; a successful probe, not a device name or bundle identifier, is the authorization signal. CoreDevice launch uses --terminate-existing, returns a PID only from the bounded process inventory, and CoreDevice process actions accept only positive PIDs and an allowlisted signal set. CoreDevice/legacy session cleanup owns only the adapter and explicitly selected fixture process. Legacy lifecycle and LLDB-DAP mutation require ios-deploy, an existing signed .app, the explicit device-debug grant, and the separate device-mutation grant. Neither transport loads arbitrary user scripts.

Abuse and reporting

Do not use the server to access software or devices without authorization. Security findings should be recorded in the active ExecPlan and fixed with a reproducing test or a documented blocker. Release, signing, notarization, and external issue reporting require explicit authorization.

There aren't any published security advisories