Files
shturman/docs/contracts/security-privacy.md
kk0t9 fb4e585152 docs(domain G): v2 — связь/телефон (Connectivity+Phone), после adversarial-ревью (15 находок) + кросс-док
Новый домен G: Connectivity-core (WiFi/LTE/tethering + BT-адаптер/паринг) +
Phone-ап (HFP-звонки/контакты). Многоагентный adversarial-ревью: 24 находки,
15 подтверждено (default-refute; tech-измерение — 0, техника устояла), все применены.

Ключевое из ревью:
- BT-шов резолвнут двунаправленно: владелец BlueZ — Connectivity-core (G §3 ↔ H §6/§15 ).
- Граница голоса: только HFP спаренного телефона; встроенный модем — data-only (VoLTE/eCall вне скоупа).
- SIM PIN/PUK-флоу (sim_locked/no_sim) + captive-portal детект (portal/limited, не ложный online) — модем v1.
- Connectivity поднимается на Stage 2 (синхр. с architecture §6).
- Мультипаринг: ровно один active-телефон для HFP/PBAP (Dial/CallStateChanged однозначны).
- SCO-loss mid-call → CallState=audio_lost + снятие роли phone_call → H раскорчивает медиа (не залипает).
- Входящий-оверлей не перекрывает реверс-вид (z-order overlay-слота → C); tethering-петля AP+tether запрещена.
- PBAP-синк фоновый; отказ-пути паринга/no_service симметрично J/H.

Кросс-док: H §6/§15 (BlueZ ) + якоря D §147/§148→§10; architecture §3 (Connectivity+BT)
/§6 (Stage 2); ipc §3 (Type+tether, State enum, CallState=audio_lost); security-privacy §7
(контакты/журнал/SMS local-first); hardware §4 (Bluetooth); tech-stack (NM/MM/BlueZ);
C §11 (z-order overlay).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:08:52 +03:00

200 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Контракт: безопасность и приватность
> Модель изоляции, разрешений и приватности (152-ФЗ). Здесь **решаются открытые
> вопросы**, направленные из [ipc.md](ipc.md): гейтинг data-plane, экспорт объектов
> из песочницы, приватное хранилище, целостность манифеста.
Статус: **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) · [a-base-system](../domains/a-base-system.md) · домены C, F
---
## 1. Слои защиты (defense-in-depth)
| Слой | Что гарантирует |
|------|-----------------|
| **Архитектурный** | write-пути к CAN нет в системе → красные линии держатся даже при полном компромиссе (принципы #1, #2) |
| **Песочница** | bubblewrap (апы/плагины) + systemd-hardening (ядро) — ограничивает syscalls/FS/сеть |
| **Брокер/портал** | доступ к ресурсам только через capability-гейтинг (D-Bus-прокси + сокеты) |
| **Грант пользователя** | сторонние capabilities подтверждает пользователь |
> Ключевой инвариант: **сбежавший из песочницы плагин всё равно не запишет в 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. Песочница
- **Апы/плагины — bubblewrap:** mount/pid/net/user namespaces, **seccomp**-фильтр
syscalls, минимальный FS-вид, `no-new-privileges`. Плагин видит только: свой код,
разрешённые сокеты, своё приватное хранилище.
- **Ядро — systemd-hardening:** `ProtectSystem`, `RestrictAddressFamilies`,
`SystemCallFilter`, и т.п. на доверенных юнитах.
- WASM-тир для лёгких логических плагинов — задел (см. architecture §7).
## 3. Модель capabilities
Манифест декларирует, что апу/плагину нужно. Каждая capability открывает ровно один
ресурс и гейтится на своём канале:
| capability | что открывает | канал / механизм гейтинга |
|------------|---------------|----------------------------|
| `vehicle_read:[signals]` | чтение указанных сигналов | D-Bus-прокси: `VehicleData`, фильтр по списку |
| `assistant_intents:[…]` | регистрация интентов | D-Bus: `Assistant.RegisterIntents` + OWN `IntentHandler` |
| `ui_tiles` / `ui_screens` | вклад в UI | D-Bus: `Shell.Register*` (+ Wayland-сокет, если поверхность) |
| `audio_out` | воспроизведение | **PipeWire Security Context** + WirePlumber-политика: видны только playback-ноды (не capture, не monitor чужих выходов) |
| `audio_in` (микрофон) | запись с микрофона — **высокочувствительно** | **per-node** capture-разрешение, выдаётся в момент **runtime-гранта** (не голый сокет!); **видимый индикатор**; сторонним — ограниченно. Нужен PipeWire с Security Context (~0.3.65+) |
| `network` | доступ в сеть | net-namespace + **runtime-грант**. ⚠️ Строго — только бинарно on/off; **host-allowlist** требует forced-proxy/egress-firewall по SNI/Host (DNS-allowlist обходится прямым IP), иначе best-effort (§7) |
| `location` | GPS/положение | D-Bus `ru.shturman.Location` (ipc §3) + **runtime-грант** |
| `camera_in:[sources]` (камера) | захват видео с источников — **высокочувствительно** | **per-node** PipeWire Security Context + WirePlumber (video-ноды, default-deny), **runtime-грант + видимый индикатор**; `/dev/videoN` — только first-party; стороннему — узкий per-source грант |
| `gpu` (render) | GPU-ускорение (UI/ML) | bubblewrap пропускает `/dev/dri` |
| `storage` | приватное хранилище | bubblewrap **mount** `/data/apps/<id>/` |
> Чего нет в манифесте — физически недоступно: нет сокета, нет имени на шине, нет mount.
## 4. Гейтинг по каналам (закрывает routed-вопросы из ipc)
- **D-Bus** — фильтрующий per-app прокси из манифеста (детали — [ipc.md](ipc.md) §5).
- **PipeWire (аудио)** — **проброс сокета сам по себе НЕ гейтинг** (голый сокет открывает
все ноды сессии: любой capture-источник и monitor любого выхода). Гейтинг — **per-node**:
PipeWire Security Context (sandbox-id на соединении) + WirePlumber-политика (по умолчанию
только playback; capture/микрофон — по явному per-node гранту в момент `audio_in`).
Индикатор и runtime-грант (§7) enforce-ятся на слое PipeWire/WirePlumber, не на сокете.
*закрывает «гейтинг data-plane».*
- **Wayland (графика/UI)** — wayland-сокет пропускается только при `ui_*` с поверхностью.
*закрывает «гейтинг data-plane».*
- **Экспорт объектов из песочницы** — прокси разрешает плагину **OWN** имена строго
под префиксом `ru.shturman.plugin.<id>.*`, чтобы Assistant/Shell могли звать его
`IntentHandler`/UI-объекты. → *закрывает «экспорт объектов».*
- **Приватное хранилище** — `storage` монтирует в песочницу только `/data/apps/<id>/`;
чужие данные и системные пути не видны. → *закрывает «приватное хранилище».*
## 5. Жизненный цикл разрешения
```
declare (манифест) → install: пользователь видит запрошенные capabilities и
подтверждает → enforce (прокси + sandbox конфигурятся под манифест) →
runtime-грант для чувствительных (network / audio_in / location) — отдельный
промпт в момент использования
```
**Кто что enforce-ит (две роли):**
- **Статически (в пути вызова):** per-app D-Bus-прокси + bubblewrap-сокеты/mounts,
**настраивает App-Host** из манифеста — физически блокирует незаявленное (ipc §5).
- **Рантайм-гранты** (чувствительные caps): **Perm-Broker** (`RequestRuntimeGrant`, ipc §3) —
владеет политикой и согласием, не статическим путём данных. *(В 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; manifest-id namespace **`ru.shturman.*` (весь tree)** зарезервирован за first-party/образом —
сторонний id = свой reverse-DNS (`dev.example.*`). *(D-Bus OWN-экспорт плагина — отдельный
платформенный префикс `ru.shturman.plugin.<id>.*` (§4), это не manifest-id, не путать.)* ⚠️ Без подписи (§6) это ловит только случайные коллизии, НЕ
намеренную подмену ещё-не-установленного легитимного id — аргумент за подпись **раньше** «маховика».
## 6. Доверие и дистрибуция (целостность манифеста)
🟡 **Выбор на подтверждение:**
- **(рек., сейчас)** install-time approve + песочница: пользователь видит
capabilities и соглашается; защита — sandbox. Просто, достаточно для раннего этапа.
- **(задел)** **подпись манифеста** + keyring издателей — против подмены capabilities
при сторонней дистрибуции. → домен F (экосистема/«магазин»).
- **(опц.)** курируемый стор с ревью.
Рекомендую: сейчас — approve+sandbox; подпись/стор — когда включаем community-маховик.
*закрывает «целостность манифеста» (с явным заделом).*
## 7. Приватность / 152-ФЗ
**Принцип:** local-first; наружу — минимум и осознанно.
| Данные | Где обрабатываются | Уходит в облако? |
|--------|--------------------|------------------|
| Голос (аудио) | локально (STT/TTS) | **никогда** |
| Текст запроса | локально → онлайн-LLM при необходимости | только текст, только в онлайн-режиме |
| Данные машины (OBD/DTC) | локально | **в промпт онлайн-LLM** — только онлайн, по запросу **И при явном (отзываемом) согласии** на весь исходящий промпт; офлайн — не уходит |
| Память о водителе (`.md`) | локально | нет (без явного согласия) |
| Контакты/журнал/SMS (PBAP/MAP, домен G) | локально (fscrypt) | нет (без явного согласия) |
| Телеметрия | локально | **opt-in**, по умолчанию выключена |
- Онлайн-LLM — только **RU-провайдеры** (GigaChat/YandexGPT), данные в РФ (152-ФЗ).
- **Прозрачность:** в онлайн-режиме контекст машины уходит провайдеру в составе
промпта — это явно сообщается; офлайн-фолбэк держит всё на устройстве.
- **Согласие, не только уведомление:** при первом онлайн-использовании — явный
отзываемый consent-гейт на ВЕСЬ исходящий промпт (данные машины + подмешанное: VIN,
локация, память водителя). По умолчанию **VIN и точную локацию из промпта исключаем**,
включаем только по отдельному гранту.
- **152-ФЗ — роли и основание:** в BYO-схеме (креды пользователя) определить оператора/
обработчика ПДн и правовое основание передачи (→ legal-трек). **Локализация ≠ покрытие
152-ФЗ:** «данные в РФ» (где хранятся) ≠ основание на обработку/передачу (согласие).
**Микрофон:** wake-word слушает **в состояниях `running`/`accessory`** (гейт Power — **не**
безусловно always-on; в `sleep`/`off` выключен, риск разряда АКБ, принцип #5; домен B §7 / D §8),
всё **локально**; в обработку идёт только после слова-активатора; включённый микрофон — **видимый индикатор**.
**Охват гарантий и рисковые комбинации.** Таблица выше описывает поведение
first-party (ассистент). Сторонний плагин с `network` **+** чувствительными данными
(`vehicle_read`/`location`) теоретически может слить их куда угодно — это **рисковая
комбинация**: требует заметного предупреждения при установке. **Host-allowlist —
best-effort** (от случайной утечки, не от злонамеренного: обходится прямым IP без
forced-proxy, §3); строгая гарантия — только бинарный `network` on/off. Системная
приватность держится на **capability-контроле**, а не только на политике ассистента.
---
## Открытые вопросы (найдено на self-review → роутинг)
- ◻️ **Отзыв и управление грантами.** Пользователь должен видеть и **отзывать**
выданные capabilities по каждому апу. → Settings/Shell + домен C.
- 🟡 **Цепочка доверия к base-образу.** Secure boot (якорь) — в [hardware.md](hardware.md) §3;
осталась вторая половина — **подписанные OTA**. → домен A.
- 🟡 **Host-scoping сети.** Host-allowlist — **best-effort** (DNS-allowlist обходится по IP);
строго — только forced-proxy/egress-firewall по SNI/Host, иначе бинарный on/off. → §3/§7 + plugin-sdk §3.
-**Локальный audit-log — нормативный** (не опция): пишет домен A рядом с логированием
([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 = открытая утечка ПДн водителя
(память `.md`, токены, app-storage); sandbox это не закрывает (модель §1–§6 — про
работающую систему, не про физическое изъятие). → решено направлением в
[a-base-system.md](../domains/a-base-system.md) §3 (fscrypt чувствительных поддеревьев,
привязка к OTP/eFuse, v4 вместе с secure boot).
---
## Журнал решений (security-privacy)
| Решение | Выбор | Дата |
|---------|-------|------|
| Изоляция | bubblewrap (апы) + systemd-hardening (ядро); WASM — задел | 2026-06-16 |
| Гейтинг ресурсов | per-capability на своём канале (D-Bus / PipeWire / Wayland / FS) | 2026-06-16 |
| Экспорт из песочницы | OWN под префиксом `ru.shturman.plugin.<id>.*` | 2026-06-16 |
| Хранилище | `storage` → mount `/data/apps/<id>/` | 2026-06-16 |
| Доверие к манифесту | install-approve + sandbox сейчас; подпись/стор — задел (🟡) | 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; manifest-id `ru.shturman.*` зарезервирован за first-party (OWN-экспорт `ru.shturman.plugin.<id>.*` — отдельный платформенный префикс) | 2026-06-16 |
| `camera_in` | per-node гейтинг видео (PipeWire Security Context, как audio_in); `/dev/videoN` — only first-party | 2026-06-16 |
| Audit-log | нормативный, владелец — домен A (§9, fscrypt-поддерево); просмотр — UI домена C | 2026-06-16 |