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

ADR 0180 — Контейнер проекта работает от владельца папки, измеренного api

Статус​

Accepted. 2026-09-29 (AT-247, история HS-071, эпик EP-030, раунд 27). Поверх ADR 0130 (broker, сдерживание в пять слоёв и ВЫЧИСЛЯЕМАЯ спецификация) и ADR 0144 (broker обслуживает mounted). Не редактирует их: добавляет поле в спецификацию и говорит, почему оно не открывает никакой двери.

Контекст​

Измерено в реальном использовании 2026-09-29: docker inspect контейнера проекта показывал пустой User= (root) и CapDrop=[ALL]. Папка проекта, смонтированная в /work, — 755 с uid оператора (1000). Root без CAP_DAC_OVERRIDE не пишет в папку другого uid, а --cap-drop ALL — ровно то, что отнимает эту возможность. Итог: npm install давал EACCES у всех dev agents и у QA-automação; работал лишь write_file, потому что пишет процессом engine на ХОСТЕ, вне контейнера.

Воспроизведено в реальном Docker (node:22-bookworm-slim, --cap-drop ALL, папка 755 uid 1000): от root touch даёт Permission denied; с --user 1000:1000 пишет. С uid без записи в /etc/passwd HOME становится /, и npm умирает в //.npm.

У DockerPort пять операций, bind — ОДНА папка помеченного типа, а у типа нет privileged/cap_add/свободного -v (ADR 0130). Любое исправление должно в это поместиться.

Решение​

  1. EspecificacaoDeContainer получает usuario?: { uid, gid }. CLI-адаптер переводит его в --user <uid>:<gid> и в --env HOME=/tmp (константа: uid без passwd получает HOME=/). Без usuario run выходит байт в байт как раньше.
  2. Измеряет api, которая достигает папки (базы, смонтированной по идентичности, или управляемого корня): GET /internal/projects/:id/container-spec начинает возвращать usuarioDaPasta (stat папки; null, если не измеряет, если проект runner или если владелец root). Broker составляет usuario ИЗ ЭТОГО контекста — никогда из тела запроса: у start нет тела, а pedidoDeExecValidado игнорирует любое лишнее поле.
  3. Broker перепроверяет (usuarioValidado): целые в 1..2^31-1, 0 отклоняется с указанием поля. Root — это ОТСУТСТВИЕ; явный --user 0 был бы ослаблением, которого никто не решал.
  4. exec не получает поля: docker exec наследует пользователя контейнера.
  5. Действует для container и mounted (api измеряет папку, которую каждый монтирует); режим runner не проходит через broker и не меняется.

Последствия​

  • Не открывает двери сдерживания. У процесса становится МЕНЬШЕ власти над хостом, чем раньше (не-root вместо root), --cap-drop ALL сохраняется, и нет поля, в которое вызывающий записал бы пользователя, capability, mount или сеть.
  • Второй рассмотренный вариант, вернуть DAC_OVERRIDE/CHOWN/FOWNER, отвергнут: ослабляет сдерживание, закреплённое ADR 0130, и сохраняет root.
  • Третий, чтобы broker сам делал stat, потребовал бы монтировать папки проектов в broker (сегодня у него лишь сокет) — больше поверхности в процессе, эквивалентном root. api уже их достигает.
  • Это не BREAKING для оператора: нечитаемая папка или папка root попадает в "как всегда". УЖЕ созданный контейнер не меняется, пока его не пересоздадут (--user — часть run); следующий container_start уже поднимается правильно.
  • Владелец папки ≠ тот, кем должен быть dev agent, — это не различие, которое делает продукт: цель — писать там, где оператор уже пишет.
  • Не измерено: Docker Desktop (macOS/Windows), где отображение uid bind-mount иное; Docker rootless; папка на NFS с root_squash.