workspace 격리 없는 self-hosted runner는 사실상 무방비 — CI/CD 보안 함정

2026 기준: one job, one workspace — 이제 업계 표준

Cloud Mac AI Stack · L1  ·  2026.06.09  ·  약 11분  ·  runbook · 복붙 설정 포함

self-hosted runner CI/CD 보안 격리와 one job, one workspace 기준

많은 팀이 Cloud Mac이나 Mac mini에 self-hosted GitHub Runner를 올리고 "queue 없음, xcodebuild 동작"이면 충분하다고 생각합니다 — queue와 TCO 글이 다루는 층입니다.

진짜 함정은 다음 층입니다: 공유 workspace. DerivedData, 전역 의존성 캐시, CI와 Agent 겸용 PAT — workflow를 유지보수자만 건드릴 때는 몇 분 단축에 그칩니다. fork PR, 악의적 post step, Claude Code / OpenHands.github/workflows 자동 편집이 합류하면 격리 없는 self-hosted runner는 무방비입니다: job A의 secret을 job B가 읽습니다.

2026 업계 하한선도 여기에 맞춥니다: one job, one workspace — job별 전용 디렉터리, 종료 시 정리, token 로테이션. 이 글은 Cloud Mac AI Stack · L1 3편(선행: ① 실행 엔진 · ② queue와 트레이드오프): CI/CD 보안 함정의 출처와 복붙 runbook. 시리즈 목차는 § L1 시리즈.

읽기 전 · L1 시리즈와 Stack 진입

L0 기반: Cloud Mac 구매 vs 대여 · AI 워크스테이션 클라우드 이전

L1 시리즈(순서 권장): ① 실행 엔진② queue와 TCO③ 이 글 · CI/CD 보안과 one job, one workspace

같은 Stack에서 자주 등장(L3–L5): Claude Code 워크플로 · MCP 설정 · Ollama 병렬 스케줄링 · OpenClaw 파이프라인

비교: 예전 구성 vs 2026 기준 · 함정은 어디에

Hosted macos-latest에서 self-hosted로 옮길 때 많은 팀이 「queue 없음」만 이어받고(L1 ②) Hosted의 「job마다 새 디스크」는 이어받지 않습니다. 아래 표와 다이어그램은 당시 「편리」했던 것이 지금은 「무방비」로 보이는 지점을 표시합니다.

필독 · 핵심 비교

어제의 편의 = 오늘의 노출 — 7행 한눈에

예전 일반적 관행 2026 업계 기준 함정 결과

관점 예전 일반적 관행(2024 전후) 2026 업계 기준 함정 결과
Job 작업 디렉터리 여러 job이 _work 공유, run 디렉터리 미삭제 one job, one workspace, job 종료 시 정리 Job B가 Job A의 .env, 삽입 스크립트 읽기
빌드 캐시 공유 DerivedData / 전역 actions/cache key repo 범위 cache key + 정기 prune 캐시 오염; fork PR이 전역 캐시 스캔
자격 증명 CI + Agent 겸용 PAT; secrets는 로테하지만 디스크 미삭제 CI / MCP 별 token; 30–90일 로테 + workspace 삭제 디스크 PAT 복사본이 유효 상태 유지
서명 자료 $HOME 임시 keychain, post 삭제 없음 job 범위 keychain, if: always()로 파기 다음 job 또는 악의 step이 인증서 export
workflow 편집 주체 기본: 유지보수자 2–3명만 유지보수자 + fork PR + Agent CI 편집 공격면이 「사람」에서 「반자율 프로세스」로 확대
Runner 세그먼트 Mac 1대가 모든 repo, 모든 PR prod / staging / fork는 별 label 저신뢰 workflow가 prod 서명 환경 접촉
Hosted vs self-hosted 오해 「self-hosted = 내 머신, 속도 최우선」 self-hosted = 보안 경계를 직접 그린다(Hosted VM은 자동 격리) self-hosted가 더 안전하다고 착각; 실제는 더 노출

예전 방식을 참을 수 있었던 이유? 비공개 repo, fork PR 없음, 일일 CI는 내부 job만 — 공유 디스크는 3–8분 단축에 그침. 2026에 바꿔야 하는 이유? 같은 Cloud Mac에서 Agent, MCP, 다중 repo job이 돌아감 — 옛 습관 하나가 「편의」를 보안 사건으로 바꿉니다.

다이어그램 · 이전: 공유 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는 job마다 VM 전체 폐기)
다이어그램 · 현재: one job, one workspace(2026 기준)
  Job A  ──►  workspace A (_work/.../run-id)  ──►  if: always() 정리  ──►  ✓
  Job B  ──►  workspace B(신규 디렉터리)    ──►  if: always() 정리  ──►  ✓
                │
                ├─ 민감 캐시: repo 접두사 cache key 또는 cron prune
                ├─ PAT: CI와 MCP 별 token, 정기 로테이션
                └─ 고신뢰 / 저신뢰 job: 별 runner label(필요 시 두 번째 Cloud Mac)

       결과: 감사 가능한 Fact 층; 「Claude Code가 Diff, Runner가 Fact」와 정합

한눈에 · 예전 vs 현재

  • 예전 함정: self-hosted를 「더 빠른 서버」로 보고 공유 가능한 것은 모두 공유
  • 2026 기준: self-hosted를 「직접 소독해야 하는 실행 환경」으로 봄
  • 최대 오해: macos-latest에서 이전할 때 CPU만 옮기고 격리는 안 옮김

무방비의 시작: self-hosted runner에서 흔한 CI/CD 보안 함정 3가지

Hosted macos-latest는 job마다 새 디스크에 가깝기 때문에 처음 self-host하는 팀은 「내 머신, 편의 우선」으로 오해합니다. self-hosted의 진짜 비용은 보안 경계를 직접 그리는 것L1 2편의 「queue 없음」 밖 숨은 청구서입니다.

아래 세 가지 오설정은 Cloud Mac 현장에서 반복됩니다. 두 가지 해당 시 즉시 one job, one workspace를 도입하세요.

2026이 one job, one workspace를 전면 추진하는 이유

GitHub가 새 규칙을 낸 게 아니라 공격면이 커졌기 때문입니다: workflow는 유지보수자, fork PR, 공급망 스크립트, 동일 호스트 AI Agent에서 실행됩니다. 「디스크 공유로 속도」는 2026에 성립하지 않습니다.

1. job 간 오염: job A secret을 job B가 읽음

self-hosted _work는 job 사이에 자동 소독되지 않습니다. job A에서 풀린 서명 인증서, 스크립트가 쓴 .env, post step의 .netrc — job B는 상대 경로나 심볼릭 링크로 읽을 수 있습니다. 2026에는 Claude CodeOpenHands가 동일 머신에서 .github/workflows 편집 — diff뿐 아니라 CI 변경 시 디스크에 무엇이 남는지도 감사해야 합니다.

2. 전역 캐시 오염: DerivedData / npm 캐시가 공유 백도어

~/Library/Developer/Xcode/DerivedData나 넓은 actions/cache key 공유는 닫힌 내부 repo에서는 될 수 있습니다. fork PR이 오면 악의 post step이 전역 캐시 스캔 — Hosted VM은 파기, 정리 없는 self-hosted는 지속 공격면. 전형적 iOS 실패: nightly 서명 job과 주간 「문서 수정」PR이 동일 runner.

3. 디스크에 남은 장기 PAT: UI secrets 로테만으로는 부족

많은 Cloud Mac Stack에서 GitHub PAT 하나가 MCP repo 접근과 Runner 아티팩트 push에 쓰입니다. Agent 측 유출 시 CI도 함께 무너집니다. GitHub UI에서 secrets만 로테하고 workspace를 지우지 않으면 자물쇠만 바꾸고 디스크 복사본은 남기는 것과 같습니다.

Stack 역할 · Fact 층은 무방비로 돌 수 없음

시리즈 슬로건(L1 서두): Claude Code가 Diff를, GitHub Runner가 Fact를 생산. diff가 예뻐도 Fact가 더러운 workspace에서 green이면 의미 없습니다. L4 MCP 최소 권한은 Agent token, L1 이 글은 디스크와 job 경계 — 둘 다 필요, 하나만으로는 부족.

3
흔한 CI/CD 보안 함정
1
job 수준 workspace 경계
2026
기준 연도

모델 정렬: GitHub Actions가 runner에 남기는 것

많은 이가 「workspace」를 checkout된 git 트리와 동일시하지만 일부일 뿐입니다. macOS job 후 디스크에 남을 수 있는 것:

경로 / 객체 전형적 내용 미정리 시 위험
_work/<repo>/<run-id>/ checkout, 빌드 산출물, 테스트 출력 다음 job이 생성물·소스 밖 악의 스크립트 읽기
~/.npm, ~/Library/Caches 의존성·도구 캐시 캐시 오염, repo 간 의존성 혼동
DerivedData, .swiftpm Xcode / Swift 빌드 캐시 심볼 유출, 구 서명 설정 임베드
임시 keychain, *.mobileprovision 서명 자료 고위험: 다음 job 또는 악의 step이 인증서 export
env 주입 파일, .netrc CI 스크립트가 쓴 자격 증명 평문 PAT 잔존

Hosted runner는 job 종료 시 디스크 전체 폐기; self-hosted는 아님. Agent Stack에서는 더 악화: Claude Code 세션 파일, Ollama 가중치, Runner _work가 동일 사용자 홈 공유(동일 머신 스케줄링은 L2 병렬 스케줄링) — one job, one workspace란: 시작 시 Agent나 다른 job으로 오염됐다고 가정하고, 종료 시 job을 넘는 층은 남기지 않음(보통 없음).

업계 기준 실천: one job, one workspace의 의미

4층, 쉬운 순:

  1. 디렉터리 격리: 각 job은 전용 RUNNER_TEMP / run 디렉터리; /tmp/shared나 repo 밖 「팀 공유」폴더 쓰기 금지.
  2. 프로세스 경계: runner 프로세스 하나가 job을 직렬 실행해도 job 사이 미정리 전역 상태(예: ~/.zshrc에 export한 API key) 공유 금지.
  3. 자격 경계: 서명용 임시 keychain은 job post에서 삭제; secrets는 env만, 디스크 금지 — 디스크 필요 시 run 디렉터리 내에서 job과 함께 삭제.
  4. 운영 경계: 고위험 repo는 신뢰 workflow 전용 runner(label 분리), fork PR runner와 물리 분리 — Cloud Mac에서는 보통 두 번째 노드, 한 대에서 정리 스크립트가 영원히 맞다고 걸지 않음.

ephemeral runner와의 관계

GitHub Enterprise ephemeral self-hosted runner각 job 후 프로세스 종료·새 인스턴스 — one job, one workspace 자동화. 상주 runner(Cloud Mac에서 흔함)면 스크립트 + workflow 규약으로 유사 효과.

token 로테이션: 디렉터리 삭제만으로는 부족

workspace 정리는 파일 잔류 해결; token 로테이션은 유출돼도 복사본 만료. 최소 3종 로테:

  • Runner 등록 token: runner 제거·재등록(또는 조직 정책 로테), 구 머신 등록 남용 방지.
  • CI용 GitHub PAT / App: 최소 scope(repo 읽기 vs packages 쓰기), MCP PAT 정책과 분리 — Agent + CI 겸용 token 금지.
  • Apple 서명·서드파티 API key: 단기 자격 또는 job별 secrets 주입; runner 홈 plist 쓰기 금지.

만능 주기는 없음: 비공개 repo, fork PR 없음, workflow는 유지보수자만 — 90일이면 충분한 경우 많음; 오픈 contributor나 Agent 자동 workflow 제출 시 30일로 단축, 사건 후 즉시 runner 제거·재등록.

Runbook: one job, one workspace를 workflow에 굽기

기존 파이프라인에 아래를 붙여 기준을 감사 가능하게. macOS self-hosted runner, 기본 _work 레이아웃 가정. 같은 Cloud Mac에서 Claude Code도 돌리면 Runner는 전용 macOS 사용자 권장. 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"
# 48h 초과 run 디렉터리 삭제(mtime)
find "$WORK_ROOT" -mindepth 3 -maxdepth 3 -type d -mtime +2 -exec rm -rf {} +
# 선택: 7일 초과 DerivedData 항목 prune
find ~/Library/Developer/Xcode/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true

검증: 연속 2 job에서 ls -la "$GITHUB_WORKSPACE/.."와 주요 캐시 경로 출력; 두 번째 job이 첫 번째 마커 파일을 볼 수 없는지(post에서 touch /tmp/job-marker-$GITHUB_RUN_ID 후 잔류 확인).

흔한 오설정

actions/cache미서명 서드파티 바이너리를 전역 cache key로 캐시하고 repo 범위 없음 — runner에서 job·repo 횡단 공유층 생성. cache key·브랜치를 조이거나 캐시 디렉터리를 prune 스크립트에 포함.

Cloud Mac 동거: Agent + Runner 보안 경계

전형적 Cloud Mac AI Stack: Claude Code(Diff) + Runner(Fact) + 선택 Ollama 동일 호스트. queue와 git pull 절약하지만 지울 수 없는 전역 디렉터리 공유 시 무방비 CI가 Agent 세션까지 끌어들임:

  • 사용자 분리: Runner는 runner 사용자, Agent는 개발자 사용자; ANTHROPIC_API_KEY와 서명 키를 한 ~/.zshrc에 섞지 않음.
  • Agent workspace ≠ CI workspace: Claude Code project dir을 Runner _work에 두지 않음; Agent patch는 git 경유, CI 캐시 트리 직접 쓰기 금지.
  • 메모리 경합 ≠ 디스크 공유: Ollama vs Runner 메모리는 병렬 스케줄링; 높은 Swap도 DerivedData 공유 핑계가 아님.
  • 출구 IP와 label: 내부 staging에 닿는 runner는 fork PR도 받지 않음; Agent 제출 workflow는 먼저 저권한 label, prod runner는 사람 승격 후.

미뤄도 되는 경우(지금 해야 하는 경우)

시나리오 미뤄도 됨? 메모
비공개 monorepo, 유지보수자 2–3명만, fork PR 없음 단기 가능 월간 manual prune + 분기 token 로테 권장
오픈소스가 외부 PR로 Actions 실행 불가 전용 runner 또는 Hosted macOS 복귀
Claude Code / OpenHands / MCP가 repo에 쓰기 불가 기본 one job, one workspace; 민감 캐시 공유 금지
서명 인증서를 CI에서 복호화 불가 job 범위 keychain + post 삭제 필수

출시 전 체크리스트(인쇄용)

  • 모든 job에 if: always() 정리 step 또는 동등 호스트 prune
  • 임시 keychain / 서명 파일을 영구 $HOME 경로에 두지 않음
  • 고위험 repo와 저신뢰 workflow는 별 runner label
  • Runner 등록 token과 CI PAT 로테 캘린더(30–90일 권장)
  • 신규 contributor 첫 workflow PR은 사람 리뷰, prod runner 직격 금지
  • Agent와 CI는 별 PAT / App — MCP와 Runner 겸용 token 금지
  • L1 2편 대조: ②는 「느림」, ③은 「self-hosted 무방비 여부」

L1 시리즈 · Stack 층 연결

이 글은 L1(Fact 층) 보안선을 마무리: Runner가 왜 필요한가 → self-hosted 가치 있는가 → 2026 one job, one workspace 기준 착지. 표 순서로 읽기; 세로 L0, 가로 L3–L5.

주제 상태
· 실행 엔진Runner가 Cloud Mac AI Stack L1인 이유(Diff → Fact)게시됨
· queue와 TCOmacOS CI queue 시간 · self-hosted vs macos-latest게시됨
· 이 글self-hosted runner 보안 · one job, one workspace 기준게시됨
· OpenClaw 파이프라인Runner가 step 실행 · OpenClaw가 트리거·영수증 편성(L1 확장)게시됨

Stack 세로 링크(층당 진입 1개):

L1 3부작 후 Agent와 CI가 같은 Cloud Mac이면 다음은 보통 L3 Diff 층 결정(Claude Code가 전통 IDE를 대체하는 이유 — vs Cursor워크스테이션 글과 역할 분리) — 이후 L6 E2E 지도(예정).

FAQ

one job, one workspace가 CI를 느리게 하나?
콜드 스타트는 느려집니다 — 공유 디스크의 이유입니다. 2026 균형: 민감 산출물과 job dir만 삭제; 재생 가능 캐시는 repo 접두사 key, prune 안 하는 전역 DerivedData 아님.

self-hosted runner가 「무방비」인 경우는?
여러 job/repo가 서명 자료, .netrc, 넓은 전역 캐시를 한 사용자 홈에서 공유하고 if: always() 정리 없음 — 특히 fork PR이나 Agent 편집 workflow 시.

MCP 최소 권한만으로 runner 디스크 공유가 해결되나?
아니요. MCP 최소 권한은 Agent 도구 호출; Runner는 디스크 파일 관할. 악의 fork workflow는 MCP를 우회해 _work 잔류 스캔 가능.

GitHub 원클릭 전환 있나?
Hosted runner는 근사 「원클릭」; self-hosted는 workflow post, 호스트 cron, 선택 ephemeral 조합. actions/checkout 한 플래그로 전체 경계 대체 불가.

OpenClaw 편성 층과 분담은?
OpenClaw는 트리거 순서·영수증; Runner는 step 실행. 격리는 workflow·호스트에 기록 — OpenClaw가 디스크를 지운다고 가정하지 말 것(L1 ④ · OpenClaw 파이프라인).

L1 시리즈는 어디서 시작?
권장: ① 실행 엔진② queue③ 이 글. 전체 표는 § L1 시리즈.

L1 3부작 완료 · 다음 Diff 층

Fact 층 확보 후 Claude Code로

L1은 CI가 어디서 Fact를 도는지. 다음은 보통 L3: Cloud Mac Claude Code 워크플로와 IDE 대체 로직(vs Cursor 심층과 다름).

Claude Code 워크플로 읽기
Cloud Mac M4 요금 보기