docs(security-privacy): v2 — ретро-ревью (10 находок)

- аудио per-node гейтинг (PipeWire Security Context + WirePlumber), не голый сокет:
  audio_out ≠ audio_in; capture только по runtime-гранту
- сеть: строгая гарантия только бинарно on/off; host-allowlist best-effort
  (DNS-allowlist обходится прямым IP — нужен forced-proxy по SNI/Host)
- якорь first-party не существует до v4: v0-v3 = trust-by-build/физконтроль
- целостность id при установке (отказ при коллизии; ru.shturman.plugin.* зарезервирован)
- жизненный цикл гранта: while-in-use, авто-снятие на stop/crash; enforce-split
  (статически прокси+sandbox/App-Host, рантайм-гранты Perm-Broker)
- НОВОЕ: §1a Модель угроз (адверсарии↔слои + out-of-scope)
- 152-ФЗ: явное отзываемое согласие (не только уведомление), минимизация VIN/локации,
  роли оператора/обработчика в BYO, локализация ≠ правовое основание
- audit-log промотирован в нормативный (владелец A §9 + UI домена C)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 00:55:15 +03:00
parent 76596408e1
commit 3790715fc7
+68 -21
View File
@@ -4,8 +4,8 @@
> вопросы**, направленные из [ipc.md](ipc.md): гейтинг data-plane, экспорт объектов > вопросы**, направленные из [ipc.md](ipc.md): гейтинг data-plane, экспорт объектов
> из песочницы, приватное хранилище, целостность манифеста. > из песочницы, приватное хранилище, целостность манифеста.
Статус: **v1 (на ревью).** Статус: **v2 (на ревью).** v2 — после ретро-ревью (10 находок).
Связано с: [architecture.md](../architecture.md) (§5,§7) · [ipc.md](ipc.md) · [plugin-sdk.md](plugin-sdk.md) · [principles.md](../principles.md) (#1,#2,#7) · домен F Связано с: [architecture.md](../architecture.md) (§5,§7) · [ipc.md](ipc.md) · [plugin-sdk.md](plugin-sdk.md) · [principles.md](../principles.md) (#1,#2,#7) · [a-base-system](../domains/a-base-system.md) · домены C, F
--- ---
@@ -21,6 +21,19 @@
> Ключевой инвариант: **сбежавший из песочницы плагин всё равно не запишет в CAN** — > Ключевой инвариант: **сбежавший из песочницы плагин всё равно не запишет в CAN** —
> писать некуда. Песочница защищает данные и приватность, не красные линии. > писать некуда. Песочница защищает данные и приватность, не красные линии.
## 1a. Модель угроз (кого и чем закрываем)
| Адверсарий | Чем адресуем | Предел |
|------------|--------------|--------|
| Злонамеренный сторонний плагин | песочница + capability-гейтинг + рисковые комбинации (§7) | данные вне его storage не гарантированы без at-rest-шифрования |
| Сбежавший/скомпрометированный плагин | архитектурный слой (нет write-пути к CAN на нативном silent-CAN) | приватность чужих данных НЕ защищена |
| Багнутый/скомпрометированный first-party (авто-грант) | **не ограничивается** capability-контролем; доверие — подписанный образ (v4) | до v4 — trust-by-build |
| Сетевой MITM к онлайн-LLM | TLS + RU-провайдеры/152-ФЗ | что уходит в промпт — §7 |
| Supply-chain / подмена образа·манифеста | secure boot + подписанный OTA (v4) + install-id-check (§5) | задел; до v4 частично |
| Изъятый носитель (eMMC/SD) | at-rest fscrypt + OTP ([a-base](../domains/a-base-system.md) §3, v4) | до v4 — открыто |
**Вне scope:** украденное целиком самораз-блокирующееся устройство (headless-unlock из eFuse); баги в самих ядро-сервисах (Vehicle-Data/брокер); недоверенное железо/BSP/DBC; атаки на стороне RU-LLM-провайдера.
## 2. Песочница ## 2. Песочница
- **Апы/плагины — bubblewrap:** mount/pid/net/user namespaces, **seccomp**-фильтр - **Апы/плагины — bubblewrap:** mount/pid/net/user namespaces, **seccomp**-фильтр
@@ -40,10 +53,10 @@
| `vehicle_read:[signals]` | чтение указанных сигналов | D-Bus-прокси: `VehicleData`, фильтр по списку | | `vehicle_read:[signals]` | чтение указанных сигналов | D-Bus-прокси: `VehicleData`, фильтр по списку |
| `assistant_intents:[…]` | регистрация интентов | D-Bus: `Assistant.RegisterIntents` + OWN `IntentHandler` | | `assistant_intents:[…]` | регистрация интентов | D-Bus: `Assistant.RegisterIntents` + OWN `IntentHandler` |
| `ui_tiles` / `ui_screens` | вклад в UI | D-Bus: `Shell.Register*` (+ Wayland-сокет, если поверхность) | | `ui_tiles` / `ui_screens` | вклад в UI | D-Bus: `Shell.Register*` (+ Wayland-сокет, если поверхность) |
| `audio_out` | воспроизведение | **PipeWire-сокет** (пропускается в песочницу) | | `audio_out` | воспроизведение | **PipeWire Security Context** + WirePlumber-политика: видны только playback-ноды (не capture, не monitor чужих выходов) |
| `audio_in` (микрофон) | запись с микрофона — **высокочувствительно** | PipeWire-сокет + **runtime-грант + видимый индикатор**; сторонним — ограниченно | | `audio_in` (микрофон) | запись с микрофона — **высокочувствительно** | **per-node** capture-разрешение, выдаётся в момент **runtime-гранта** (не голый сокет!); **видимый индикатор**; сторонним — ограниченно. Нужен PipeWire с Security Context (~0.3.65+) |
| `network` | доступ в сеть (опц. **host-allowlist** в манифесте) | net-namespace + **runtime-грант** | | `network` | доступ в сеть | net-namespace + **runtime-грант**. ⚠️ Строго — только бинарно on/off; **host-allowlist** требует forced-proxy/egress-firewall по SNI/Host (DNS-allowlist обходится прямым IP), иначе best-effort (§7) |
| `location` | GPS/положение | D-Bus (location) + **runtime-грант** | | `location` | GPS/положение | D-Bus `ru.shturman.Location` (ipc §3) + **runtime-грант** |
| `gpu` (render) | GPU-ускорение (UI/ML) | bubblewrap пропускает `/dev/dri` | | `gpu` (render) | GPU-ускорение (UI/ML) | bubblewrap пропускает `/dev/dri` |
| `storage` | приватное хранилище | bubblewrap **mount** `/data/apps/<id>/` | | `storage` | приватное хранилище | bubblewrap **mount** `/data/apps/<id>/` |
@@ -52,7 +65,11 @@
## 4. Гейтинг по каналам (закрывает routed-вопросы из ipc) ## 4. Гейтинг по каналам (закрывает routed-вопросы из ipc)
- **D-Bus** — фильтрующий per-app прокси из манифеста (детали — [ipc.md](ipc.md) §5). - **D-Bus** — фильтрующий per-app прокси из манифеста (детали — [ipc.md](ipc.md) §5).
- **PipeWire (аудио)** — bubblewrap пропускает pipewire-сокет только при `audio_*`. - **PipeWire (аудио)** — **проброс сокета сам по себе НЕ гейтинг** (голый сокет открывает
все ноды сессии: любой capture-источник и monitor любого выхода). Гейтинг — **per-node**:
PipeWire Security Context (sandbox-id на соединении) + WirePlumber-политика (по умолчанию
только playback; capture/микрофон — по явному per-node гранту в момент `audio_in`).
Индикатор и runtime-грант (§7) enforce-ятся на слое PipeWire/WirePlumber, не на сокете.
*закрывает «гейтинг data-plane».* *закрывает «гейтинг data-plane».*
- **Wayland (графика/UI)** — wayland-сокет пропускается только при `ui_*` с поверхностью. - **Wayland (графика/UI)** — wayland-сокет пропускается только при `ui_*` с поверхностью.
*закрывает «гейтинг data-plane».* *закрывает «гейтинг data-plane».*
@@ -71,11 +88,26 @@ runtime-грант для чувствительных (network / audio_in / loc
промпт в момент использования промпт в момент использования
``` ```
- **First-party** апы — авто-грант. *Что делает их first-party:* они **часть **Кто что enforce-ит (две роли):**
подписанного read-only base-образа** (вшиты, не устанавливаются пользователем) — - **Статически (в пути вызова):** per-app D-Bus-прокси + bubblewrap-сокеты/mounts,
это и есть якорь доверия, а не флажок в манифесте. **настраивает App-Host** из манифеста — физически блокирует незаявленное (ipc §5).
- **Сторонние** — устанавливаются в `/data`, **всегда** в песочнице и под - **Рантайм-гранты** (чувствительные caps): **Perm-Broker** (`RequestRuntimeGrant`, ipc §3) —
capability-гейтингом; install-time ревью + runtime-промпты на чувствительное. владеет политикой и согласием, не статическим путём данных. *(В architecture §7
«единственная дверь» = единый грантодатель/портал, не «брокер в каждом вызове».)*
**Жизненный цикл гранта:** привязан к активной сессии; **авто-снимается на
StopApp/AppCrashed** (Perm-Broker — источник истины; App-Host закрывает ресурс —
PipeWire-capture/location). Для `audio_in`/`location`**while-in-use**: отзыв/ре-промпт
при уходе в фон. Персист («всегда») — только по явному выбору; иначе TTL на простое.
- **First-party** апы — авто-грант. *Якорь:* они **часть read-only base-образа**.
⚠️ **До v4 криптоподписи образа НЕТ** (secure boot + подписанный OTA — v4, [a-base](../domains/a-base-system.md) §4):
в v0v3 якорь = trust-by-build / физический контроль (dev-ключи, eFuse не жжём).
- **Сторонние** — в `/data`, **всегда** в песочнице; install-ревью + runtime-промпты.
**Целостность id при установке:** App-Host/Perm-Broker **отвергает** плагин, чей `id`
совпадает с уже установленным или встроенным/first-party; namespace `ru.shturman.plugin.*`
зарезервирован за образом. ⚠️ Без подписи (§6) это ловит только случайные коллизии, НЕ
намеренную подмену ещё-не-установленного легитимного id — аргумент за подпись **раньше** «маховика».
## 6. Доверие и дистрибуция (целостность манифеста) ## 6. Доверие и дистрибуция (целостность манифеста)
@@ -97,13 +129,20 @@ runtime-грант для чувствительных (network / audio_in / loc
|--------|--------------------|------------------| |--------|--------------------|------------------|
| Голос (аудио) | локально (STT/TTS) | **никогда** | | Голос (аудио) | локально (STT/TTS) | **никогда** |
| Текст запроса | локально → онлайн-LLM при необходимости | только текст, только в онлайн-режиме | | Текст запроса | локально → онлайн-LLM при необходимости | только текст, только в онлайн-режиме |
| Данные машины (OBD/DTC) | локально | **в промпт онлайн-LLM** — только когда пользователь спросил и активен онлайн-режим; в офлайне не уходит вовсе | | Данные машины (OBD/DTC) | локально | **в промпт онлайн-LLM** — только онлайн, по запросу **И при явном (отзываемом) согласии** на весь исходящий промпт; офлайн не уходит |
| Память о водителе (`.md`) | локально | нет (без явного согласия) | | Память о водителе (`.md`) | локально | нет (без явного согласия) |
| Телеметрия | локально | **opt-in**, по умолчанию выключена | | Телеметрия | локально | **opt-in**, по умолчанию выключена |
- Онлайн-LLM — только **RU-провайдеры** (GigaChat/YandexGPT), данные в РФ (152-ФЗ). - Онлайн-LLM — только **RU-провайдеры** (GigaChat/YandexGPT), данные в РФ (152-ФЗ).
- **Прозрачность:** в онлайн-режиме контекст машины уходит провайдеру в составе - **Прозрачность:** в онлайн-режиме контекст машины уходит провайдеру в составе
промпта — это явно сообщается; офлайн-фолбэк держит всё на устройстве. промпта — это явно сообщается; офлайн-фолбэк держит всё на устройстве.
- **Согласие, не только уведомление:** при первом онлайн-использовании — явный
отзываемый consent-гейт на ВЕСЬ исходящий промпт (данные машины + подмешанное: VIN,
локация, память водителя). По умолчанию **VIN и точную локацию из промпта исключаем**,
включаем только по отдельному гранту.
- **152-ФЗ — роли и основание:** в BYO-схеме (креды пользователя) определить оператора/
обработчика ПДн и правовое основание передачи (→ legal-трек). **Локализация ≠ покрытие
152-ФЗ:** «данные в РФ» (где хранятся) ≠ основание на обработку/передачу (согласие).
**Микрофон:** wake-word слушает постоянно, но **локально**; в обработку идёт только **Микрофон:** wake-word слушает постоянно, но **локально**; в обработку идёт только
после слова-активатора; включённый микрофон показывается **видимым индикатором**. после слова-активатора; включённый микрофон показывается **видимым индикатором**.
@@ -111,9 +150,10 @@ runtime-грант для чувствительных (network / audio_in / loc
**Охват гарантий и рисковые комбинации.** Таблица выше описывает поведение **Охват гарантий и рисковые комбинации.** Таблица выше описывает поведение
first-party (ассистент). Сторонний плагин с `network` **+** чувствительными данными first-party (ассистент). Сторонний плагин с `network` **+** чувствительными данными
(`vehicle_read`/`location`) теоретически может слить их куда угодно — это **рисковая (`vehicle_read`/`location`) теоретически может слить их куда угодно — это **рисковая
комбинация**: требует заметного предупреждения при установке, по возможности комбинация**: требует заметного предупреждения при установке. **Host-allowlist
host-allowlist сети. Системная приватность держится на **capability-контроле**, а не best-effort** (от случайной утечки, не от злонамеренного: обходится прямым IP без
только на политике ассистента. forced-proxy, §3); строгая гарантия — только бинарный `network` on/off. Системная
приватность держится на **capability-контроле**, а не только на политике ассистента.
--- ---
@@ -123,10 +163,12 @@ host-allowlist сети. Системная приватность держит
выданные capabilities по каждому апу. → Settings/Shell + домен C. выданные capabilities по каждому апу. → Settings/Shell + домен C.
- 🟡 **Цепочка доверия к base-образу.** Secure boot (якорь) — в [hardware.md](hardware.md) §3; - 🟡 **Цепочка доверия к base-образу.** Secure boot (якорь) — в [hardware.md](hardware.md) §3;
осталась вторая половина — **подписанные OTA**. → домен A. осталась вторая половина — **подписанные OTA**. → домен A.
- **Host-scoping сети.** → решено в [plugin-sdk.md](plugin-sdk.md) §3 (host-allowlist - 🟡 **Host-scoping сети.** Host-allowlist — **best-effort** (DNS-allowlist обходится по IP);
в манифесте; голый `network: true` — повышенный риск). строго — только forced-proxy/egress-firewall по SNI/Host, иначе бинарный on/off. → §3/§7 + plugin-sdk §3.
- ◻️ **Локальный audit-log** чувствительного доступа (микрофон, сеть, гранты) — - **Локальный audit-log — нормативный** (не опция): пишет домен A рядом с логированием
локально, не телеметрия. → этот контракт + Settings. ([a-base](../domains/a-base-system.md) §9), в fscrypt-поддерево `/data` (§3 там), durable
через fsync. Факты доступа к чувствительному (audio_in/сеть/location) + cloud-egress
(провайдер, время, capability — без payload). Просмотр/отзыв — **UI домена C** (не сервис `ru.shturman.Settings`).
- 🟡 **Шифрование данных at-rest.** Изъятая eMMC/SD = открытая утечка ПДн водителя - 🟡 **Шифрование данных at-rest.** Изъятая eMMC/SD = открытая утечка ПДн водителя
(память `.md`, токены, app-storage); sandbox это не закрывает (модель §1–§6 — про (память `.md`, токены, app-storage); sandbox это не закрывает (модель §1–§6 — про
работающую систему, не про физическое изъятие). → решено направлением в работающую систему, не про физическое изъятие). → решено направлением в
@@ -144,4 +186,9 @@ host-allowlist сети. Системная приватность держит
| Экспорт из песочницы | OWN под префиксом `ru.shturman.plugin.<id>.*` | 2026-06-16 | | Экспорт из песочницы | OWN под префиксом `ru.shturman.plugin.<id>.*` | 2026-06-16 |
| Хранилище | `storage` → mount `/data/apps/<id>/` | 2026-06-16 | | Хранилище | `storage` → mount `/data/apps/<id>/` | 2026-06-16 |
| Доверие к манифесту | install-approve + sandbox сейчас; подпись/стор — задел (🟡) | 2026-06-16 | | Доверие к манифесту | install-approve + sandbox сейчас; подпись/стор — задел (🟡) | 2026-06-16 |
| Приватность | local-first; в облако только текст; OBD в промпт лишь онлайн + по запросу; телеметрия opt-in | 2026-06-16 | | Приватность | local-first; в облако только текст; OBD в промпт лишь онлайн + по запросу **+ явное согласие**; телеметрия opt-in | 2026-06-16 |
| Enforce-split | статически — прокси+sandbox (App-Host); рантайм-гранты — Perm-Broker; грант while-in-use, авто-снятие | 2026-06-16 |
| Аудио | per-node гейтинг (PipeWire Security Context + WirePlumber), не голый сокет; capture по гранту | 2026-06-16 |
| Сеть | строго — бинарно on/off; host-allowlist best-effort без forced-proxy | 2026-06-16 |
| Целостность id | install-time отказ при коллизии id; `ru.shturman.plugin.*` зарезервирован | 2026-06-16 |
| Audit-log | нормативный, владелец — домен A (§9, fscrypt-поддерево); просмотр — UI домена C | 2026-06-16 |