196 lines
28 KiB
Markdown
Executable File
196 lines
28 KiB
Markdown
Executable File
---
|
||
title: 'Изоляция исполнения агента: OS и контейнерный уровень'
|
||
tags:
|
||
- agent-security
|
||
- sandboxing
|
||
- seccomp
|
||
- gvisor
|
||
- firecracker
|
||
- bubblewrap
|
||
- ssh
|
||
- docker
|
||
- research
|
||
created: '2026-09-29'
|
||
status: complete
|
||
---
|
||
# Изоляция исполнения агента: OS и контейнерный уровень
|
||
|
||
Исследование: 8 подходов + паттерны ограниченного доступа к хосту.
|
||
Контекст: дать агенту собирать/тестировать код, но не дать дотянуться до чужих файлов и секретов.
|
||
|
||
---
|
||
|
||
## 1. gVisor (Google) — application kernel в userspace (`runsc`)
|
||
|
||
- gVisor — **не seccomp-фильтр, не обёртка над Linux-примитивами и не VM**. Это «третий подход»: application kernel, реализующий Linux-подобный интерфейс, написанный на **Go (memory-safe)** и работающий в **userspace**. Себя описывают как «seccomp on steroids».
|
||
- Архитектура: **Sentry** — application kernel на песочницу (перехватывает системные вызовы, сигналы, управление памятью, потоки); **Gofer** — на контейнер, даёт доступ к ФС. Системные вызовы приложения **не проходят** насквозь в ядро хоста — их обрабатывает Sentry.
|
||
- OCI-runtime `runsc` интегрируется с Docker и Kubernetes (GKE Sandbox, minikube) — можно запускать обычные контейнеры «под gVisor» без переписывания.
|
||
- Накладные расходы: **эмерджентны** (threads/memory mappings, а не фиксированные guest-ресурсы), быстрый старт, но **плохо для syscall-heavy нагрузок** (компиляция — как раз syscall-heavy, важно учитывать).
|
||
- Преимущество для агентов: **защита от container-escape через kernel-эксплойты** — единственная поверхность это сам Sentry в Go. Комбинируется с rule-based (seccomp) для defense-in-depth.
|
||
- Ссылка: https://gvisor.dev/docs/
|
||
|
||
## 2. Firecracker microVM (AWS) + jailer
|
||
|
||
- Firecracker — lightweight **microVM** (KVM-based, Rust), созданный для multi-tenant serverless (Lambda, Fargate). **Аппаратная виртуализация** = самая сильная граница изоляции при быстром старте (десятки мс) и низком footprint.
|
||
- Компаньон **`jailer`** создаёт многослойную границу для VMM-процесса:
|
||
- `chroot` + `pivot_root` в новый mount namespace; копирование бинарника в jail (защита от shared memory с другими Firecracker).
|
||
- Сброс в **непривилегированный uid/gid** (`--uid`/`--gid`) — VMM не root.
|
||
- **cgroups v1/v2** — лимиты CPU/памяти (`--cgroup cpuset.cpus=0`, `--parent-cgroup`).
|
||
- `setrlimit` — **fsize** (ограничение размера создаваемых файлов) и **no-file** (по умолчанию 2048 дескрипторов).
|
||
- Новый **PID namespace** (`--new-pid-ns` → `clone(CLONE_NEWPID)`, процесс становится init(1)), опционально `--netns`, `setsid()` + redirect I/O в /dev/null при `--daemonize`.
|
||
- **Вычищает все env-переменные** родителя и закрывает все FD кроме stdin/stdout/stderr.
|
||
- Каждая microVM изолирована обычными Linux-barriers через jailer + **seccomp-фильтры** для самого VMM-процесса (см. отдельный документ про seccomp filtering).
|
||
- Ссылки: https://github.com/firecracker-microvm/firecracker/blob/main/docs/jailer.md , https://firecracker-microvm.github.io/
|
||
|
||
## 3. E2B (e2b.dev) — sandbox для AI-агентов
|
||
|
||
- Managed sandbox-API именно под AI-агентов; под капотом — **Firecracker microVM** (у E2B открытая инфра-стековая часть, Firecracker-основа). Долгоживущие песочницы с **persistence**.
|
||
- Жизненный цикл: `Sandbox.create({timeoutMs})`, `setTimeout` для продления, `kill()` для остановки. Лимиты **до 24ч (Pro) / 1ч (Base)**, далее pause+resume (состояние сохраняется, окно таймаута сбрасывается).
|
||
- **Эфемерность на задачу**: таймаут → авто-pause/kill; каждый sandbox — отдельная microVM (свой kernel, своя ФС, свой network). Агент не видит хост.
|
||
- API: `Sandbox` из JS/TS/Python SDK; getInfo → sandboxId/templateId/startedAt/endAt; metadata. Шаблоны (templates) из Dockerfile.
|
||
- Для паттерна «собирать/тестировать код»: sandbox = полное Linux-окружение, изолированное от хоста инженера; секреты хоста недостижимы физически.
|
||
- Ссылка: https://e2b.dev/docs/sandbox
|
||
|
||
## 4. Daytona — control/compute plane sandbox-платформа
|
||
|
||
- **Три плоскости**: Interface plane (SDK Python/TS/Ruby/Go/Java, CLI, Dashboard, **MCP-сервер**, SSH) → Control plane (NestJS API, Proxy, Snapshot builder, Sandbox manager; Redis/PostgreSQL/Auth0) → Compute plane (runners, sandbox daemon, snapshot store, volumes).
|
||
- Изоляция: каждый sandbox — **isolated instance с собственными Linux namespaces** (process, network, filesystem mounts, IPC) + **dedicated vCPU, RAM и disk на sandbox**. Multi-tenancy на уровне organization boundary.
|
||
- **Snapshot builder** собирает снапшоты из **Dockerfile или pre-built image** → пушит в внутренний OCI-registry. Быстрый provisioning эфемерных окружений под задачу.
|
||
- Network: отдельный HTTP-прокси с **host-based routing** — `{port}-{sandboxId}.{proxy-domain}`; инжектит auth-заголовки, поддерживает WS.
|
||
- **Sandbox daemon / Toolbox API** внутри sandbox: файловые и git-операции, исполнение кода, computer use, log streaming, терминальные сессии. Volumes — персистентное хранилище между sandbox.
|
||
- Ссылка: https://www.daytona.io/docs/en/architecture/
|
||
|
||
## 5. Modal Sandboxes — контейнеры для untrusted code на лету
|
||
|
||
- Явно позиционируются как **secure containers for executing untrusted user or agent code**; определяешь контейнер **в runtime** (`modal.Sandbox.create(...)`), исполняешь произвольный код. Use-case прямо: «execute code generated by a language model», checkout git-репы и запуск тест-сьютов.
|
||
- **Эфемерность**: default max lifetime **5 минут**, до **24 часов** через `timeout`; `idle_timeout` для авто-остановки (sandbox активен, если есть running command / stdin-write / open TCP over tunnel).
|
||
- Lifecycle: Created → Scheduled (provision CPU/mem/GPU/volumes) → Started → Ready (readiness probe) → Finished (exit/timeout/OOM/terminate).
|
||
- **Readiness probes** (TCP или Exec, interval_ms default 100ms, max 5 min) — идеально для «подняли окружение, накатили зависимости, готово к тестам».
|
||
- Filesystem Snapshots — сохранить состояние и восстановить в новом sandbox (для работ >24ч).
|
||
- Изоляция: каждое исполнение — отдельный контейнер на worker'е Modal; запуск команд через `sb.exec(...)` с таймаутами.
|
||
- Ссылка: https://modal.com/docs/guide/sandbox
|
||
|
||
## 6. Fly Machines (Fly.io) — быстрые VM с супербыстрым стартом
|
||
|
||
- **Быстро-запускаемые VM** с start/stop на **subsecond** скоростях; контроль количества машин, lifecycle каждой машины, ресурсов и region placement через **REST API или flyctl**.
|
||
- Модель «одна машина = один экземпляр приложения/окружения»: полная VM-граница изоляции, микро-уровень ресурсов. Подходит как эфемерная «машина на задачу» для агента — создать, выполнить сборку/тесты, остановить.
|
||
- Эфемерность: stop/start по API; отдельные `[machines]` с независимым lifecycle. Для агента удобно: run-per-task VM, старый экземпляр уничтожается.
|
||
- Интеграция с flyd / hardware-virtualization (Firecracker-подобное) — граница уровня гипервизора, а не просто namespaces.
|
||
- Ссылки: https://fly.io/docs/machines/ , https://fly.io/machines/overview
|
||
|
||
## 7. Anthropic Claude Code sandbox modes (macOS Seatbelt)
|
||
|
||
- **Platform support**: sandboxed Bash tool встроен; работает на **macOS, Linux, WSL2**. **Native Windows не поддерживается** (только WSL2).
|
||
- **macOS**: ничего ставить не надо — используется встроенный фреймворк **Seatbelt** (sandbox-exec / sandbox profiles).
|
||
- **Linux/WSL2**: требуются пакеты **`bubblewrap`** (ФС-изоляция) и **`socat`** (relay сетевого трафика через sandbox-прокси). Опционально seccomp-фильтр из `@anthropic-ai/sandbox-runtime` (блок AF_UNIX sockets).
|
||
- **Режимы-аналоги** (у Claude Code — permission modes + sandbox modes): **Manual** стартует read-only (built-in read-only команды `ls/cat/git status` без вопросов); **Accept Edits** авто-одобряет правки и фиксированный набор ФС-команд (`mkdir,touch,rm,mv,cp,sed`) в рабочей директории; **Auto mode** — отдельная classifier-модель ревьюит и блокирует небезопасные действия.
|
||
- **Working directory boundary**: в Manual режиме запись **только в стартовую папку и подпапки**; чтение вне границы через Read/Grep/Glob требует разрешения. Расширяется `--add-dir` / `permissions.additionalDirectories`, сужается sandbox `denyRead`.
|
||
- **Network isolation**: первый запрос к новому домену → промпт; `curl`/`wget` **не авто-одобряются**; deny-правила матчат команду «как написано», а независимую от текста сетевую политику даёт именно sandbox network isolation.
|
||
- Escape hatch: `dangerouslyDisableSandbox` параметр (retry вне sandbox через обычный permission flow); можно запретить `allowUnsandboxedCommands: false` / `failIfUnavailable: true` для managed-деплоя.
|
||
- Linux нюанс: на Ubuntu 24.04+ с `kernel.apparmor_restrict_unprivileged_userns=1` нужен AppArmor-профиль для `bwrap` (иначе unprivileged userns запрещён).
|
||
- Ссылки: https://docs.claude.com/en/docs/claude-code/sandboxing , https://docs.claude.com/en/docs/claude-code/security
|
||
|
||
## 8. OpenAI Codex CLI sandbox (Seatbelt на macOS, bubblewrap/Landlock на Linux)
|
||
|
||
- **Дефолт локально**: сеть **выключена**, write-права **ограничены активным workspace**; OS-enforced sandbox + approval policy сверху.
|
||
- **Sandbox modes**: `read-only`, `workspace-write` (рабочая директория + оговорённые исключения), `danger-full-access`. Профили прав: built-in `:read-only`, `:workspace`, `:danger-full-access`, кастомные через `[permissions.<name>]`.
|
||
- **macOS**: **Seatbelt** (как и Chromium). **Linux**: исторически Landlock, теперь **bubblewrap по умолчанию** (Landlock отвергается для ФС-политик, т.к. **не умеет изолировать app-server Unix sockets**; флаг `features.use_legacy_landlock` надо выключать при апгрейде).
|
||
- Реализация `codex-linux-sandbox` (крейт) поверх bwrap:
|
||
- **`PR_SET_NO_NEW_PRIVS`** + in-process **seccomp network filter**.
|
||
- ФС **read-only по умолчанию** (`--ro-bind / /`), writable-корни через `--bind <root> <root>`.
|
||
- **Защищённые подпути** под writable-корнями (`.git`, resolved `gitdir:`, `.codex`) переоткрываются read-only через `--ro-bind`.
|
||
- **`--unshare-user`**, `--unshare-pid` (по умолчанию), `--unshare-net` при запрете сети без прокси.
|
||
- Порядок по path-specificity: `/repo=write`, `/repo/a=none`, `/repo/a/b=write` корректно комбинируются.
|
||
- Защита от symlink/несуществующих путей: монтирование **`/dev/null`** поверх symlink или первого отсутствующего компонента.
|
||
- **Managed proxy mode**: `--unshare-net` + внутренний TCP→UDS→TCP bridge; после старта bridge seccomp **блокирует создание AF_UNIX/socketpair**.
|
||
- `bwrap` ищется на PATH вне CWD; при отсутствии — bundled fallback (`codex-resources/bwrap`); WSL1 не поддерживается (нет user namespaces).
|
||
- **Two-phase в Codex cloud**: *setup phase* (сеть есть, ставим зависимости) → *agent phase* (по умолчанию offline). **Секреты доступны только в setup и удаляются до старта agent phase** — это ровно паттерн «дать собирать, но не дать украсть секреты».
|
||
- Managed: `requirements.toml` может запретить `approval_policy = "never"` и `sandbox_mode = "danger-full-access"`.
|
||
- Ссылки: https://developers.openai.com/codex/sandbox/ , https://github.com/openai/codex/blob/main/codex-rs/linux-sandbox/README.md
|
||
|
||
## 9. Chromium / Chrome seccomp-bpf sandbox (эталон многослойности)
|
||
|
||
- **Multi-process security model**: процессы, обрабатывающие недоверенные данные (renderer, utility, GPU), ограничены; к привилегированным ресурсам — только через **медиацию Browser-процесса** (broker).
|
||
- **Linux**: `namespaces` + **seccomp-BPF** (+ опционально legacy SELinux/AppArmor/Landlock в зависимости от доступных фич ядра). Несколько слоёв: confinement процесса **и** сокращение exposed kernel attack surface.
|
||
- Ключевая проблема seccomp-bpf: фильтрация на уровне syscall даёт трудно-понятную семантику; если процесс A под одним seccomp-bpf policy, нужно **гарантировать, что он не может повлиять на целостность процесса B** под другим policy (иначе sandbox escape).
|
||
- **macOS**: `sandbox/mac` использует **Seatbelt**; **Windows**: restricted tokens + job objects + alternate desktops + integrity levels. Один код-бейз, три разных OS-механизма.
|
||
- **Broker-паттерн**: GPU/utility не ходят в ядро напрямую, а просят Browser-процесс выполнить операцию — переносится на любые агентские архитектуры (агент просит «runner» выполнить привилегированное действие).
|
||
- Ссылки: https://github.com/chromium/chromium/tree/main/sandbox , https://chromium.googlesource.com/chromium/src.git/+/master/sandbox/linux/
|
||
|
||
## 10. bubblewrap (bwrap) — unprivileged sandboxing (упоминался в DeepSeek Harness как sandbox seam)
|
||
|
||
- **Low-level unprivileged sandboxing tool** (Flatpak, rpm-ostree, и теперь Claude Code / Codex на Linux). Ключ: использует **Linux user namespaces**, поэтому доступен **непривилегированному пользователю** — в отличие от docker/systemd-nspawn, которые админам.
|
||
- Всегда создаёт **новый mount namespace**: root на **tmpfs**, невидимый с хоста, автоочистка при выходе последнего процесса. Опции конструируют ФС: `--ro-bind /usr /usr`, `--proc /proc`, `--dev /dev`, `--bind`.
|
||
- Namespaces: `CLONE_NEWUSER` (`--unshare-user`, маппинг uid/gid), `CLONE_NEWIPC`, `CLONE_NEWPID` (`--unshare-pid` + trivial pid1 reaps zombies — решает docker pid-1 problem), `CLONE_NEWNET` (`--unshare-net`, только loopback), `CLONE_NEWUTS`; плюс **seccomp-фильтры**.
|
||
- Использует **`PR_SET_NO_NEW_PRIVS`** — выключает setuid-бинарники, классический способ выйти из chroot.
|
||
- ⚠️ Важно: bwrap **не готовый sandbox с политикой** — уровень защиты целиком определяется переданными аргументами. Владельцы могут повысить риск DoS.
|
||
- ⚠️ Подводные камни: без фильтрации `TIOCSTI` нужен `--new-session` (CVE-2017-5226); **всё, что смонтировано, потенциально эскалирует привилегии** (напр. bind D-Bus socket = исполнение через systemd → используйте `xdg-dbus-proxy`); nested sandboxing: браузеры со своим seccomp требуют разрешить syscall `seccomp`, иначе не смогут применить собственные ограничения.
|
||
- **Установка на Ubuntu 24.04+**: `apparmor_restrict_unprivileged_userns=1` блокирует unprivileged userns → нужен AppArmor-профиль `profile bwrap /usr/bin/bwrap flags=(unconfined) { userns }`.
|
||
- Ссылка: https://github.com/containers/bubblewrap
|
||
|
||
---
|
||
|
||
# Доступ к хосту с ограниченным набором команд (изоляция машины инженера)
|
||
|
||
## Паттерн A: SSH forced command (`authorized_keys command=`)
|
||
|
||
- В `~/.ssh/authorized_keys` перед ключом — опции: `command="..."` заставляет **sshd игнорировать любую команду/шелл клиента и выполнить серверную команду**. Формат: `options keytype base64 comment`.
|
||
- В паре с **`restrict`** (новый агрегат) — отключает PTY, agent forwarding, X11 forwarding, TCP forwarding и user rc processing **одним словом**. Эквивалент набора: `no-pty, no-port-forwarding, no-agent-forwarding, no-X11-forwarding, no-user-rc`.
|
||
- Дополнительно: `from="IP"` — ограничение источника; `environment="..."` (только если `PermitUserEnvironment`); `permitopen`/`permitlisten` для точечного туннелирования.
|
||
- **Wrapper-паттерн**: `command="/usr/local/bin/agent-wrapper.sh"` — скрипт читает `$SSH_ORIGINAL_COMMAND`, валидирует по **whitelist** допустимых команд, и только затем исполняет через `exec`. Это и есть command-whitelist wrapper: агент шлёт `ssh host "git status"`, wrapper сверяет с allowlist, остальное → reject + лог.
|
||
- Реальный пример в природе: **`git-shell`** — логин-шелл только для git-операций; тот же принцип в GitHub/GitLab-деплой-ключах.
|
||
- Отдельно можно задать **`ForceCommand`** в `sshd_config` (глобально/по Match-блоку) — серверная сторона, независимая от клиента.
|
||
- Ссылки: https://man.openbsd.org/sshd.8 , https://www.simplified.guide/ssh/restrict-authorized-keys-command , https://superuser.com/questions/299927/
|
||
|
||
## Паттерн B: bastion / jump host (граница «хост-инженера ↔ агент»)
|
||
|
||
- Отдельный **bastion-пользователь** (`agentrunner`) на отдельном хосте/VM: агент аутентифицируется ключом **только** к bastion, не к машине инженера. С бастиона — либо `ProxyJump`, либо жёсткий wrapper к целевому хосту.
|
||
- `ssh -J bastion target` / `ProxyJump` в `~/.ssh/config` — единая точка аудита и rate-limit; удобно логировать весь трафик агента.
|
||
- Bastion в **непривилегированном** аккаунте, `rbash`/restricted shell, chroot-джейл (см. паттерн A + `sshd_config Match`), все forward'ы запрещены кроме разрешённых.
|
||
- Машина инженера полностью закрыта снаружи; даже скомпрометированный ключ агента ведёт лишь на узкий bastion с whitelist.
|
||
- Ссылка: https://man.openbsd.org/ssh.1 (раздел про `-J`, port forwarding, agent forwarding)
|
||
|
||
## Паттерн C: restricted shells (rbash) — как дополнение, не как основная защита
|
||
|
||
- `rbash`/`bash --restricted`: нельзя `cd`, менять `$PATH`/`$SHELL`, использовать `/` в именах команд, редиректы, `exec`. Работает только в связке с корректно настроенным `PATH` и неизменяемыми переменными.
|
||
- ⚠️ `rbash` **легко обходится** (интерпретаторы, `vi`, `less`, `awk`, `find -exec`, git hooks) — общеизвестен как слабая защита. Использовать **только как второй слой**; основной — forced command + wrapper + опционально контейнер/VM.
|
||
- Ссылка: https://man.openbsd.org/sshd.8 (login process), https://oneuptime.com/blog/post/2026-03-02-how-to-restrict-user-ssh-access-to-specific-commands-on-ubuntu/view
|
||
|
||
---
|
||
|
||
# Docker socket proxy: как дать `docker build` без доступа к `docker.sock`
|
||
|
||
## Tecnativa/docker-socket-proxy
|
||
|
||
- **Проблема**: дать сервису доступ к `/var/run/docker.sock` = дать **root на хосте** (даже на весь swarm). Прокси блокирует то, что сервису не нужно, возвращая **HTTP 403 Forbidden**.
|
||
- Реализация: официальный Alpine-based **HAProxy** + маленький конфиг; **plain HTTP proxy** к хосту-unix-socket (TLS нет by design — ограничивать доступ сетью/Docker firewall).
|
||
- Управление — **env-переменные по URL-префиксу** (напр. `AUTH` → блокирует `/auth/*`), значения **`0`=revoke, `1`=grant**:
|
||
- Разрешены по умолчанию: `EVENTS`, `PING`, `VERSION` (почти безвредны).
|
||
- **Запрещены по умолчанию (критично)**: `AUTH`, `SECRETS`, **`POST`** (при `POST=0` разрешены только GET/HEAD → весь API read-only).
|
||
- Прочее по умолчанию off: `BUILD`, `CONTAINERS`, `EXEC`, `IMAGES`, `NETWORKS`, `VOLUMES`, `ALLOW_START/STOP/RESTARTS/PAUSE/UNPAUSE`, `COMMIT`, `CONFIGS`, `DISTRIBUTION`, `GRPC`, `INFO`, `NODES`, `PLUGINS`, `SERVICES`, `SESSION`, `SWARM`, `SYSTEM`, `TASKS`.
|
||
- **Для `docker build`**: включить `IMAGES=1` и `BUILD=1` (или положиться на BuildKit-эндпоинты), но `POST` нужен разрешённым — иначе build не пройдёт. Точечно: НЕ давать `EXEC` (иначе агент влезет в чужой контейнер), `SECRETS`, `AUTH`.
|
||
- Запуск: `docker run -d --privileged --name dockerproxy -v /var/run/docker.sock:/var/run/docker.sock -p 127.0.0.1:2375:2375 tecnativa/docker-socket-proxy` → клиент через `DOCKER_HOST=tcp://localhost:2375`.
|
||
- **Обязательно**: не публиковать порт в публичную сеть — только во внутренний Docker network с прокси и потребителем. Использовать GitHub Container Registry образ, а не Docker Hub (остальные теги deprecated).
|
||
- **Более сильные альтернативы для build**: **rootless docker**, **BuildKit без root** (`buildkitd` как непривилегированный демон, `buildctl`), **kaniko**/**buildah** (сборка образа без демона вообще), **sysbox**, **Podman** с user-namespaced rootless containers. Для «агенту надо собрать образ» лучше не давать docker.sock вообще, а дать rootless `buildah`/`kaniko` или удалённый build-сервис.
|
||
- Ссылка: https://github.com/tecnativa/docker-socket-proxy
|
||
|
||
---
|
||
|
||
# Сводка: как собрать «сборка/тесты — да, чужие файлы/секреты — нет»
|
||
|
||
| Слой | Механизм | Инструмент |
|
||
|---|---|---|
|
||
| Граница железной изоляции | microVM | Firecracker (+jailer), Fly Machines |
|
||
| Сильная граница, но дешевле VM | userspace kernel | **gVisor** (`runsc`) |
|
||
| Unprivileged ФС-песочница | user/mount/pid/net namespaces + `NO_NEW_PRIVS` | **bubblewrap** |
|
||
| ФС-политика в ядре | Landlock / seccomp-bpf | Chromium, Codex (legacy) |
|
||
| macOS-граница | Seatbelt / sandbox-exec | Claude Code, Codex, Chromium |
|
||
| Ограничение syscall | seccomp-bpf | Chromium, Firecracker VMM, Codex net-filter |
|
||
| Непривилегированный юзер | uid/gid drop, rootless | jailer `--uid/--gid`, rootless docker |
|
||
| Эфемерность | timeout + авто-kill + снапшоты | E2B, Modal, Daytona, Fly |
|
||
| Секреты | доступ только в setup-фазе, удаляются до agent phase | **Codex two-phase** |
|
||
| Доступ к хосту | forced command + whitelist wrapper + bastion | `authorized_keys command=`/`restrict`, `ForceCommand`, `-J` |
|
||
| Docker без docker.sock | API-прокси с точечными правами | tecnativa/docker-socket-proxy |
|
||
|
||
**Рекомендуемый минимум для агента-сборщика**: непривилегированный отдельный пользователь → bubblewrap/gVisor sandbox с read-only корнем и одним writable workspace → `--unshare-net`/прокси для сети → секреты только на этапе setup → отдельный bastion-пользователь с forced command + whitelist wrapper для доступа к хосту инженера → docker build через socket-proxy или (лучше) rootless BuildKit/buildah/kaniko.
|