Self-hosted runner без изоляции workspace — CI/CD открыт: ловушки безопасности

Базовый уровень 2026: one job, one workspace — отраслевой стандарт

Cloud Mac AI Stack · L1  ·  2026.06.09  ·  ~11 мин  ·  runbook · конфиги для копирования

Изоляция безопасности CI/CD self-hosted runner и базовый уровень one job, one workspace

Многие команды вешают self-hosted GitHub Runner на Cloud Mac или Mac mini и думают, что «нет queue, xcodebuild работает» достаточно — статья про queue и TCO закрывает этот слой.

Настоящая ловушка — следующий слой: общий workspace. Общий DerivedData, глобальные кэши зависимостей, один PAT для CI и Agent — пока workflow трогают только мейнтейнеры, это экономия минут. С fork PR, вредоносными post step и автоправкой .github/workflows через Claude Code / OpenHands self-hosted runner без изоляции открыт: секреты job A читает job B.

Отраслевой минимум 2026 совпадает здесь: one job, one workspace — эксклюзивный каталог на job, очистка при выходе, плюс ротация токенов. Это Cloud Mac AI Stack · L1 часть 3 (пререквизиты: ① движок выполнения · ② queue и компромиссы): откуда ловушка CI/CD и runbook для копирования. Индекс серии в § серия L1.

Перед чтением · серия L1 и вход в Stack

Фундамент L0: купить vs арендовать Cloud Mac · перенести AI-рабочую станцию в облако

Серия L1 (читать по порядку): ① движок выполнения② queue и TCO③ эта статья · безопасность CI/CD и one job, one workspace

Часто в том же stack (L3–L5): workflow Claude Code · настройка MCP · параллельное планирование Ollama · пайплайн OpenClaw

Сравнение: старая конфигурация vs базовый уровень 2026 · где прячутся ловушки

При переходе с hosted macos-latest на self-hosted многие сохраняют только «нет queue» (см. L1 ②) и не наследуют hosted «новый диск на job». Таблица и диаграммы ниже отмечают, что казалось «удобным», а сейчас выглядит «открытым».

Обязательно · ключевое сравнение

Вчерашнее удобство = сегодняшняя уязвимость — семь строк сразу

Старая распространённая практика Отраслевой базис 2026 Последствие ловушки

Измерение Старая распространённая практика (около 2024) Отраслевой базис 2026 Последствие ловушки
Рабочий каталог job Несколько job делят _work, run dirs не удаляются one job, one workspace, очищается при завершении job Job B читает .env job A, внедрённые скрипты
Кэш сборки Общий DerivedData / глобальные ключи actions/cache Ключи cache с repo-scope + периодический prune Отравление кэша; fork PR сканирует глобальный кэш
Учётные данные Один PAT для CI + Agent; secrets ротируют, диск не стирают Раздельные токены CI / MCP; ротация 30–90 дн. + wipe workspace Копии PAT на диске остаются валидными
Материалы подписи Временный keychain в $HOME, без post delete Keychain scope job, уничтожается с if: always() Следующий job или вредоносный step экспортирует сертификаты
Кто правит workflows По умолчанию: только 2–3 мейнтейнера Мейнтейнеры + fork PR + Agent правит CI Поверхность атаки растёт от людей к полуавтономным процессам
Сегментация runner Один Mac на все repo и PR Prod / staging / fork используют разные labels Low-trust workflow касается prod signing env
Миф hosted vs self-hosted «self-hosted = моя машина, оптимизировать скорость» self-hosted = вы сами рисуете границу безопасности (hosted VM изолирует автоматически) Думали, self-hosted безопаснее; на деле более открыт

Почему терпели старый способ? Приватный repo, нет fork PR, ежедневный CI только внутренний — общий диск экономил 3–8 минут. Почему в 2026 надо менять? На том же Cloud Mac часто крутятся Agent, MCP и job нескольких repo — любая старая привычка превращает удобство в инцидент безопасности.

Схема · раньше: утечки общего workspace (между job)
  Тот же self-hosted runner (тот же пользователь macOS)

  Job A · nightly подпись
       │
       ├─► распаковка *.mobileprovision, временный keychain
       ├─► пишет в ~/Library/Developer/Xcode/DerivedData   ← казалось «скорость»
       └─► PAT в ~/.netrc (ленивый скрипт)                ← казалось «удобство»
                │
                │  не очищено · остатки _work и кэша  Job B · дневной fork PR «правка доков»
       │
       └─► post step сканирует _work / DerivedData / .netrc  ← ловушка: читает остатки job A

       Итог: self-hosted runner открыт (hosted macos-latest выбрасывает всю VM на job)
Схема · сейчас: one job, one workspace (базовый уровень 2026)
  Job A  ──►  workspace A (_work/.../run-id)  ──►  очистка if: always()  ──►  ✓
  Job B  ──►  workspace B (свежий каталог)    ──►  очистка if: always()  ──►  ✓
                │
                ├─ чувствительный кэш: ключи cache с префиксом repo или cron prune
                ├─ PAT: раздельные токены CI и MCP, плановая ротация
                └─ high-trust / low-trust job: разные runner labels (второй Cloud Mac при необходимости)

       Итог: аудируемый слой Fact; согласуется с «Claude Code производит Diff, Runner производит Fact»

Вкратце · раньше vs сейчас

  • Старая ловушка: self-hosted как «более быстрый сервер» и шарить всё, что можно
  • Базис 2026: self-hosted как «среду выполнения, которую вы дезинфицируете»
  • Главный миф: миграция с macos-latest и перенесли CPU, не перенесли изоляцию

Откуда открытость: три частые ловушки безопасности CI/CD на self-hosted runner

Hosted macos-latest получает свежий диск на job, поэтому при первом self-host многие думают «моя машина, оптимизировать удобство». Реальная цена self-hosted: границу безопасности рисуете вы — скрытый счёт помимо «нет queue» в L1 часть 2.

Три ошибки конфигурации ниже повторяются на Cloud Mac; попали в две — внедряйте one job, one workspace сразу.

Почему 2026 продвигает one job, one workspace повсюду

Не потому что GitHub выпустил новое правило — поверхность атаки выросла: workflow запускают мейнтейнеры, fork PR, supply-chain-скрипты и AI Agent на том же хосте. Три причины, почему «общий диск ради скорости» в 2026 не держится.

1. Межjob-загрязнение: секреты job A читаются в job B

Self-hosted _work не дезинфицируется автоматически между job. Сертификаты подписи из job A, .env от скриптов, .netrc от post step — job B читает через относительные пути или symlinks. В 2026 поверхность шире: Claude Code и OpenHands могут править .github/workflows на той же машине — аудируете не только diff, но и что остаётся на диске при смене CI.

2. Отравление глобального кэша: DerivedData / npm cache как общий backdoor

Шаринг ~/Library/Developer/Xcode/DerivedData или широких ключей actions/cache может работать в закрытом внутреннем repo; с fork PR вредоносный post сканирует глобальный кэш — hosted VM уничтожаются, self-hosted без cleanup — постоянная поверхность атаки. Типичный iOS-провал: nightly signing job и дневной PR «правка доков» на одном runner.

3. Долгоживущий PAT на диске: ротации secrets в UI недостаточно

Во многих Cloud Mac stack один GitHub PAT служит и доступу repo MCP, и push артефактов Runner. Утечка на стороне Agent валит и CI. Ротация secrets в UI GitHub без wipe workspace — как сменить цилиндр, но оставить копии на диске.

Роли Stack · слой Fact не может работать открыто

Слоган серии (из открытия L1): Claude Code производит Diff; GitHub Runner производит Fact. Красивые diff бессмысленны, если Fact зелёный в грязном workspace. L4 MCP least privilege управляет токенами Agent; L1 здесь — диском и границами job — оба слоя, не или-или.

3
частые ловушки безопасности CI/CD
1
граница workspace на уровне job
2026
год базиса

Выровнять модель: что GitHub Actions оставляет на runner

Многие отождествляют «workspace» с checkout git-деревом — это лишь часть. После macOS job на диске может остаться:

Путь / объект Типичное содержимое Риск без очистки
_work/<repo>/<run-id>/ checkout, артефакты сборки, вывод тестов следующий job читает сгенерированные файлы, вредоносные скрипты вне source
~/.npm, ~/Library/Caches кэши зависимостей и инструментов отравление кэша, путаница зависимостей между repo
DerivedData, .swiftpm кэш сборки Xcode / Swift утечка символов, встроенная устаревшая конфигурация подписи
Временный keychain, *.mobileprovision материалы подписи высокий риск: следующий job или вредоносный step экспортирует certs
файлы injection env, .netrc учётные данные, записанные CI-скриптами PAT в открытом виде сохраняется

Hosted runner выбрасывают весь диск при завершении job; self-hosted — нет. На Agent stack хуже: файлы сессий Claude Code, веса Ollama и _work Runner могут делить один user home (планирование на одной машине в L2 параллельное планирование) — one job, one workspace значит: в начале считать среду загрязнённой Agent или другим job, в конце оставлять только слои, которые должны пересекать job (обычно никакие).

Отраслевой базис на практике: что значит one job, one workspace

Четыре слоя, от простого к сложному:

  1. Изоляция каталогов: каждый job использует свой RUNNER_TEMP / run dir; запрет скриптам писать в /tmp/shared или «командные общие» папки вне repo.
  2. Граница процессов: один процесс runner может выполнять job последовательно, но не должен делить неочищенное глобальное состояние между job (напр. API keys в ~/.zshrc).
  3. Граница учётных данных: временные keychain подписи удалять в post job; secrets только через env, не на диск — если диск нужен, путь в run dir и удаление с job.
  4. Граница ops: high-risk repo получают выделенные runner только для доверенных workflow (разделение labels), физически отдельно от runner fork PR — на Cloud Mac обычно второй узел, а не ставка на один Mac, где скрипты очистки никогда не упадут.

Связь с ephemeral runner

Ephemeral self-hosted runner GitHub Enterprise завершаются после каждого job и стартуют заново — автоматизируя one job, one workspace. При постоянном runner (типично на Cloud Mac) похожий эффект через скрипты + соглашения workflow.

Ротация токенов: почему стирания каталогов недостаточно

Очистка workspace убирает остатки файлов; ротация токенов гарантирует, что копии истекут даже при эксфильтрации. Ротируйте минимум три типа учётных данных:

  • Токен регистрации runner: удалить и перерегистрировать runner (или ротировать по политике org), чтобы устаревшая регистрация на старых машинах не использовалась.
  • GitHub PAT / App для CI: минимальные scopes (чтение repo vs запись packages), отдельно от политики PAT MCP — избегать одного токена для Agent + CI.
  • Подпись Apple и API keys третьих сторон: краткоживущие credentials или injection из secrets на job; не писать в plist home runner.

Нет серебряной пули по частоте: приватный repo, нет fork PR, workflow правят только мейнтейнеры — 90 дней часто хватает; с открытыми контрибьюторами или Agent, автосабмитящим workflow, сократите до 30 дней и после инцидента сразу удалите и перерегистрируйте runner.

Runbook: встроить one job, one workspace в workflow

Вставьте эти сниппеты в существующие пайплайны, чтобы сделать базис аудируемым. Предполагается macOS self-hosted runner с layout _work по умолчанию. Если тот же Cloud Mac крутит Claude Code, предпочтите отдельного пользователя macOS для Runner. Подключение MCP: гайд настройки.

workflow · очистка в конце job (пример)
# фрагмент .github/workflows/ios-ci.yml
jobs:
  build:
    runs-on: [self-hosted, macos, cloud-mac]
    steps:
      - uses: actions/checkout@v4
      - name: Build iOS
        run: xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' build
      - name: Scrub workspace (always)
        if: always()
        run: |
          rm -rf "$RUNNER_TEMP"/*
          rm -rf "$GITHUB_WORKSPACE"/build
          security delete-keychain ci_temp.keychain-db 2>/dev/null || true
#!/usr/bin/env bash
# /usr/local/bin/runner-prune-work.sh · ежедневно 03:00 cron
set -euo pipefail
WORK_ROOT="${HOME}/actions-runner/_work"
# Удалить run dirs старше 48ч (mtime)
find "$WORK_ROOT" -mindepth 3 -maxdepth 3 -type d -mtime +2 -exec rm -rf {} +
# Опционально: prune записей DerivedData старше 7 дней
find ~/Library/Developer/Xcode/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true

Проверка: в двух последовательных job выведите ls -la "$GITHUB_WORKSPACE/.." и ключевые пути кэша; убедитесь, что второй job не видит маркерные файлы первого (напр. touch /tmp/job-marker-$GITHUB_RUN_ID в post и проверка остатков).

Частая ошибка конфигурации

Использовать actions/cache для кэширования неподписанных сторонних бинарников под глобальным cache key без repo-scope — строит общий слой между job и repo на runner. Либо ужесточите ключи cache и ветки, либо включите каталог кэша в скрипты prune.

Совместное размещение Cloud Mac: граница безопасности Agent + Runner

Типичный Cloud Mac AI Stack: Claude Code (Diff) + Runner (Fact) + опционально Ollama на одном хосте. Экономит queue и git pull, но общие неочищаемые глобальные каталоги означают открытый CI втягивает сессии Agent в blast radius:

  • Разделение пользователей: Runner под пользователем runner, Agent под разработчиком; не смешивать ANTHROPIC_API_KEY и ключи подписи в одном ~/.zshrc.
  • workspace Agent ≠ workspace CI: каталог проекта Claude Code не должен указывать на _work Runner; патчи Agent идут через git, не прямыми записями в деревья кэша CI.
  • Конкуренция памяти ≠ общий диск: память Ollama vs Runner — параллельное планирование; высокий Swap не оправдание продолжать шарить DerivedData.
  • Egress IP и labels: runner с доступом к internal staging не должен принимать fork PR; workflow от Agent сначала на labels с низкими привилегиями, prod runner после человеческого продвижения.

Когда можно отложить (и когда действовать сейчас)

Сценарий Можно отложить? Примечания
Приватный monorepo, только 2–3 мейнтейнера, нет fork PR Краткосрочно да Всё равно monthly manual prune + квартальная ротация токенов
Open-source repo запускает Actions на внешних PR Нет Выделенный runner или возврат к hosted macOS
Claude Code / OpenHands / MCP пишут в repo Нет По умолчанию one job, one workspace; запрет общего чувствительного кэша
Сертификаты подписи расшифровываются в CI Нет Keychain scope job + post delete обязательны

Чеклист перед запуском (для печати)

  • У каждого job есть step очистки if: always() или эквивалентный prune хоста
  • Временный keychain / файлы подписи не попадают в постоянные пути $HOME
  • High-risk repo и low-trust workflow используют разные runner labels
  • Токен регистрации runner и CI PAT имеют календарь ротации (рекомендуется 30–90 дней)
  • Первый workflow PR нового контрибьютора — человеческий review, не прямой удар по prod runner
  • Agent и CI используют разные PAT / App — не один токен для MCP и Runner
  • Сверка с L1 часть 2: ② чинит «медленно»; ③ — «открыт ли self-hosted»

Серия L1 · как связаны слои Stack

Эта статья замыкает линию безопасности L1 (слой Fact): зачем Runner → стоит ли self-hosted → как приземлить базовый уровень 2026 one job, one workspace. Читайте таблицу по порядку; вертикально к L0, горизонтально к L3–L5.

Часть Тема Статус
· Движок выполненияПочему Runner — L1 Cloud Mac AI Stack (Diff → Fact)Опубликовано
· Queue и TCOВремя queue macOS CI · self-hosted vs macos-latestОпубликовано
· эта статьяБезопасность self-hosted runner · базис one job, one workspaceОпубликовано
· Пайплайн OpenClawRunner выполняет steps · OpenClaw оркестрирует триггеры и квитанции (расширение L1)Опубликовано

Вертикальные ссылки Stack (один вход на слой):

После трилогии L1, если Agent и CI делят Cloud Mac, следующий слой обычно решения слоя L3 Diff (почему Claude Code заменяет традиционную IDE — иначе чем vs Cursor и статья workstation) — затем карта end-to-end L6 (планируется).

FAQ

Замедлит ли one job, one workspace CI?
Cold start станет медленнее — поэтому шарили диск. Баланс 2026: удалять только чувствительные артефакты и dirs job; возобновляемый кэш с ключами с префиксом repo, не никогда не prune-ный глобальный DerivedData.

Когда self-hosted runner «открыт»?
Когда несколько job/repo делят материалы подписи, .netrc или широкий глобальный кэш в одном user home без очистки if: always() — особенно с fork PR или workflow, отредактированными Agent.

Может ли MCP least privilege alone исправить общий диск runner?
Нет. MCP least privilege управляет вызовами инструментов Agent; Runner должен управлять файлами на диске. Вредоносные fork workflow обходят MCP и сканируют остатки _work.

Есть ли переключатель GitHub в один клик?
Hosted runner приближаются к «одному клику»; self-hosted нужны post steps workflow, cron хоста и опциональный ephemeral mode. Один флаг actions/checkout не заменяет всю границу.

Как это разделяется с оркестрацией OpenClaw?
OpenClaw управляет порядком триггеров и квитанциями; Runner выполняет steps. Изоляция живёт в workflow и хосте — не предполагайте, что OpenClaw стирает диск (см. L1 ④ · пайплайн OpenClaw).

С чего начать серию L1?
Рекомендуется: ① движок выполнения② queue③ эта статья. Полная таблица в § серия L1.

Трилогия L1 готова · следующий слой Diff

Слой Fact защищён — время для Claude Code

L1 отвечает, где CI выполняет Fact. Далее обычно L3: workflow Claude Code на Cloud Mac и логика замены IDE (не то же, что deep dive vs Cursor).

Читать workflow Claude Code
Cloud Mac Смотреть тарифы M4