Перейти к основному содержимому

Полный доступ к диску — это грубое разрешение: ограничение прав чтения агента по классам данных

Полный доступ к диску в macOS — это один булев флаг на все пользовательские файлы. Устойчивое решение — права чтения по классам данных с провенансом при каждом чтении.

Крупный план серверной стойки с сетевыми кабелями и индикаторами, символизирующий инфраструктуру контроля доступа к файловой системе.

Проблема в самом разрешении

Ужесточение Apple политики macOS Full Disk Access (FDA) — разумный ответ на реальный сценарий отказа: фреймворк агента, обёртка терминала или фоновый помощник запрашивает максимально широкие права чтения, потому что узкий запрос сложнее сформулировать. Результат — один булев флаг на каждый файл, который может прочитать пользователь. Агент, которому нужен один каталог проекта, получает те же права, что и агент, которому нужен один файл конфигурации. Разрешение дёшево выдать и дорого осмыслить.

В этой асимметрии — вся суть. Выдача разрешения стоит одного диалога. Осмысление стоит аудита каждого рабочего процесса, который от него зависит, потому что отзыв не ограничен областью: отзыв FDA делает недействительными несвязанные процессы, которые случайно делили тот же булев флаг. Операторы реагируют тем, что оставляют разрешение на месте и относятся к нему как к фоновой инфраструктуре — а это ровно то состояние, которое ужесточение призвано предотвратить.

Более глубокая проблема в том, что модель разрешений появилась раньше агентов, которые действуют без человека в цикле на каждом чтении. Когда человек открывает файл, провенанс — это человек. Когда агент читает файл от имени задачи, провенанс — это задача, и платформе негде его записать. FDA может сказать, что что-то прочитало вашу связку ключей. Оно не может сказать, какая задача, какой агент и было ли это чтение законным.

Классы данных как примитив ограничения области

Устойчивое решение — перестать считать общесистемное разрешение на файловую систему единицей авторизации агента. Вместо этого ограничивайте права чтения по классам данных. Класс данных — это набор путей, разделяющих политику чтения, например:

  • учётные данные: связки ключей, файлы .env, материалы SSH, конфигурация облачных провайдеров, хранилища учётных данных браузеров
  • деревья исходников: рабочие копии под контролем версий, чекауты монорепозиториев, вендорные зависимости
  • личные коммуникации: почтовые хранилища, базы сообщений, хранилища календарей и контактов
  • артефакты сборки: скомпилированный вывод, кэши, слои контейнеров, хранилища менеджеров пакетов
  • системная конфигурация: /etc, launch-агенты, сетевые профили

Агенты запрашивают класс, а не путь. Платформа сопоставляет пути классам в момент выдачи разрешения и повторно сопоставляет в момент чтения. Агент сборки запрашивает source-trees и build-artefacts; агент развёртывания запрашивает credentials в области одного провайдера и build-artefacts; агент суммаризации запрашивает personal-communications для одной учётной записи. Запрос понятен человеку-ревьюеру так, как ~/Projects — нет, потому что он выражает намерение, а не местоположение.

Two lab workers in protective gear carry sample trays through a sterile white laboratory hallway

Критическое свойство в том, что принадлежность к классу вычисляет платформа, а не декларирует агент. Если бы агент мог заявить «этот путь — артефакт сборки», переименование секрета в 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, — это сигнал дрейфа. Без решения изменения политики невидимы, пока что-нибудь не сломается. С ним дрейф обнаруживается как запрос: найди чтения, где сопоставленный класс расходится с текущим классификатором.

Конкретный механизм сопоставления

Работоспособная реализация состоит из трёх этапов.

Классификация. Фоновый классификатор поддерживает отображение шаблонов путей и записей происхождения в классы. Он запускается при создании файла, переименовании и изменении содержимоClose-up of an orange power button on an electronic remote controlую версию политики.

Circuit board macro

Сопоставление. При каждом чтении среда выполнения агента сопоставляет запрошенный класс с текущим представлением классификатора. Если вычисленный класс пути не входит в выданный набор агента, чтение отклоняется со структурированной причиной: вычисленный класс, версия политики и разрешение, которое потребовалось бы.

Эмиссия провенанса. Среда выполнения отправляет событие чтения с полями выше в приёмник, доступный только для добавления. Приёмник доступен для запросов по задаче, агенту и классу и служит основой для обнаружения дрейфа.

Две детали определяют, работает ли это на практике. Во-первых, сопоставление должно быть достаточно быстрым, чтобы находиться на пути чтения; кэша, ключ которого — inode и версия политики, обычно достаточно, с инвалидацией при изменении классификации. Во-вторых, отказ должен быть действенным. Отказ вида computed_class: credentials, required_grant: credentials:aws-prod точно говорит оператору, что менять. Отказ вида permission denied учит его выдать FDA.

Чего это стоит

Честных компромиссов три.

Задержка и поддержка. Сопоставление классов добавляет поиск на пути чтения, и таксономию нужно поддерживать. Классы, специфичные для предметной области, дрейфуют по мере реорганизации репозиториев командами. Таксономия, которую не поддерживают, становится источником ложных отказов, а ложные отказы подрывают доверие быстрее, чем широкие разрешения.

Законные отказы. Агентам иногда будут отказывать в чтениях, которые им следовало разрешить. Шаг сборки, читающий сгенерированный файл, который классификатор пометил как смежный с учётными данными, завершится ошибкой. Дизайн должен делать такой отказ действенным, а не молчаливым: показывать вычисленный класс, версию политики и самое узкое разрешение, которое разблокировало бы задачу. Молчаливый отказ порождает то же обходное поведение, что и грубые разрешения.

Сопротивление вендоров. Платформенные вендоры будут сопротивляться поставке таксономий классов, потому что они специфичны для предметной области и плохо задаются по умолчанию. Операционная система общего назначения не может знать, что каталог fixtures/ одной команды содержит синтетические данные, а другой — продакшн-снимки. Реалистичный путь — небольшой набор базовых классов, определённых платформой, плюс механизм расширения, с пониманием, что расширение — это место, где живёт большая часть ценности и большая часть бремени поддержки.

Ничто из этого не бесплатно, и ничто из этого не является полным ответом. Однако это смена единицы авторизации. FDA спрашивает, может ли агент читать всё. Модель с ограничением по классам спрашивает, какие виды данных этот агент в этой задаче может читать, и записывает ответ. Второй вопрос — тот, на который операторам действительно нужно отвечать, и тот, который текущая модель выразить не может.

ПоделитьсяXLinkedInПостоянная ссылка5 мин чтения

Связанное чтение

На какие вопросы это отвечает

Why is Full Disk Access the wrong unit of authorisation for agents?

It is a single boolean over every file the user can read, so an agent needing one project directory receives the same authority as one needing one config file. Revocation is unscoped, which makes the grant expensive to reason about and therefore rarely revoked.

What is a data class in this model?

A set of paths sharing a read policy, such as credentials, source trees, personal communications, and build artefacts. Agents request a class rather than a path, and the platform resolves paths to classes at grant time and re-resolves at read time.

Why should class membership be computed rather than declared?

If an agent could assert a path's class, renaming a secret into a build output directory would defeat the taxonomy. Computing class from path patterns, metadata, content signals, and lineage keeps a renamed secret protected.

Why does every read need a task identifier?

Without it, audit records cannot distinguish a legitimate build step from an exfiltration path. The task identifier lets operators ask which tasks read credentials and whether any also read personal communications in the same window.

What are the main costs of class-scoped read rights?

Resolution adds a lookup on the read path, the taxonomy must be maintained, legitimate agents will occasionally be denied, and vendors resist shipping domain-specific taxonomies as defaults. The denial must be actionable rather than silent.