왜 라이선스 검사가 문제가 되는가

오픈소스 의존성은 프로젝트당 수백 개에 이르고, 각 패키지는 저마다 다른 라이선스를 가진다. 문제는 대부분의 팀이 이를 코드 리뷰에서 육안으로 확인하지 못한다는 점이다. MIT나 Apache-2.0처럼 사업에 무해한 라이선스만 있으면 다행이지만, GPL·AGPL 계열은 배포 시 소스 공개 의무를 유발할 수 있고, 라이선스가 명시되지 않은(UNKNOWN) 패키지는 법적 회색지대로 남는다.

더 까다로운 것은 전이 의존성(transitive dependency)이다. 직접 추가한 라이브러리는 괜찮아도, 그 라이브러리가 끌고 오는 하위 의존성 어딘가에 문제 라이선스가 숨어 있을 수 있다. 사람이 매번 추적하기란 불가능하므로 CI에서 자동화하는 것이 유일하게 지속 가능한 방법이다.

정책을 먼저 정의한다

도구를 붙이기 전에 팀의 정책을 명문화해야 한다. 최소한 세 가지 목록이 필요하다.

  • 허용(allow): MIT, BSD-2/3-Clause, Apache-2.0, ISC 등
  • 차단(deny): GPL-3.0, AGPL-3.0 등 카피레프트 강제 조항이 있는 것
  • 검토 필요(review): LGPL, MPL-2.0, UNKNOWN — 사람이 판단

정책 없이 도구부터 켜면 경고가 수백 개 쏟아지고 팀은 곧 무시하게 된다. 화이트리스트 방식(허용 목록에 없으면 실패)이 블랙리스트보다 안전하지만, 초기 도입 시에는 오탐이 많아 운영 부담이 크다.

파이썬 프로젝트: pip-licenses로 게이트 만들기

파이썬은 pip-licenses로 설치된 패키지의 라이선스를 JSON으로 뽑아 정책과 대조할 수 있다. 다음은 차단 목록에 걸리면 종료 코드 1로 CI를 실패시키는 스크립트다.

#!/usr/bin/env python3
# check_licenses.py
import json, subprocess, sys

DENY = {"GPL-3.0", "GPL-2.0", "AGPL-3.0", "AGPL-3.0-only"}
REVIEW = {"LGPL-3.0", "MPL-2.0", "UNKNOWN"}

out = subprocess.check_output(
    ["pip-licenses", "--format=json", "--with-license-file=false"]
)
pkgs = json.loads(out)

blocked, flagged = [], []
for p in pkgs:
    lic = p["License"].strip()
    name = f'{p["Name"]}=={p["Version"]}'
    if lic in DENY:
        blocked.append(f"{name} -> {lic}")
    elif lic in REVIEW:
        flagged.append(f"{name} -> {lic}")

for f in flagged:
    print(f"::warning:: 검토 필요: {f}")

if blocked:
    print("차단된 라이선스 발견:", file=sys.stderr)
    for b in blocked:
        print(f"  - {b}", file=sys.stderr)
    sys.exit(1)

print(f"OK: {len(pkgs)}개 패키지 검사 완료")

GitHub Actions에 연결하기

워크플로에서는 PR 단위로 검사를 돌린다. 캐시로 설치를 빠르게 하고, 검사 실패 시 머지를 막도록 필수 체크로 지정한다.

name: license-check
on:
  pull_request:
    paths:
      - "requirements*.txt"
      - "pyproject.toml"

jobs:
  licenses:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install deps
        run: |
          pip install -r requirements.txt
          pip install pip-licenses
      - name: Run license gate
        run: python check_licenses.py

paths 필터로 의존성 파일이 바뀐 PR에서만 돌게 하면 불필요한 실행을 줄일 수 있다.

도구 선택 비교

생태계별로 성숙한 도구가 다르다. 언어가 섞인 모노레포라면 SBOM 기반 도구가 유리하다.

도구대상특징
pip-licensesPython가볍고 스크립트 연동 쉬움
license-checkerNode.jsnpm 트리 전체 스캔
Syft + Grype/정책다언어SBOM(SPDX/CycloneDX) 생성 기반
FOSSA / ScanCode다언어전이·듀얼 라이선스 정밀 분석

운영상의 주의점

자동화가 오히려 신뢰를 잃는 경우가 있다. 몇 가지를 미리 막아야 한다.

  • 라이선스 표기의 불일치: 같은 라이선스도 Apache 2.0, Apache-2.0, Apache License 2.0으로 제각각 표기된다. SPDX 식별자로 정규화한 뒤 비교해야 오탐이 준다.
  • 듀얼 라이선스: MIT OR GPL-3.0처럼 선택 가능한 경우, 단순 문자열 매칭은 무해한 패키지를 차단한다. SPDX 표현식 파싱이 필요하다.
  • 예외 목록: 특정 패키지를 법무 검토 후 승인했다면 커밋된 allowlist 파일로 관리하고, 승인 근거와 만료일을 함께 남긴다.

마지막으로, CI 검사는 배포 산출물의 라이선스 준수를 보장하지 않는다. 개발 의존성과 실제 배포에 포함되는 런타임 의존성을 구분하고, 후자에 더 엄격한 정책을 적용하는 것이 현실적이다.