Проблема в самом разрешении
Ужесточение Apple политики macOS Full Disk Access (FDA) — разумный ответ на реальный сценарий отказа: фреймворк агента, обёртка терминала или фоновый помощник запрашивает максимально широкие права чтения, потому что узкий запрос сложнее сформулировать. Результат — один булев флаг на каждый файл, который может прочитать пользователь. Агент, которому нужен один каталог проекта, получает те же права, что и агент, которому нужен один файл конфигурации. Разрешение дёшево выдать и дорого осмыслить.
В этой асимметрии — вся суть. Выдача разрешения стоит одного диалога. Осмысление стоит аудита каждого рабочего процесса, который от него зависит, потому что отзыв не ограничен областью: отзыв FDA делает недействительными несвязанные процессы, которые случайно делили тот же булев флаг. Операторы реагируют тем, что оставляют разрешение на месте и относятся к нему как к фоновой инфраструктуре — а это ровно то состояние, которое ужесточение призвано предотвратить.
Более глубокая проблема в том, что модель разрешений появилась раньше агентов, которые действуют без человека в цикле на каждом чтении. Когда человек открывает файл, провенанс — это человек. Когда агент читает файл от имени задачи, провенанс — это задача, и платформе негде его записать. FDA может сказать, что что-то прочитало вашу связку ключей. Оно не может сказать, какая задача, какой агент и было ли это чтение законным.
Классы данных как примитив ограничения области
Устойчивое решение — перестать считать общесистемное разрешение на файловую систему единицей авторизации агента. Вместо этого ограничивайте права чтения по классам данных. Класс данных — это набор путей, разделяющих политику чтения, например:
- учётные данные: связки ключей, файлы
.env, материалы SSH, конфигурация облачных провайдеров, хранилища учётных данных браузеров - деревья исходников: рабочие копии под контролем версий, чекауты монорепозиториев, вендорные зависимости
- личные коммуникации: почтовые хранилища, базы сообщений, хранилища календарей и контактов
- артефакты сборки: скомпилированный вывод, кэши, слои контейнеров, хранилища менеджеров пакетов
- системная конфигурация:
/etc, launch-агенты, сетевые профили
Агенты запрашивают класс, а не путь. Платформа сопоставляет пути классам в момент выдачи разрешения и повторно сопоставляет в момент чтения. Агент сборки запрашивает source-trees и build-artefacts; агент развёртывания запрашивает credentials в области одного провайдера и build-artefacts; агент суммаризации запрашивает personal-communications для одной учётной записи. Запрос понятен человеку-ревьюеру так, как ~/Projects — нет, потому что он выражает намерение, а не местоположение.

Критическое свойство в том, что принадлежность к классу вычисляет платформа, а не декларирует агент. Если бы агент мог заявить «этот путь — артефакт сборки», переименование секрета в dist/ обошло бы таксономию. Сопоставление должно сочетать шаблоны путей, метаданные файлов, сигналы содержимого и происхождение: файл, записанный процессом, владеющим учётными данными, должен наследовать классификацию, смежную с учётными данными, даже после копирования. Переименованный секрет остаётся защищённым, потому что его класс выводится из того, как он возник, а не из того, где он сейчас лежит.
Провенанс при каждом чтении
Ограничения области недостаточно без записи о том, что произошло. Каждое событие чтения должно нести как минимум три поля:
{
"agent_identity": "build-runner@ci-7",
"task_id": "task-4f9c2a1e",
"resolved_class": "source-trees",
"path": "/Users/dev/work/api/src/main.rs",
"decision": "allow",
"policy_version": "2024-11-03"
}
Идентификатор задачи — то поле, которое большинство систем опускает, и именно оно делает записи аудита интерпретируемыми. Без него строка лога с чтением ~/.aws/credentials неотличима от законного шага развёртывания и от пути эксфильтрации. С ним оператор может задать вопрос, который имеет значение: какие задачи читали учётные данные и читала ли какая-нибудь из них также личные коммуникации в том же окне? На этот вопрос можно ответить, только если идентификатор задачи присутствует при каждом чтении, распространяясь от точки входа агента через каждый вызов инструмента и подпроцесс.
Логирование решения о сопоставлении важно не меньше логирования пути. Запись, где для пути указано resolved_class: source-trees, а политика теперь классифицировала бы его как credentials, — это сигнал дрейфа. Без решения изменения политики невидимы, пока что-нибудь не сломается. С ним дрейф обнаруживается как запрос: найди чтения, где сопоставленный класс расходится с текущим классификатором.
Конкретный механизм сопоставления
Работоспособная реализация состоит из трёх этапов.
Классификация. Фоновый классификатор поддерживает отображение шаблонов путей и записей происхождения в классы. Он запускается при создании файла, переименовании и изменении содержимо
ую версию политики.

Сопоставление. При каждом чтении среда выполнения агента сопоставляет запрошенный класс с текущим представлением классификатора. Если вычисленный класс пути не входит в выданный набор агента, чтение отклоняется со структурированной причиной: вычисленный класс, версия политики и разрешение, которое потребовалось бы.
Эмиссия провенанса. Среда выполнения отправляет событие чтения с полями выше в приёмник, доступный только для добавления. Приёмник доступен для запросов по задаче, агенту и классу и служит основой для обнаружения дрейфа.
Две детали определяют, работает ли это на практике. Во-первых, сопоставление должно быть достаточно быстрым, чтобы находиться на пути чтения; кэша, ключ которого — inode и версия политики, обычно достаточно, с инвалидацией при изменении классификации. Во-вторых, отказ должен быть действенным. Отказ вида computed_class: credentials, required_grant: credentials:aws-prod точно говорит оператору, что менять. Отказ вида permission denied учит его выдать FDA.
Чего это стоит
Честных компромиссов три.
Задержка и поддержка. Сопоставление классов добавляет поиск на пути чтения, и таксономию нужно поддерживать. Классы, специфичные для предметной области, дрейфуют по мере реорганизации репозиториев командами. Таксономия, которую не поддерживают, становится источником ложных отказов, а ложные отказы подрывают доверие быстрее, чем широкие разрешения.
Законные отказы. Агентам иногда будут отказывать в чтениях, которые им следовало разрешить. Шаг сборки, читающий сгенерированный файл, который классификатор пометил как смежный с учётными данными, завершится ошибкой. Дизайн должен делать такой отказ действенным, а не молчаливым: показывать вычисленный класс, версию политики и самое узкое разрешение, которое разблокировало бы задачу. Молчаливый отказ порождает то же обходное поведение, что и грубые разрешения.
Сопротивление вендоров. Платформенные вендоры будут сопротивляться поставке таксономий классов, потому что они специфичны для предметной области и плохо задаются по умолчанию. Операционная система общего назначения не может знать, что каталог fixtures/ одной команды содержит синтетические данные, а другой — продакшн-снимки. Реалистичный путь — небольшой набор базовых классов, определённых платформой, плюс механизм расширения, с пониманием, что расширение — это место, где живёт большая часть ценности и большая часть бремени поддержки.
Ничто из этого не бесплатно, и ничто из этого не является полным ответом. Однако это смена единицы авторизации. FDA спрашивает, может ли агент читать всё. Модель с ограничением по классам спрашивает, какие виды данных этот агент в этой задаче может читать, и записывает ответ. Второй вопрос — тот, на который операторам действительно нужно отвечать, и тот, который текущая модель выразить не может.
