도입
composer install이 성공했다고 안전한 배포가 보장되지는 않는다. composer.json과 lock 파일이 어긋날 수 있고, 잠긴 패키지에 보안 권고가 생길 수 있으며, Composer 플러그인은 설치 과정에서 코드를 실행할 수 있다.
이번 글에서는 Composer 2를 기준으로 세 검사를 CI의 배포 게이트로 만든다.
1. 재현용 프로젝트
mkdir composer-gate
cd composer-gate
composer init --name=example/composer-gate --no-interaction
composer require monolog/monolog:^3.0
composer --version
composer.json과 composer.lock은 함께 커밋한다. CI에서는 의존성을 다시 결정하는 update가 아니라 lock 파일을 그대로 설치하는 install을 사용한다.
2. 설정과 lock 불일치 검사
composer validate --strict
validate는 JSON 구조뿐 아니라 lock 파일이 현재 설정과 맞는지 확인한다. --strict는 경고도 0이 아닌 종료 코드로 바꿔 CI가 놓치지 않게 한다.
실패를 재현하려면 composer.json의 요구 버전만 수정하고 lock 파일은 갱신하지 않는다.
{
"require": {
"monolog/monolog": "^2.0"
}
}
다시 composer validate --strict를 실행하면 lock 파일 불일치를 발견한다. 해결은 lock 검사를 끄는 것이 아니라 변경 의도를 확인한 뒤 로컬에서 composer update monolog/monolog --with-all-dependencies를 실행하고 diff를 검토하는 것이다.
3. 잠긴 의존성 감사
composer audit --locked --format=json
--locked는 vendor 디렉터리의 현재 상태가 아니라 배포 기준인 lock 파일을 검사한다. JSON 결과는 보관하거나 CI 요약으로 가공하기 쉽다.
현재 Composer 문서의 종료 코드 정책은 버전에 따라 확장될 수 있으므로 특정 숫자를 직접 해석하기보다 0이면 통과, 0이 아니면 실패로 처리하는 편이 견고하다.
composer audit --locked --format=json > composer-audit.json
보고서를 artifact로 남기되 실패를 숨기지 않으려면 셸의 종료 코드 전파 방식을 확인해야 한다. 간단한 CI 단계에서는 리다이렉션 없이 실행하고, 별도 artifact가 필요할 때는 사용하는 셸에 맞게 종료 코드를 저장한다.
4. 플러그인 허용 목록
Composer 플러그인은 일반 라이브러리와 다르다. Composer가 실행될 때 플러그인 코드도 실행될 수 있으므로 이름을 명시적으로 허용한다.
{
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"*": false
}
}
}
실제 프로젝트에서 사용하지 않는 예시 패키지는 넣지 않는다. 다음 명령으로 프로젝트 설정을 추가할 수도 있다.
composer config allow-plugins.vendor/trusted-plugin true
CI의 비대화식 실행에서 허용 여부가 정해지지 않은 새 플러그인은 실패해야 정상이다. 급하다는 이유로 "allow-plugins": true 또는 "*": true를 적용하면 공급망 경계가 사라진다.
의심스러운 플러그인 때문에 Composer 자체가 실행되지 않을 때 진단 목적으로만 다음 옵션을 사용할 수 있다.
composer install --no-plugins --no-scripts
이는 정상 배포 명령이 아니라 조사용 우회다.
5. CI 배포 게이트
GitHub Actions 예시는 다음과 같다.
name: dependency-gate
on:
pull_request:
push:
branches: [main]
jobs:
composer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
tools: composer:v2
- run: composer validate --strict
- run: composer install --no-interaction --prefer-dist --no-progress
- run: composer audit --locked --format=json
서드파티 Action은 태그 대신 검토한 commit SHA로 고정하면 더 강한 공급망 통제가 가능하다. 이 원고에서는 읽기 편하도록 버전 태그를 사용했다.
실패 사례별 판단
| 실패 | 의미 | 조치 |
|---|---|---|
| validate 실패 | 설정 오류 또는 lock 불일치 | 변경 의도와 lock diff 검토 |
| audit 실패 | 취약점·악성·정책 위반 의존성 | 안전한 버전 조사 및 테스트 |
| 플러그인 거부 | 실행 코드가 허용 목록 밖 | 패키지 출처·코드 검토 후 명시 |
| install 실패 | 플랫폼 또는 lock 재현 실패 | PHP 확장·버전과 lock 확인 |
점검 체크리스트
composer.json과composer.lock을 함께 검토하는가?- CI에서
composer validate --strict를 실행하는가? audit --locked의 비정상 종료를 실패로 처리하는가?allow-plugins에 실제 검토한 패키지만 적었는가?- CI에서
--no-interaction을 사용하는가? - 감사 예외에는 사유와 만료일이 있는가?
함께 읽기
참고 자료
확인일: 2026-07-29
한 줄 요약
validate --strict, audit --locked, 좁은 allow-plugins를 CI에 묶으면 의존성 문제가 배포 뒤가 아니라 변경 시점에 드러난다.
댓글 0