소개
기여자는 코드 변경 제안에 제목과 설명을 작성합니다. 빌드 작업은 이 메타데이터로 미리보기 산출물을 만듭니다. **지속적 통합(CI)**은 변경이 들어오면 이런 작업을 자동 실행하는 방식입니다. **실행기(runner)**는 작업을 수행하는 프로그램입니다. 여기서는 준비된 작은 로컬 실행기를 직접 시작합니다.
인물과 이벤트는 모두 합성 데이터입니다. 실행기는 네트워크와 배포 자격 증명 없이 일회용 컨테이너에서 실제 Bash를 실행합니다. release.conf의 production은 로컬 테스트 값이며 실제 배포를 일으키지 않습니다. 이 실습은 텍스트 치환과 셸 실행을 모델링하며 호스팅 CI의 트리거나 권한은 재현하지 않습니다. CI Workshop 터미널은 유지보수 작업 공간입니다. Topology는 흐름을 설명하며 새로 고침하면 실제 실행 채널을 표시합니다.
정상 빌드를 실행하고 스크립트 인젝션을 추적한 뒤 짧은 파일 두 개를 편집하고 메타데이터 보존을 다시 검사합니다. 기본적인 터미널 사용 경험이면 충분하며 필요한 JSON과 Bash 문법은 사용 전에 설명합니다.
정상 빌드 실행
이 단계에서는 문제를 조사하기 전에 기대되는 업무 결과를 확인합니다. 기여자의 제목이 그대로 산출물이 되고 릴리스 채널은 preview로 유지되어야 합니다.
이 단계를 시작하려면 실습 인터페이스에서 CI Workshop 탭을 선택합니다.
CI Workshop 탭으로 전환합니다. pwd는 현재 디렉터리를, ls는 파일 목록을 표시합니다. 작업 공간은 /home/labex/project/ci-workshop입니다.
다음 명령을 실행하기 전에 CI Workshop 탭으로 전환합니다.
pwd
ls
cat은 텍스트 파일을 표시합니다. JSON 객체는 이름 있는 필드를 중괄호로 감싸며 콜론이 필드 이름과 값을 연결합니다. 문자열은 큰따옴표를 사용합니다. 문자열 안의 \n은 줄바꿈을 나타냅니다. 정상 이벤트를 읽으세요.
cat events/ordinary.json
title은 Improve search results이며 body는 여러 줄 설명입니다. 기여자가 제공한 데이터이므로 빌드가 명령으로 신뢰해서는 안 됩니다.
짧은 작업 정의와 스크립트를 읽습니다.
cat job.json
cat build.sh
| 항목 | 이 로컬 실행기에서의 의미 |
|---|---|
script |
실행할 Bash 파일 이름. 현재는 build.sh |
env |
프로세스 환경 이름을 문자열에 매핑. 현재 빈 {} |
{{event.title}} |
Bash 시작 전에 이 정확한 자리표시자를 이벤트 제목으로 치환 |
{{event.body}} |
설명 필드에 대한 같은 치환 |
artifact.txt |
빌드가 생성하는 메타데이터 파일 |
release.conf |
실행마다 preview로 초기화되는 채널 |
Bash는 셸 명령을 읽습니다. TITLE="..."은 변수 대입이며 = 양옆에 공백을 넣지 않습니다. "$TITLE"은 값을 하나의 인수로 확장해 공백과 줄바꿈을 보존합니다. printf '%s'는 줄바꿈을 추가하지 않고 문자열을 출력하며 작은따옴표가 %s를 문자 그대로 유지합니다. >는 출력을 파일로 보내 기존 내용을 덮어씁니다. 세미콜론은 명령 두 개를 구분합니다. set -euo pipefail은 일반적인 명령 실패, 미정의 변수, 파이프라인 실패에서 스크립트를 중단하지만 인젝션을 막지는 않습니다.
ci는 제공된 로컬 실행기 명령입니다. run에는 .json을 뺀 이벤트 이름과 새로운 출력 디렉터리 이름을 줍니다. 매 실행은 새 preview 설정으로 시작합니다. 정상 이벤트를 실행합니다.
ci run ordinary first
channel=preview와 exit=0이 예상됩니다. 종료 코드 0은 Bash가 성공적으로 끝났다는 뜻입니다. show로 생성된 스크립트, 산출물, 설정을 살펴봅니다.
ci show first
산출물은 JSON 문자열 "Improve search results"로 표시되어 나중에 보이지 않는 줄바꿈도 표현할 수 있습니다. 이것은 실제 파일 내용이며 가상의 성공 표시가 아닙니다. 마지막 부분에는 preview가 보여야 합니다. Topology 탭으로 전환하고 새로 고칩니다. first 행은 같은 설정 파일을 읽습니다.
first로 run을 반복하면 기존 디렉터리 오류가 발생합니다. 이전 증거를 덮어쓰지 않기 위한 동작입니다. first-again 같은 새 이름을 선택하세요. 실패한 실행도 진단을 위해 runs/NAME에 생성 스크립트와 build.log를 남깁니다.
위험한 치환 추적
이 단계에서는 기여자 데이터가 실행 코드로 변해 격리된 시연 실행의 채널을 바꾸는 과정을 관찰합니다.
이 단계를 시작하려면 실습 인터페이스에서 CI Workshop 탭을 선택합니다.
흐름은 이벤트 → 작업 → 생성된 셸 소스 → Bash → 산출물입니다. 위험한 시점은 Bash가 따옴표를 해석하기 전 치환입니다. 큰따옴표는 이미 해석된 명령 안의 변수 확장을 보호하지만, 해석 전에 소스에 삽입되는 원시 값은 보호하지 못합니다.
제공된 무해한 시연 이벤트를 읽습니다.
다음 명령을 실행하기 전에 CI Workshop 탭으로 전환합니다.
cat events/hostile.json
title에는 큰따옴표, 세미콜론, printf, 또 다른 세미콜론과 #이 있습니다. Bash에서 따옴표는 원래 문자열을 끝내고, 세미콜론은 새 명령을 시작하며, #은 줄 끝까지 주석 처리합니다. 이 printf는 해당 실행의 합성 release.conf만 변경합니다. JSON의 \"는 문자열 안의 따옴표를 인코딩할 뿐, 나중에 셸 소스에 삽입될 때 안전하게 만들지 않습니다.
새 작업 공간에서 이벤트를 실행합니다.
ci run hostile demo
ci show demo
실행기가 preview로 초기화했는데도 channel=production이 나타납니다. generated.sh의 세미콜론 사이에 삽입된 printf를 찾으세요. Bash가 실제로 실행했습니다. 산출물에는 Update만 남을 수 있습니다. 나머지가 제목 데이터가 아닌 문법이 되었기 때문이며, 이것이 수정할 결함입니다.
demo는 first와 별개이며 어느 실행도 job.json이나 build.sh를 변경하지 않습니다. Topology 탭으로 전환하고 페이지를 새로 고쳐 실제 채널을 비교하세요. production 행은 변경된 테스트 설정이지 실제 릴리스가 아닙니다. 이벤트 텍스트를 유지보수 셸에서 직접 실행하지 마세요.

일반적인 위험은 GitHub의 스크립트 인젝션 설명에 나옵니다. 이 자리표시자와 작업 형식은 의도적으로 GitHub Actions보다 작습니다. 공통된 원리는 해석보다 치환이 먼저 일어난다는 것입니다.
값을 데이터로 전달
이 단계에서는 기여자 텍스트를 셸 소스에서 분리하고 공백, 따옴표, 여러 줄을 보존합니다.
이 단계를 시작하려면 실습 인터페이스에서 CI Workshop 탭을 선택합니다.
환경 변수는 새 프로세스에 주는 이름 있는 문자열입니다. 실행기의 env 매핑은 셸 대입문을 생성하지 않고 직접 값을 설정합니다. JSON에서 매핑은 중괄호 안의 다른 객체입니다. BUILD_TITLE을 {{event.title}}에서 설정하고, 실행 시 Bash가 "$BUILD_TITLE"을 확장하게 합니다. Bash는 확장된 값의 따옴표, 세미콜론, $(), 백틱을 새 명령으로 다시 해석하지 않습니다. eval을 쓰거나 값을 bash -c로 넘기면 다시 해석되므로 사용하지 마세요.
here-document로 짧은 두 파일을 교체합니다. cat > filename <<'JSON'은 JSON만 있는 줄까지 다음 줄들을 파일에 씁니다. 구분자에 따옴표를 붙이면 유지보수 셸이 텍스트의 $ 등을 확장하지 않습니다. 마지막 구분자를 포함해 코드 블록 전체를 복사하세요. 파일 전체를 교체하므로 오타 복구에도 사용할 수 있습니다.
다음 명령을 실행하기 전에 CI Workshop 탭으로 전환합니다.
cat > job.json <<'JSON'
{
"script": "build.sh",
"env": {
"BUILD_TITLE": "{{event.title}}"
}
}
JSON
이제 스크립트를 교체합니다. BASH 구분자도 문자 그대로 기록하는 역할입니다. 스크립트는 환경 변수를 직접 사용하며 기여자 자리표시자를 포함하지 않습니다.
cat > build.sh <<'BASH'
set -euo pipefail
printf '%s' "$BUILD_TITLE" > artifact.txt
printf 'channel='; cat release.conf
BASH
배운 cat으로 두 파일을 다시 읽으세요. 쉼표나 중괄호가 빠지면 JSON 오류, 환경 이름이 틀리면 미정의 변수 오류가 발생합니다. 해당 블록 전체를 다시 쓰고 재시도하세요. 기존 실행은 이전 스크립트의 증거이므로 새 이름을 사용합니다.
ci run ordinary repaired
ci show repaired
생성된 소스에는 제목의 문자 대신 "$BUILD_TITLE"이 있습니다. 산출물은 여전히 Improve search results이고 채널은 preview입니다.
검사 전에 문장 부호와 여러 줄 샘플을 읽으세요.
cat events/quotes.json
cat events/multiline.json
JSON 문자열의 \n은 실제 줄바꿈이며 마지막 \n도 보존해야 합니다. O'Brien의 아포스트로피와 제목 안의 큰따옴표는 정상 데이터입니다. 같은 매핑으로 여러 줄 body를 전달할 수 있습니다. 필드 참조만 {{event.body}}로 바뀌고 변수를 따옴표로 감싸는 규칙은 같습니다. 챌린지의 릴리스 노트에서 재사용할 방법입니다.
ci test normal은 제출 작업과 스크립트를 수정하지 않고 정상, 문장 부호, 여러 줄 이벤트로 일회용 임시 디렉터리에서 실행합니다. 산출물 바이트를 정확히 비교하고 preview를 확인합니다. 임시 디렉터리만 삭제하며 제출 파일과 저장된 실행은 보존합니다.
ci test normal
각 샘플에 exact metadata; channel=preview가 나와야 합니다. 문장 부호 제거, 줄 합치기, 제목 고정, 모든 이벤트 거부, 빌드 생략은 정상 기능 검사에 실패합니다. 오류는 샘플 이름을 알려줍니다. 해당 파일을 읽고 두 파일을 수정한 뒤 재시도하세요.
빌드 경계 재검사
이 단계에서는 같은 수정된 작업이 정상 메타데이터를 보존하고 시연한 부작용을 차단하는지 확인합니다.
이 단계를 시작하려면 실습 인터페이스에서 CI Workshop 탭을 선택합니다.
새 출력 이름으로 악성 샘플을 다시 실행합니다.
다음 명령을 실행하기 전에 CI Workshop 탭으로 전환합니다.
ci run hostile repaired-hostile
ci show repaired-hostile
이제 산출물에는 악성 제목 전체가 텍스트로 있어야 합니다. JSON 문자열 표시에 세미콜론과 따옴표가 보이고 release.conf는 preview로 유지됩니다. 이전 demo 증거는 다른 작업 공간에 속하므로 바뀌지 않습니다.
다른 샘플에는 $()와 백틱이 있습니다. 셸 소스에서 이 둘은 명령 치환 형식이지만, 큰따옴표로 확장한 환경 값 안에서는 데이터로 남습니다. 샘플을 읽으세요.
cat events/substitution.json
같은 제출 파일로 다섯 샘플을 모두 실행합니다.
ci test all
ordinary, quotes, multiline, hostile, substitution 모두 정확한 메타데이터와 preview를 보존하고 마지막에는 임시 디렉터리 정리 메시지가 나와야 합니다. 검사는 실제 Bash 실행과 출력 파일을 확인하며 로그 문구나 가상의 secure=true를 채점하지 않습니다. 완료 검사는 작업, 스크립트, 저장된 실행을 수정하지 않고 같은 임시 테스트를 반복합니다.
악성 샘플만 실패하면 셸 파일에 이벤트 자리표시자가 남았는지, 변수를 다시 해석하는지 확인하세요. 문장 부호나 여러 줄이 실패하면 큰따옴표로 감싼 변수를 printf '%s'로 출력하는지 확인하세요. 까다로운 입력을 삭제하지 마세요. 정상 메타데이터 보존도 목표입니다. Topology 탭으로 전환하고 페이지를 새로 고쳐 repaired-hostile과 과거 demo를 비교하세요.

요약
기여자 메타데이터가 로컬 실행기를 거쳐 실제 Bash로 들어가는 경로를 추적하고 격리 실행에서 설정 변경을 재현했습니다. 환경 매핑과 따옴표 확장으로 데이터 경계를 수정했으며 정확한 출력 검사로 문장 부호, 여러 줄, 미리보기 채널을 보존했습니다. 다음에는 같은 방법을 릴리스 노트 작업에 적용합니다.



