# CocoaSkills RFC 0008: Реестр аудита и цепочка доверия

Статус: черновик
Дата: 2026-07-07
Автор: Ivan Oparin
Цель: v0.11.0
Английская версия, источник правды: [v0.11-design.md](v0.11-design.md)

Вердикт аудита полезен, только когда машина может его получить, проверить, кто
его выдал, и убедиться, что он покрывает ровно те байты, которые будут
установлены. Этот RFC определяет подписанную запись аудита, протокол реестра,
который эти записи отдаёт, модель доверия с корнем в пинованных вне канала
ключах и конфигурацию клиента, которая позволяет организации направить
CocoaSkills на собственный реестр. Все код-блоки идентичны английской версии.

## 1. Обзор

- Запись аудита это подписанное утверждение, что названный навык на конкретном
  commit и content-hash имеет определённый статус аудита. Записи подписываются
  Ed25519.
- Реестр отдаёт записи по небольшому HTTP API и ведёт append-only журнал
  прозрачности с подписанным снапшотом.
- Доверие укоренено вне канала: клиент пинует публичные ключи каждого реестра в
  своей конфигурации. Транспорт несёт данные, подписи несут доверие.
- Конфигурация клиента принимает массив доверенных реестров. Встроенный
  дефолт указывает на публичный центральный реестр; организация может отключить
  его и запиновать только свой внутренний реестр.
- При установке клиент разрешает каждый навык против доверенных реестров и
  применяет политику. Федерация работает по правилу deny-wins: отзыв в любом
  доверенном реестре блокирует навык.
- Маркер установки записывает аттестацию, которая авторизовала установку,
  поэтому решение проверяемо офлайн.

## 2. Мотивирующий случай

Регулируемая организация гоняет CocoaSkills на сотнях машин и разрешает
установку навыков только из источников, которые её собственная служба
безопасности проаудировала. Она не может зависеть от публичного сервиса и не
может позволить локальной конфигурации разработчика расширить границу доверия.
Ей нужны три вещи: реестр внутри своей сети, который фиксирует, какие commit
одобрили её аудиторы; способ запиновать доверие только на этот реестр; и
машинная конфигурация, которую разработчик не может переопределить. Этот RFC
определяет первые две; принудительная машинная конфигурация приходит в
сопутствующем изменении (раздел 10).

## 3. Запись аудита

Запись это JSON-объект с канонической сериализацией для подписи. Подпись
покрывает каждое поле, кроме `sig`.

```json
{
  "schema_version": 1,
  "name": "skill-tracker",
  "source_identity": "gitlab.example.com/skills/skill-tracker",
  "commit": "8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d",
  "content_sha256": "sha256:1f2e3d...",
  "status": "audited",
  "audit": {
    "ruleset_version": "csk-audit/1",
    "findings_summary": { "critical": 0, "high": 0, "medium": 1, "low": 3 },
    "capabilities_hash": "sha256:aa11bb...",
    "scope": "all",
    "auditor": { "org": "Example Security", "contact": "security@example.com" },
    "audited_at": "2026-07-01T10:00:00Z",
    "expires_at": "2027-07-01T10:00:00Z"
  },
  "sig": { "key_id": "a1b2c3d4", "algorithm": "ed25519", "signature": "base64..." }
}
```

Правила полей:

- `name`, `source_identity`, `commit` и `content_sha256` идентифицируют
  аудированный артефакт. `source_identity` это каноническая форма `host/path`
  из RFC 0007, `commit` это полный 40-символьный hex, а `content_sha256` это
  content-hash установки, который CocoaSkills уже вычисляет.
- `status` принимает одно из `audited`, `revoked`, `deprecated` или `pending`.
  `audited` авторизует установку, `revoked` блокирует её, `deprecated`
  предупреждает и разрешает, `pending` не несёт вердикта.
- `audit.ruleset_version` называет набор детекторов, который выдал вердикт,
  поэтому клиент может отличить устаревший аудит от актуального.
- `audit.capabilities_hash` привязывает вердикт к объявленному конверту
  capabilities. Навык, меняющий объявленные capabilities без нового commit,
  невозможен, а хэш позволяет клиенту подтвердить одобренный конверт.
- `audit.scope` равен `all`, когда одобрен весь навык, или значению, которое
  называет одобренное подмножество, когда организация одобряет только
  конкретные команды.
- `audit.expires_at` необязателен. Клиент трактует истёкшую запись `audited`
  как `pending`.
- `sig.key_id` выбирает ключ подписи среди опубликованных ключей реестра.

## 4. API реестра

Реестр отдаёт следующие endpoint по HTTPS. Ответы в JSON.

- `GET /v1/meta` возвращает имя реестра, его опубликованные ключи подписи,
  версии схемы записей, которые он отдаёт, и заявленную политику.
- `GET /v1/records?source_identity=&commit=` возвращает запись одного
  артефакта или пустой результат. `GET /v1/records?content_sha256=` разрешает
  по content-hash.
- `GET /v1/snapshot` возвращает подписанный снапшот: Merkle-корень над всеми
  записями, размер журнала, монотонно растущую версию и timestamp. Клиент
  использует снапшот, чтобы обнаружить реестр, который придерживает новые
  записи или отдаёт откаченный вид.
- `GET /v1/log?since=` возвращает записи журнала прозрачности, каждая несёт
  запись и её позицию в hash-цепочке.
- `POST /v1/records` отправляет подписанную запись. Endpoint требует токен
  аудитора и проверяет подпись записи против зарегистрированного ключа
  аудитора.
- `GET /health` возвращает liveness без аутентификации.

Записи и снапшоты подписаны на покое. Клиент никогда не доверяет полю потому,
что его доставил транспорт; он сначала проверяет подпись против пинованного
ключа.

## 5. Модель доверия

Доверие укоренено в ключах, которые клиент пинует вне канала. Ключи реестра
никогда не бутстрапятся из самого реестра.

- Каждая запись доверенного реестра в конфигурации клиента перечисляет один или
  несколько публичных ключей. Клиент принимает запись, только когда её `sig`
  проверяется против одного из этих ключей.
- Снапшот защищает свежесть. Клиент хранит наибольшую версию снапшота, которую
  видел, по каждому реестру, и отклоняет снапшот, чья версия сдвинулась назад,
  что обнаруживает откат. Снапшот старше настроенного максимального возраста
  считается устаревшим, и клиент трактует запросы к нему как недоступность.
- Журнал прозрачности делает выпуск проверяемым. Каждая запись входит в
  append-only hash-цепочный журнал, поэтому аудитор может проверить, что набор
  записей, которые реестр отдаёт, совпадает с набором, который он когда-либо
  журналировал.

Это редуцированный профиль The Update Framework: пинованные корни плюс
подписанные снапшот и timestamp, без делегированных ролей. Полная иерархия
делегирования относится к будущей работе (раздел 14).

## 6. Конфигурация клиента

Машинная конфигурация получает массив `audit_registries`.

```json
{
  "audit_registries": [
    {
      "name": "central",
      "url": "https://registry.cocoaskills.org",
      "public_keys": ["ed25519:base64key..."],
      "enabled": true
    }
  ],
  "disable_builtin_registries": false
}
```

- Релиз CocoaSkills поставляет одну встроенную запись реестра: публичный
  центральный реестр с публичным ключом, вкомпилированным в релиз. Встроенная
  запись сливается с настроенными записями.
- `disable_builtin_registries: true` удаляет встроенную запись, что подходит
  закрытой сети, доверяющей только своему внутреннему реестру.
- Запись с тем же `url`, что и встроенная, переопределяет её, поэтому
  организация может заново добавить центральный реестр на собственных явных
  условиях после отключения дефолтов.
- `enabled: false` оставляет запись документированной, но неактивной.

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

## 7. Федерация и приоритет

Навык может разрешаться против нескольких доверенных реестров. Клиент
комбинирует результаты по правилу deny-wins:

- Если любой доверенный реестр возвращает `revoked` для артефакта, навык
  запрещён. Отзыв это сильнейший сигнал, и одного реестра достаточно для
  срабатывания.
- Иначе, если любой доверенный реестр возвращает валидную запись `audited`,
  навык авторизован.
- Иначе артефакт неизвестен, и решает настроенная политика: advisory-политика
  предупреждает и продолжает, строгая политика проваливает установку, если
  content-hash не запинен через существующий trust-механизм.

Для политики побеждает наиболее специфичная конфигурация, а для статуса всегда
побеждает deny.

## 8. Разрешение при установке

Гейт аудита получает шаг реестра, выполняемый на том же снапшоте содержимого,
который конвейер установки уже произвёл.

1. Вычислить content-hash снапшота навыка, что конвейер уже делает.
2. Обновить снапшот каждого доверенного реестра согласно правилам свежести.
   Реестр, который недостижим, использует последний кэшированный снапшот в
   пределах окна оффлайн-грейса, с предупреждением.
3. Найти артефакт по `source_identity` и `commit`, а также по
   `content_sha256`, в каждом доверенном реестре. Проверить каждую возвращённую
   запись против пинованных ключей реестра.
4. Применить правило федерации, чтобы получить статус, затем применить
   политику.
5. Записать авторизующую аттестацию в маркер установки: имя реестра, запись и
   id проверяющего ключа.

`csk status --attest` перезапускает шаги со 2 по 4 против установленных
маркеров, поэтому отзыв, выпущенный после установки, всплывает при следующей
проверке статуса.

## 9. Журнал прозрачности

Каждая принятая запись добавляется в hash-цепочный журнал. Запись N несёт
запись аудита и хэш над хэшем предыдущей записи и каноническими байтами
записи. Подписанный снапшот фиксирует голову журнала. Из этого следуют два
свойства:

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

## 10. Корпоративные операции

- Принудительная машинная конфигурация. Сопутствующее изменение добавляет
  системный слой конфигурации, читаемый до пользовательской конфигурации, чьи
  выбранные ключи не могут быть переопределены из пользовательской
  конфигурации. Организация пинует там `audit_registries` и политику аудита и
  распространяет это через управление устройствами, поэтому разработчик не может
  расширить границу доверия. Этот слой специфицируется собственным изменением и
  сочетается с подписью конфигурации из RFC 0007 раздел 10.
- Импорт для air-gapped. Реестр может экспортировать подписанный бандл записей,
  а внутренний реестр может его импортировать, поэтому офлайн-сеть зеркалирует
  вышестоящий набор аудита без живого соединения.
- Доступ аудитора. Отправка требует токена аудитора, привязанного к
  зарегистрированному ключу, поэтому набор сторон, которые могут выпускать
  записи, контролируется и атрибутируется.
- Двойная подпись. Запись может нести более одной подписи, когда организация
  требует одобрения навыка двумя аудиторами.

## 11. Граница безопасности

- Ключи пинуются вне канала. Реестр не может расширить собственные полномочия,
  потому что клиент проверяет против настроенных ключей и никогда против
  ключей, которые отдаёт реестр.
- Подпись привязывает вердикт к `content_sha256`, поэтому одобрение покрывает
  ровно эти байты, а новый commit не наследует ничего.
- Deny-wins федерация означает, что один отзывающий реестр защищает каждую
  машину, которая ему доверяет, даже когда другой реестр всё ещё отдаёт запись
  `audited`.
- Снапшот и журнал защищают от реестра, который придерживает отзывы или
  откатывает свой вид.
- Реестр сам по себе ничего не решает об установке. Он отдаёт подписанные
  утверждения, а клиент комбинирует их под локальной детерминированной
  политикой.

## 12. Каталог отказов

Разрешение выдаёт конкретный исход, когда:

- подпись записи не проверяется против ни одного пинованного ключа её реестра:
  запись игнорируется, выдаётся предупреждение;
- версия снапшота сдвинулась назад или снапшот старше максимального возраста:
  реестр считается недоступным;
- доверенный реестр недостижим и нет кэшированного снапшота в пределах окна
  грейса: реестр недоступен, и строгая политика проваливает установку;
- артефакт неизвестен каждому доверенному реестру: решает политика;
- любой доверенный реестр возвращает `revoked`: установка запрещена.

## 13. Совместимость

- Существующие установки не затронуты. Без настроенных `audit_registries` и с
  включённым встроенным дефолтом запросы advisory, поэтому машина, которая
  никогда не настраивала реестр, ставится как раньше.
- Шаг реестра аддитивен к гейту аудита из RFC 0005 и RFC 0006. Статические
  детекторы и локальные backend работают как сегодня; реестр добавляет
  федеративный вердикт поверх.
- Маркеры установки получают необязательную секцию аттестации. Старые маркеры
  без неё остаются валидными.

## 14. Будущая работа

- Делегированные роли подписи, переход от пинованных корней к полной иерархии
  делегирования The Update Framework.
- Автоматический ре-аудит при апгрейде набора детекторов, поэтому новый
  `ruleset_version` планирует свежие вердикты для затронутых артефактов.
- Публикация аттестаций в публичный сервис прозрачности для стороннего
  мониторинга.
- Проверяемая связь между записью реестра и прогоном аудита, который её
  произвёл, поэтому потребитель может воспроизвести детекторы, приведшие к
  вердикту.
