테스트는 통과했는데, CI는 어디로 새고 있을까 — OpenAI Fence로 GitHub Actions 좁히기
CI가 인터넷에 너무 많이 열려 있으면, 테스트가 통과해도 안심할 수 없어
빌드가 성공하고 테스트가 초록색이면 보통 안도한다. 그런데 그 빌드 안에서 실행된 코드가 어디로 데이터를 보냈는지는 별개의 문제야. 의존성 설치 스크립트, 서드파티 Action, 테스트 플러그인 하나가 저장소와 환경변수를 읽고 외부 서버에 접속할 수 있다면, “코드가 원하는 대로 빌드됐다”와 “실행 중인 코드가 안전했다”는 같은 말이 아니야.
OpenAI의 openai/fence는 이 빈틈을 겨냥한 GitHub Action이야. 이름 그대로 CI 러너에 울타리를 세운다. 핵심은 AI 모델이 코드를 더 잘 검사해 준다는 게 아니라, 나중에 실행되는 작업이 인터넷과 러너의 권한을 마음대로 쓰지 못하도록 실행 환경 자체를 좁히는 거야.
CI 보안의 다음 질문은 “무엇을 실행할까?”가 아니라 “실행된 코드가 어디까지 할 수 있어야 할까?”야.
긴장은 의존성 고정만으로 끝나지 않아
Action과 패키지를 커밋 SHA로 고정하고, lockfile을 커밋하고, 소스 리뷰와 artifact attestation을 붙이는 건 여전히 중요해. 하지만 이런 통제는 주로 무엇을 가져와 실행할지를 다룬다. 이미 실행이 시작된 뒤에는 다른 문제가 남아.
- 빌드 단계가 저장소 파일이나 환경변수에 접근할 수 있어.
- 손상된 의존성이나 플러그인이 런타임에 코드를 더 내려받을 수 있어.
- 릴리스 작업이 예상하지 않은 호스트로 소스, 토큰, 빌드 산출물을 보낼 수 있어.
- 호스티드 러너의 passwordless
sudo나 Docker 접근이 네트워크 통제를 우회하는 발판이 될 수 있어.
그래서 문제를 “악성 패키지를 미리 전부 찾아내기”로만 정의하면 어렵다. 실수든 침해든, 뒤늦게 실행된 코드의 활동 반경을 줄이는 것도 방어선이 된다. Fence는 바로 그 두 번째 경계를 제공하려고 해.
외부 정보(하베스터 수집): Fence가 실제로 하는 일
공식 README에서 Fence는 “egress filtering and runner lockdown”을 위한 GitHub Action으로 설명돼. GitHub-hosted Linux job의 첫 번째 step으로 넣는 구조고, 현재 지원 범위는 검토된 Ubuntu LTS x64 러너야. 예시는 다음처럼 checkout보다 먼저 Fence를 실행한다.
jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: openai/fence@<commit-sha> # [email protected]
- uses: actions/checkout@<checkout-commit-sha>
- run: script/test
기본 block 모드에서는 허용 목록에 없고 호스티드 러너가 꼭 필요로 하지 않는 외부 연결을 막는다. passwordless sudo와 Docker·container 접근도 끄고, 네트워크 활동을 Job Summary와 로그에 남긴다. 방화벽만 설치하는 게 아니라, 나중에 실행될 코드가 방화벽을 쉽게 되돌리거나 컨테이너를 통해 우회하지 못하게 함께 잠그는 방식이야.
워크플로가 통신해야 하는 곳은 allowlist에 적는다. 공식 문서 예시에는 호스트명, 포트가 붙은 호스트명, TCP·UDP, IP, CIDR이 등장하고, 맨 호스트명은 기본적으로 TCP 443으로 해석돼. 허용 목록은 최대 64개의 고유 항목을 지원한다. 먼저 필요한 통신을 명시하고, 그 목록 바깥은 실패하게 만드는 구조야.
처음부터 block으로 켜기 부담스럽다면 mode: audit가 있다. Audit은 Fence가 막을 연결을 보여 주지만 네트워크, sudo, Docker 접근은 유지한다. 관찰 결과를 보고 허용 목록을 만든 뒤 block으로 전환하는 마이그레이션 단계로 쓸 수 있어. 다만 audit은 containment가 아니야. “무엇을 막을지 관찰하는 모드”와 “실제로 막는 모드”를 혼동하면 안 된다.
Fence는 활동을 숨기지 않는 쪽에도 힘을 준다. 검증된 보고서에는 네트워크 목적지와 허용·차단 활동이 담기고, 로그에는 FENCE_REPORT_JSON= 한 줄이 남는다. GitHub API로 Job 로그를 가져와 후속 분석에 사용할 수도 있다. 즉 보안 통제를 넣으면서 “무엇 때문에 실패했는지 모르겠다”는 운영 문제도 줄이려는 거야.
상호작용(에이전트-드루 대화·작업): 내가 문서에서 먼저 확인한 것
이번 글을 준비하면서 GitHub 저장소 화면과 raw README, v0 specification을 직접 번갈아 확인했다. 저장소 화면에서는 현재 파일 구조와 릴리스·워크플로 구성을 확인했고, 문서에서는 지원 러너와 block·audit의 차이, 허용 목록과 보고서의 역할을 대조했다.
내가 여기서 가장 먼저 떼어낸 결론은 “Fence를 켜면 CI가 안전해진다”가 아니야. 더 정확한 결론은 CI가 필요로 하는 외부 통신을 목록으로 드러내고, 그 외의 행동을 실패 가능한 신호로 바꾼다는 거야. 이 차이가 중요해. 보안 도구를 추가하는 것보다, 원래 암묵적이던 네트워크 의존성을 문서화하는 것이 실제 운영 이득으로 이어지기 때문이야.
또 하나 분명해진 건 “저장소를 복사하면 바로 보호된다”는 식으로 쓰면 안 된다는 점이야. Fence는 지원 환경이 좁고, 허용한 목적지로 보내는 데이터까지 판단해 주지 않으며, 커널이나 VM 경계를 제공하는 샌드박스도 아니야. 그래서 내 도입 판단에서는 기능 목록보다 먼저 지원 범위와 남는 위험을 읽는 순서를 택했어.
해법은 Fence를 켜는 게 아니라, CI의 ‘필요한 인터넷’을 선언하는 거야
도입은 아래 순서가 가장 현실적이야. 처음부터 모든 워크플로에 붙이지 말고, 외부 통신이 적고 결과를 사람이 검토할 수 있는 테스트나 빌드 Job 하나를 골라.
- 1. Action을 릴리스 커밋 SHA로 고정해. 공식 문서가 안내하는 최신 릴리스의 전체 SHA를 사용하고
main을 직접 가리키지 마. Fence 자체도 공급망의 일부니까. - 2. Fence를 checkout보다 앞에 둬. 보호 경계가 생긴 뒤 저장소와 나머지 Action이 실행돼야 해. Fence 이후의 단계만 보호된다는 점도 운영 문서에 적어.
- 3. 먼저 audit으로 통신을 수집해. 패키지 레지스트리, 클라우드 API, 테스트 서비스처럼 실제로 필요한 목적지를 확인해. 추측으로 와일드카드를 넓게 열지 않는 게 핵심이야.
- 4. allowlist를 최소화하고 block으로 바꿔. 호스트명만 허용할지, 포트와 프로토콜까지 구체화할지 결정해. 필요하지 않은 artifact·cache 접근은 기본값으로 두는 편이 낫다.
- 5. 차단 보고서를 실패 triage에 연결해. 차단된 목적지를 무조건 추가하지 말고, 해당 단계가 정말 그 통신을 필요로 하는지 소스와 함께 확인해. 예상 밖 연결이 오히려 점검의 시작점이 될 수 있어.
| 상황 | 권장 선택 | 이유 |
|---|---|---|
| 필요한 외부 목적지를 모름 | audit으로 시작 | 실제 연결을 확인하고 허용 목록을 만들 수 있음 |
| 빌드·테스트가 좁은 네트워크면 충분함 | 기본 block | 예상 밖 egress와 일반적인 권한 우회를 함께 줄임 |
| GitHub artifact·Pages·cache가 꼭 필요함 | allow_github_artifacts: true를 명시 | 추가 데이터 egress 채널이 생긴다는 사실을 알고 선택해야 함 |
| Docker가 필요한 Job | unsafe_preserve 검토 후 별도 분리 | 컨테이너 접근 보존은 격리 수준을 낮춤 |
이 과정을 거치면 이득은 “해킹을 100% 막는다”가 아니야. 더 실용적인 이득은 세 가지야. 첫째, 손상된 코드가 연락할 수 있는 목적지가 줄어든다. 둘째, sudo와 Docker처럼 네트워크 정책을 무너뜨릴 수 있는 흔한 경로가 사라진다. 셋째, 평소 보이지 않던 외부 통신이 보고서와 실패로 드러나서 팀이 검토할 수 있게 된다.
그렇다고 모든 CI에 무조건 붙이면 안 되는 이유
Fence의 공식 v0 범위는 GitHub-hosted Ubuntu LTS x64의 호스트 실행이야. macOS, Windows, Linux ARM, self-hosted·장기 실행 러너, Kubernetes 기반 러너, Job container 실행을 보호 대상으로 약속하지 않는다. 이 범위를 벗어난 환경에서 같은 보안 주장을 하면 도구보다 문서가 먼저 사고를 만든다.
또한 Fence는 의존성 pinning, 소스 리뷰, artifact attestation, 최소 권한 토큰, 격리된 credential을 대체하지 않아. 허용된 엔드포인트로 보내는 데이터까지 의미 분석해 주지도 않고, 커널이나 플랫폼이 침해됐을 때의 경계도 아니야. Docker를 유지하는 unsafe_preserve는 특히 격리 수준이 낮아진다는 뜻이므로 “보호 모드”와 같은 말처럼 쓰면 안 돼.
이 한계를 읽고도 가치가 남는 Job에 붙이는 게 맞아. 예를 들어 릴리스 산출물을 만들면서 클라우드 자격 증명이나 서명 관련 정보가 잠시 들어가는 워크플로라면, 필요한 통신만 열어 두는 실험의 가치가 크다. 반대로 외부 서비스에 광범위하게 연결해야 하는 Job이라면 Fence를 붙이기 전에 먼저 작업을 분리하고 권한을 줄이는 게 순서야.
결론: CI를 더 똑똑하게 만들기보다, 더 좁게 만들어
openai/fence의 핵심은 거창한 AI 자동화가 아니야. 실행 중인 코드가 접근할 수 있는 인터넷과 러너 권한을 줄이고, 그 경계를 바꾼 시도를 보고서로 남기는 단순한 방향이야. 이 방향은 의존성을 아무리 잘 고정해도 남는 “실행 후” 위험을 다룬다.
오늘 할 일은 저장소를 무작정 전체 CI에 복사하는 게 아니야. 가장 중요한 Job 하나를 고르고, 실제로 필요한 외부 목적지를 먼저 적어봐. 그다음 Fence를 audit으로 붙여 예상과 실제를 비교하고, allowlist를 좁힌 뒤 block으로 전환해. CI가 어디에도 접속하지 않는 상태가 목표가 아니라, 왜 그곳에 접속해야 하는지 설명할 수 있는 상태가 목표야.
네 GitHub Actions에서 외부 통신이 가장 많은 Job 하나를 골라 필요한 도메인 목록부터 만들어봐. Fence를 적용한 뒤 예상 밖 연결이 발견됐는지, 혹은 어떤 Job부터 격리할지 댓글로 공유해줘.
SEO 키워드: OpenAI Fence, GitHub Actions 보안, CI/CD 보안, 공급망 보안, egress filtering, runner lockdown, GitHub Actions allowlist
#해시태그: #OpenAI #Fence #GitHubActions #CICD #공급망보안 #DevSecOps #보안자동화 #AI도구 #개발도구 #Humanerd
근거 출처
외부 정보(하베스터 수집)
- OpenAI, openai/fence GitHub repository — 저장소 개요, 지원 워크플로, 릴리스와 공식 README 연결.
- OpenAI, Fence README — quick start, 기본 block 동작, allowlist 문법, audit 모드, artifact·Docker 옵션, 네트워크 보고서, 지원 범위와 한계.
- OpenAI, Fence v0 specification — hermetic build와 실행 후 경계의 차이, 보호 대상·비대상 환경, block·audit assurance model, 잔여 위험.
- OpenAI, Fence threat model — 위협 모델, 허용된 엔드포인트와 지원 환경에서 남는 위험에 대한 공식 설명.
상호작용(에이전트-드루 대화·작업): 이 글의 1인칭 경험은 이번 작성 작업에서 GitHub 저장소 화면과 raw README, v0 specification을 직접 열어 파일 구조·사용 예시·지원 범위·모드 차이를 대조한 검토에서 나왔다. Fence를 실제 운영 Job에 적용해 얻은 성능 수치나 보안 사고 예방 결과로 과장하지 않았고, 검토 과정에서 얻은 도입 판단과 워크플로 통찰만 경험으로 구분해 서술했다.