Выбор между MCP stdio и Unix-сокетом зависит от границы доверия
Сравнение MCP stdio и Unix-сокетов для настольного брокера секретов: срок жизни, права, атрибуция, очистка и развертывание.

Настольному брокеру секретов нужен stdio, когда полномочия принадлежат одному процессу агента, и Unix domain socket, когда ими управляет общая локальная служба. Это выглядит как выбор транспорта, но передача байтов проще всего. Сложнее решить, кто может вызвать действие, сколько живет разрешение и какие доказательства останутся после сбоя любой из сторон.
Я видел, как команды выбирали сокет, потому что он больше похож на инфраструктуру, а потом неделями заново строили атрибуцию вызывающего процесса и контроль жизненного цикла, уже заложенные в дереве процессов. Видел и обратное: stdio растягивали до постоянной службы слоями запускателей, мультиплексоров и скрытого состояния. Оба транспорта могут быть безопасными. Любой из них незаметно ломает модель угроз, если предположения о сроке жизни и идентичности не совпадают с выданными полномочиями.
В этом сравнении настольный брокер macOS хранит учетные данные API или SSH и выполняет действия для локальных ИИ-агентов. Агент никогда не получает секрет. Поэтому брокер защищает не локальный кэш, а более серьезную границу: он решает, какой процесс может превратить сохраненные полномочия во внешнее действие.
Сначала выберите границу доверия
Транспорт следует из единицы авторизации. Если пользователь одобряет один запуск агента, дочерний stdio-сервер дает разрешению естественную границу: канал существует в рамках связи двух процессов и закрывается при завершении одной стороны. Если несколько инструментов используют один разблокированный брокер, Unix-сокет дает стабильную точку встречи, но брокеру придется самому создать границу сеанса поверх сокета.
Опишите модель угроз через участников и действия, а не общим пожеланием «защитить IPC». Перечислите локальные процессы, которым разрешено подключение, хранимые учетные данные, допустимые с ними операции и события, прекращающие доступ. Учтите вредоносную программу от имени того же пользователя. Права на файл часто останавливают других пользователей, но почти не мешают второму процессу в той же учетной записи. Учтите копию бинарного файла агента, измененный плагин, запущенную агентом оболочку и старый процесс, который пережил видимую задачу.
Затем определите смысл одобрения. Оно может разрешать работу подписанного исполняемого файла, одного системного процесса, дерева процессов, задания терминала или всех клиентов вошедшего пользователя. Это разные обещания. Транспорт не выберет нужное за вас. Он лишь упрощает выполнение некоторых обещаний.
Отзыв разрешения хорошо проверяет проект. Спросите, что брокер может отозвать сразу, не блокируя все хранилище. В stdio закрытие канала или завершение дочернего процесса прекращает этот сеанс, хотя потомки могли унаследовать дескрипторы при ошибке запускателя. В сокете закрытие принятого соединения отключает одного клиента, а слушающий сокет остается. Если запись авторизации живет дольше соединения, одного закрытия в обоих случаях мало.
Не ставьте сетевого атакующего в центр локального решения, если брокер не открывает сетевой listener. Здесь опаснее путаница с идентичностью, фоновые полномочия пользовательского сеанса, наследование дескрипторов, подмена сокета в файловой системе и разрешение, которое действует дольше одобренной работы.
Stdio связывает канал со сроком жизни процесса
MCP через stdio лучше всего подходит для shim-процесса, который должен запускаться и завершаться вместе с агентом. Клиент запускает серверную команду, пишет сообщения JSON-RPC в стандартный ввод и читает ответы из стандартного вывода. EOF служит системным сигналом жизненного цикла, а не условностью внутри heartbeat приложения.
Спецификация транспорта Model Context Protocol задает еще одно полезное требование: stdio-сервер не пишет посторонние данные в stdout. Журналы идут в stderr. Это не просто аккуратный framing. Правило не дает диагностической строке превратиться в недопустимое сообщение внутри границы безопасности. Относитесь к stdout как к памяти протокола. Настройте библиотеки и отчеты о сбоях до первого ответа, чтобы они не загрязнили поток.
Stdio не требует имени в файловой системе, каталога сокетов, режима прав, файла обнаружения или постоянного listener. В конфигурации агента указан исполняемый файл и аргументы. Для настольного продукта это заметно упрощает установку: endpoint не нужно искать, а устаревший путь не нужно чинить. У обновления есть ясный момент активации: следующий shim запускает новый файл. Активный сеанс может остаться на старой версии, поэтому сохраняйте идентичность и версию исполняемого файла в записи сеанса, а не считайте всех клиентов обновленными сразу после установки.
Связь процессов дает полезное доказательство, но не полную идентичность. Брокер может проверить shim, который подключился к его закрытому backend, а shim может проверить родительский процесс. После завершения PID может достаться другому процессу, а между видимым агентом и shim может стоять запускатель. Получайте audit token или сведения о подписи, пока процесс жив. Не сохраняйте один PID для последующей проверки.
У каналов есть ловушки наследования. Если запускатель оставил дескриптор наследуемым, процесс-внук может держать конец записи открытым после завершения агента. Тогда брокер ждет EOF, который не придет. Устанавливайте close on exec там, где платформа не делает этого сама, сразу закрывайте ненужные концы после запуска и следите одновременно за каналом и ожидаемым клиентским процессом. EOF должен отзывать доступ, но завершение процесса тоже должно отзывать его независимо.
Параллелизм здесь намеренно ограничен. Один экземпляр stdio-сервера обычно обслуживает один клиентский процесс. Изоляция понятна, а цена равна одному дополнительному процессу на агента. Если настоящее хранилище находится в настольном приложении, исполняемый файл stdio обычно работает как небольшой shim и пересылает типизированные запросы приложению. Этот внутренний переход тоже требует аутентификации и привязки сеанса. Stdio на границе MCP не защищает общий backend без аутентификации.
Unix-сокет задает срок жизни службы
Unix domain socket подходит брокеру, который уже работает как постоянная служба и принимает независимых клиентов. Слушающий endpoint переживает завершение клиента, поэтому приложение в строке меню принимает вызовы, не передавая каждому агенту владение процессом брокера. Несколько клиентов, обратное давление и централизованные обновления реализуются прямо. Но службе придется определить каждую границу, которую один клиентский процесс раньше задавал неявно.
Путь сокета помогает его найти, но ничего не аутентифицирует. Клиенту, который нашел путь, еще нужно право на подключение, а подключенному клиенту еще нужна авторизация. Размещайте сокет в каталоге под контролем пользователя или приложения, создавайте каталог с режимом 0700, а сокет с режимом 0600. Не используйте предсказуемый путь в общем каталоге с правом записи. Sticky bit временного каталога мешает некоторым атакам с удалением, но не превращает каталог в доверенное пространство имен.
Права зависят не только от итогового режима. Umask процесса влияет на создание. Родительские каталоги определяют, сможет ли другая учетная запись пройти по пути или заменить имя. Выбранное имя уже может занимать символическая ссылка или старый узел. Служба должна открыть доверенный родительский каталог, проверить запись без перехода по ссылкам и удалить ее только после доказательства, что это сокет завершившегося экземпляра или путь текущей установки. Слепой unlink известного имени создает гонку с подменой.
В macOS у принятого локального сокета можно получить данные peer через системные вызовы наподобие getpeereid, а низкоуровневые API дают audit token. Идентификаторы пользователя и группы говорят, какой учетной записи принадлежит процесс. Они не говорят, какое приложение пользователь хотел разрешить. Для этого свяжите audit token с живым процессом и проверьте идентичность подписи, путь исполняемого файла и контекст запуска по заявленной политике продукта.
Сокет упрощает мультиплексирование, но оно размывает границы сеансов. Успешное соединение не означает, что разрешен любой запрос с произвольным идентификатором сеанса. Сервер сам назначает идентичность соединению, привязывает к ней авторизацию и отвергает попытку клиента переключить идентичность. Если helper подключается заново после перезапуска службы, требуется новое решение, если модель угроз явно не разрешает сохранять одобрение.
Производительность редко определяет выбор. Локальные каналы и Unix-сокеты передают небольшие запросы JSON-RPC намного быстрее, чем завершается операция HTTP API или SSH. Измеряйте скорость при больших ответах, но не меняйте понятную модель полномочий на предполагаемую экономию в локальном IPC.
Права на сокет не доказывают намерение пользователя
Режим 0600 запрещает процессам с другими пользовательскими идентификаторами открыть сокет через обычную дискреционную проверку. Он не означает, что каждый процесс того же пользователя заслуживает сохраненные учетные данные. Настольная вредоносная программа, сомнительный скрипт пакета, расширение редактора и одобренный агент часто работают под одним ID.
Эту разницу упускают, потому что права Unix конкретны и легко проверяются. Команда видит закрытый сокет и называет endpoint аутентифицированным. Аутентифицирована только учетная запись. Если модель защищается лишь от других вошедших пользователей, этого может хватить. Брокер для автономных инструментов обычно обещает контроль внутри одного пользовательского сеанса, поэтому ему нужна более узкая идентичность.
В macOS сведения о подписи кода сужают идентичность. Проверяйте живой peer, а не строку пути из запроса. Путь может вести к подмененному файлу, а процесс после обновления может продолжать выполнять старый загруженный образ. Записывайте центр подписи и designated requirement, которые сообщает система. Решите, как работают сборки для разработки с ad hoc подписью. Если молча приравнять их к рабочему приложению, удобство разработчика превратится в обход проверки.
Идентичность вызывающего процесса и его полномочия тоже различаются. Идентичность отвечает, какой процесс открыл соединение. Полномочия отвечают, может ли он прямо сейчас применить конкретные учетные данные для конкретного действия. Правильно подписанный агент может работать в непроверенном репозитории или получить в prompt враждебное содержимое. Разрешать все действия только из-за знакомого исполняемого файла означает принять происхождение за согласие.
Для stdio запускатель может передать идентичность непосредственного родителя при старте shim, а брокер привяжет доказательство к новому nonce канала. Для сокета брокер определит peer при accept и привяжет его к принятому дескриптору. В обоих случаях источником идентичности выступает брокер. Поле вроде client_name годится для интерфейса, но не для доказательства.
Честная карточка одобрения показывает факты, которые установила система, и предмет одобрения. Если wrapper-скрипт мешает надежно определить верхний агент, сообщите об этом или отклоните вызов. Поиск вверх по дереву до первого знакомого имени создает управляемый атакующим путь.
Очистка входит в модель безопасности
Очистка stdio касается дескрипторов и дочерних процессов. Штатный случай прост: клиент закрывает stdin, сервер видит EOF, завершает или отменяет работу и выходит. Нештатные случаи важнее. Клиент может упасть, пока потомок держит канал. Сервер может зависнуть, а клиент сочтет его завершенным. Привилегированное действие может закончиться после отзыва, если отмена не дошла до выполняющего его worker.
Назначайте каждому запросу созданный брокером ID операции и состояние queued, executing, completed, denied или indeterminate. При закрытии канала сразу отзывайте будущие запросы. Если действие уже ушло в удаленный API, не утверждайте, что оно отменено, если удаленная сторона не поддерживает отмену. Запишите indeterminate, если локальный процесс завершился до получения результата. Такая запись не даст циклу повторов незаметно выполнить действие дважды.
При очистке сокета есть два объекта: принятые соединения и путь слушающего сокета. Закрывайте соединение при ошибке протокола, отказе авторизации, истечении простоя или остановке службы. Удаляйте путь, только если службе по-прежнему принадлежит тот же объект файловой системы. Ошибка EADDRINUSE после перезапуска требует расследования, а не разрешает сделать unlink всему, что найдено.
Эта shell-проверка полезна при разработке: она показывает тип пути, владельца, режим и слушающий процесс, ничего не меняя:
sock="$TMPDIR/com.example.broker.sock"
stat -f 'type=%HT owner=%Su mode=%Sp inode=%i' "$sock"
lsof -n -U "$sock"
Обычный результат macOS имеет такой вид:
type=Socket owner=alice mode=srw------- inode=123456
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Broker 48102 alice 9u unix 0x0123456789abcdef 0t0 /.../com.example.broker.sock
Не разбирайте значения из примера. Проверяйте, что тип равен Socket, владелец совпадает с ожидаемым пользователем, режим запрещает доступ группе и остальным, а процесс относится к установленному брокеру. Повторите проверку после принудительного сбоя, обновления, выхода пользователя и второго запуска. Если очистка работает только при штатном завершении, она не проверена.
Если службу запускает launchd, последовательно оставьте управление ему. Сочетание запуска самим приложением и launch agent может дать два экземпляра, спорящих за один путь. Созданием, готовностью и удалением управляет один компонент, а клиентам нужно явное состояние unavailable вместо многократного запуска новых брокеров.
Работа по развертыванию переходит в другое место
Stdio проще при установке и сложнее, когда много клиентов используют общее постоянное состояние. Каждому клиенту MCP нужна команда. Shim должен найти подписанное приложение или его закрытый endpoint, согласовать версию протокола и ясно сообщить, что приложение отсутствует или заблокировано. Упаковка должна сохранить права на выполнение и подписи кода. Не полагайтесь на стартовые файлы shell: приложения с интерфейсом часто запускаются без окружения терминала.
Unix-сокету нужны установка службы, единый владелец запуска, обнаружение endpoint, права каталога, восстановление после сбоя и совместимость при независимых обновлениях клиента и сервера. Взамен все клиенты используют стабильный endpoint, а служба держит состояние хранилища и порядок аудита в одном процессе. Это удобно, если настольное приложение уже работает постоянно.
Для обнаружения нужен контракт. В macOS $TMPDIR выделен на пользователя, но наследование окружения различается у терминала, редактора и GUI-запускателя. Фиксированный путь в защищенном каталоге поддержки приложения проще анализировать, хотя sandbox и схема установки могут его ограничить. Если клиенты получают путь из bootstrap-команды, аутентифицируйте результат и не принимайте произвольный путь из конфигурации проекта.
Расхождение версий бывает в обеих моделях. В stdio клиент выбирает запускаемый shim. С постоянным сокетом старый клиент может попасть на новую службу или наоборот во время поэтапного обновления. В первом обмене согласуйте небольшую версию протокола. Отбрасывайте несовместимые варианты до запроса одобрения, потому что пользователь не может осмысленно разрешить запрос, который клиент и брокер понимают по-разному.
Специалисты эксплуатации иногда выбирают сокеты, потому что знакомые инструменты умеют их перечислять. Разработчики иногда выбирают stdio, потому что команду можно запустить в терминале. Удобство не должно стать отладочным входом. Диагностический клиент с привилегированными запросами проходит ту же атрибуцию и одобрение. Флаг отключения авторизации однажды покинет машину разработчика.
Сравнение меняется, если брокер не работает постоянно. Запуск полного приложения с интерфейсом для каждого stdio-соединения дает задержки и неуместные запросы. Постоянная служба сокета только ради отказа от маленького shim добавляет обновления и очистку. Сначала определите срок жизни приложения, затем подберите внешний транспорт.
Свяжите проект с явными угрозами
Платформенная команда должна записать, какое свойство отвечает на каждую угрозу. Таблица ниже фиксирует решение, а не выставляет баллы. Транспорт выигрывает строку только тогда, когда окружающая реализация дает указанный контроль.
| Угроза или требование | Проект stdio | Проект с Unix-сокетом |
|---|---|---|
| Другой вошедший пользователь пытается получить доступ | Закрытые дескрипторы и правильное наследование | Защищенный родительский каталог и режим 0600 |
| Другой процесс того же пользователя подключается | Сложнее, если канал принадлежит запускателю, но наследование остается риском | Ожидается по умолчанию, поэтому нужны данные peer и идентичность приложения |
| Одобрение заканчивается вместе с запуском агента | EOF и завершение отслеживаемого процесса дают естественные сигналы отзыва | Сервер создает сеанс и привязывает его к соединению и процессу |
| Несколько агентов используют одно хранилище | Каждому shim нужен закрытый аутентифицированный переход к процессу хранилища | Постоянная служба принимает отдельные аутентифицированные соединения |
| Брокер упал и перезапустился | Клиент видит EOF и запускает новый экземпляр либо сообщает ошибку | Служба обрабатывает старый путь и требует подключения и авторизации заново |
| Исполняемый файл изменился во время работы | Зафиксированная идентичность остается с сеансом | Идентичность остается с соединением; новое соединение требует новой оценки |
| Простая первая установка | Команда и подписанный файл, без настройки endpoint | Регистрация запуска, защищенный путь, обнаружение и очистка |
| Единый порядок аудита | Общий центр хранилища упорядочивает события shim | Естественен в одной службе, хотя долговечный журнал еще нужно спроектировать |
Обычно остаются два вывода. Атакующий от имени того же пользователя лишает биты режима почти всей убедительности. Срок одобрения не менее важен, чем доступ к endpoint. Полностью закрытый сокет с одобрением на весь день может дать больше полномочий, чем аккуратно ограниченный stdio-канал.
Не вводите числовые веса, если команда не умеет их обосновать. Для опасных учетных данных одна пропущенная проверка идентичности перевешивает все удобство развертывания. Напишите приемочный тест для каждой строки. Например, запустите неодобренный процесс от того же пользователя и докажите отказ; одобрите агент, завершите его, оставьте потомка и докажите, что старое разрешение не применяется.
Транспорты можно совместить слоями. MCP использует stdio между агентом и небольшим shim, а shim связывается с постоянным брокером по закрытому Unix-сокету или другому системному IPC. Для настольного приложения это часто верная форма, но она создает две границы. Аутентифицируйте обе. Shim доказывает, какой запуск агента он представляет, а приложение доказывает, что shim попал к нужному брокеру, а не к endpoint, подмененному файлами проекта.
Поместите идентичность в проверенный конверт сеанса
Небольшой конверт протокола позволяет проверить решение по безопасности. Он не заменяет системную идентичность. Он связывает уже проверенные брокером факты с запросом, который брокер собирается выполнить.
После проверки живого вызывающего процесса брокер сам создает идентификатор сеанса и nonce и возвращает их по аутентифицированному каналу. Каждый следующий запрос несет этот идентификатор, возрастающий номер, запрошенную capability и параметры действия. Брокер отклоняет неизвестный сеанс, повтор или пропуск номера при строгом порядке, capability вне одобренного набора и запрос с другого канала.
{"session_id":"s_7M4K","sequence":12,"capability":"http:billing.read","action":{"method":"GET","path":"/v1/invoices"}}
Ответ повторяет идентичность операции и состояние, но не возвращает секреты или добавленные заголовки авторизации:
{"operation_id":"op_01J8","sequence":12,"state":"completed","result":{"status":200,"body_ref":"activity:8841"}}
Это форма протокола, а не универсальные названия полей. Важно, что сервер назначает идентичность, контролирует порядок и возвращает долговечную ссылку на записанное действие. Не подписывайте конверт ключом, переданным агенту: тогда агент станет владельцем учетных данных. Привяжите конверт к аутентифицированному локальному каналу, а секрет оставьте в брокере.
Для stdio привязка может включать случайное значение, переданное только по новому каналу, и идентичность запускателя. Для Unix-сокета она может включать принятое соединение и audit token peer. Если запросы перемещаются между соединениями, задайте отдельный протокол возобновления с коротким сроком и одноразовыми токенами. Идентификатор, скопированный из журнала, не должен восстанавливать полномочия.
Отделите данные одобрения от данных интерфейса. Путь репозитория, заданная агентом метка, имя инструмента и причина запроса помогают человеку решить, но атакующий может выбрать их сам. В записи брокера должны различаться проверенные факты процесса, утверждения пользователя, одобренные capability и содержимое запроса. При разборе инцидента аккуратная метка тогда не будет похожа на доказательство.
Настольный брокер может сочетать оба транспорта
Для подписанного и постоянно работающего приложения macOS самым ясным проектом часто оказывается stdio на границе MCP и отдельно аутентифицированный локальный переход в приложение. Агент получает ожидаемую модель запуска и срок жизни в пределах процесса. Приложение сохраняет одно хранилище, один интерфейс одобрения и упорядоченную историю аудита. Проект работает, только если внутренний переход сохраняет идентичность внешнего вызывающего процесса, а не сводит все shim к одному доверенному клиенту.
Sallyport использует эту форму: агенты с поддержкой MCP запускают встроенный stdio-shim sp mcp, а приложение выполняет действия HTTP и SSH, поэтому секреты не попадают агенту. Одобрение сеанса сначала показывает центр подписи процесса, закрывая слабое место, на которое не отвечают права сокета.
Этот выбор не делает Unix-сокеты всегда неверными, а stdio достаточным сам по себе. Платформенная команда с несколькими нативными клиентами может открыть защищенный сокет как основной API. Тогда она отвечает за проверку peer, создание сеанса, восстановление старого endpoint и совместимость обновлений. Если команда не может объяснить каждый пункт, сокет еще не готов переносить учетные данные.
Выбирайте проект, в котором при сбое проще всего отказать. Не удалось определить вызывающий процесс, откажите. Брокер не доказал, что endpoint принадлежит текущему экземпляру, не подключайтесь и не делайте unlink. Клиент подключился после перезапуска любого процесса, создайте новый сеанс. Эти правила немного снижают удобство и убирают незаметную непрерывность, из-за которой локальные брокеры опасны.
Итоговая проверка архитектуры должна уместиться на странице: единица авторизации, проверенное доказательство вызывающего процесса, начало и конец сеанса, владелец endpoint, поведение при сбое, обновление и запись аудита. Если ответ опирается на слова «тот же пользователь», укажите, входит ли вредоносная программа от этого пользователя в модель. Одна фраза покажет, сравниваете ли вы stdio и Unix-сокет как транспорты или подменяете ими решение по безопасности.
Вопросы и ответы
MCP stdio безопаснее Unix domain socket?
Сам по себе нет. Stdio естественно ограничивает канал запущенным процессом, а Unix-сокет лучше поддерживает постоянную службу. Безопасность зависит от совпадения этого срока с единицей авторизации.
Режим 0600 аутентифицирует вызывающее приложение?
Нет. Обычно он ограничивает доступ владельцем, но любой процесс этого пользователя может попытаться подключиться. Проверяйте живой peer и отдельно принимайте решение об авторизации.
Может ли настольный брокер работать с несколькими агентами через stdio?
Да. Запускайте отдельный shim для каждого агента и давайте ему закрытый аутентифицированный переход к общему центру хранилища. Разделяйте идентичности, чтобы один shim не использовал чужое одобрение.
Что делать после сбоя клиента stdio?
Брокер должен отозвать будущие вызовы при EOF или завершении процесса и записать итог уже начатой работы. Если удаленное действие могло завершиться, отмечайте indeterminate, а не повторяйте его вслепую.
Как удалить устаревший Unix-сокет?
Проверьте объект без перехода по ссылкам, его тип, владельца и связь с завершившимся экземпляром. Не делайте unlink предсказуемого пути только потому, что bind вернул EADDRINUSE.
Unix-сокеты достаточно быстры для MCP?
Да, для обычных запросов. Локальная передача обычно занимает мало времени рядом с HTTP или SSH, поэтому сначала выбирайте архитектуру по идентичности и сроку жизни.
Должна ли авторизация переживать перезапуск брокера?
Обычно нет. Перезапуск разрывает проверенный канал и доказательства процесса, поэтому требуйте нового подключения и сеанса, если продукт явно не поддерживает защищенную долговечную авторизацию.
Может ли клиент прислать имя своего процесса?
Он может прислать метку для интерфейса, но доверять ей нельзя. Определяйте идентичность по живому процессу, audit token, подписи или системной связи запуска.
Где приложению macOS разместить Unix-сокет?
Используйте родительский каталог под контролем пользователя или приложения и запретите доступ группе и остальным. Задайте способ обнаружения пути без доверия к конфигурации проекта.
Когда стоит сочетать stdio и сокет?
Такой проект подходит постоянному настольному хранилищу для отдельно запущенных MCP-агентов. Stdio дает сеансы на срок процесса, а внутреннему сокету все равно нужны аутентификация и проверенный контекст.