라이선스 정책
라이선스 정책은 팀(또는 조직 전체)이 SPDX 라이선스 식별자를 위험 카테고리
allowed·conditional·forbidden에 어떻게 매핑할지 직접 설정하게 해 줍니다.
정책이 없으면 포털은 내장된 고정 카탈로그로 라이선스를 분류합니다. 정책을 두면 그
분류가 런타임에 편집 가능한 데이터가 됩니다 — 재배포가 필요 없습니다.
정책 데이터 모델 + CRUD API, 동적 빌드 게이트 평가(하드닝된 복합 SPDX 해석 포함), 그리고 앱 내 편집기가 이제 연결되었습니다. 유효하고 enabled 인 정책은 해당 팀의 게이트 금지-라이선스 판정을 바꿉니다. 아래 포털에서 정책 편집과 동적 게이트 평가를 참고하세요.
범위(Scope)
정책은 다음 두 종류의 범위 중 정확히 하나로 존재합니다.
| 범위 | team_id | 적용 대상 | 작성 권한 |
|---|---|---|---|
| 그룹 | 설정됨 | 해당 그룹(팀) 하나 | 그룹의 team_admin 또는 super_admin |
| 조직 기본 | null | 적용할 그룹 정책이 없는 조직 내 모든 그룹 | super_admin 전용 |
조직마다 조직 기본 정책은 최대 1개, 그룹마다 정책은 최대 1개입니다. 같은 범위에 다시
PUT 하면 기존 행을 업데이트합니다(멱등 upsert).
유효 정책 해석
그룹은 계층 깊이에 제한 없이 중첩됩니다. 한 팀이 어느 사업부 아래에 있고, 그 사업부가 또 다른 사업부 아래에 있는 식으로 조직 루트까지 이어질 수 있습니다. 한 팀의 조상 사슬은 그 팀 자신과 그 위의 모든 그룹을 가장 가까운 것부터 루트까지 나열한 목록입니다. 팀의 유효 정책은 이 사슬을 따라가며 다음 순서로 결정됩니다.
- 팀 자체 정책 — 존재하고 enabled 이면 적용, 아니면
- 가장 가까운 조상 그룹의 정책 — 존재하고 enabled 이면 적용, 아니면
- 그 위 조상, 그 위 조상, ... 루트까지 반복, 그래도 없으면
- 조직 기본 정책 — 존재하고 enabled 이면 적용, 아니면
- 없음(팀은 내장 정적 카탈로그로 폴백합니다).
사슬에서 팀에 가장 가까운 enabled 정책이 필드 전체를 통째로 적용합니다. 두 범위의 필드를 섞어 합치지 않습니다. 어떤 단계의 정책이 비활성이거나 없으면 그 단계만 건너뛰고 다음 조상으로 넘어갈 뿐, 탐색을 멈추거나 곧바로 조직 기본값으로 건너뛰지 않습니다. 그룹을 조직 루트 아래로 중첩하지 않는 배포라면 이 순서는 예전의 2단계("팀, 그다음 조직") 동작과 결과가 같습니다. 그런 팀의 조상 사슬은 조직 기본값 앞에 항목이 하나뿐이기 때문입니다.
enabled: false는 정책을 삭제하지 않고 비활성화하므로, 그룹은 정책을 다시 작성하지
않고도 동적 정책을 껐다 켤 수 있습니다. 그러면 사슬 탐색은 다음 조상(또는 조직
기본값)으로 그대로 이어집니다.
정책 필드
| 필드 | 타입 | 의미 |
|---|---|---|
name | string | null | UI 표시 라벨. |
category_overrides | object | SPDX id → allowed | conditional | forbidden. 해당 id의 카탈로그 판정을 대체. |
license_exceptions | array | 명시적 예외 — 매칭된 라이선스를 allowed로 강제. |
malicious_exceptions | 배열 | 악성으로 지목된 패키지의 기한부 면제 — 게이트 집계에만 적용됩니다. |
unknown_license_category | enum | 카탈로그·오버라이드 맵에 없는 라이선스의 자세. 기본 conditional. |
compound_operator_strategy | object | 복합 SPDX 식(A AND B·A OR B·A WITH exc) 해석 방식. |
enabled | bool | 마스터 토글. false → 해석 시 정책 무시. |
category_overrides
{
"MPL-2.0": "forbidden",
"EPL-2.0": "conditional",
"MIT": "allowed"
}
license_exceptions
각 항목은 spdx_id와 reason이 필요합니다. expires_at(RFC 3339, 선택)으로
게이트가 만료된 예외로 처리하게 할 수 있고, component_purl(선택)은 예외를 라이선스를
가진 모든 컴포넌트가 아닌 단일 컴포넌트로 한정합니다.
[
{
"spdx_id": "GPL-3.0-only",
"reason": "법무 승인 예외 TICKET-123",
"expires_at": "2026-12-31T00:00:00Z",
"component_purl": "pkg:pypi/somepkg@1.2.3"
}
]
malicious_exceptions
각 항목에는 component_purl, reason, expires_at이 필요하며 만료 시한은
라이선스 면제와 달리 필수입니다. 라이선스 면제는 영구적일 수 있습니다. 법무 검토가
끝났다면 답이 달라지지 않기 때문입니다. 반면
악성 표시는 항상
결론이 납니다. 권고가 틀렸다면 상류에 이의를 제기해 다음 스냅샷에서 빠지고, 맞다면
패키지를 걷어내야 합니다. 기한 없는 면제는 그 결정을 보이지 않는 곳에 미뤄 둘 뿐입니다.
[
{
"component_purl": "pkg:npm/some-package",
"reason": "상류에 이의 제기함, TICKET-456",
"expires_at": "2026-08-20T00:00:00Z"
}
]
면제는 빌드 게이트 집계에서만 컴포넌트를 제외합니다. 배지도, ?malicious=true
필터에 나오는 것도, 드로어 항목도 그대로입니다. 권고를 정리하는 동안 차단을 미루는
장치이지 표시를 철회하는 것이 아닙니다. 대조는 버전을 뗀 식별자로 하므로 버전이 붙은
채로 적어도 적용됩니다.
compound_operator_strategy
{
"AND": "most_restrictive",
"OR": "least_restrictive",
"WITH": "most_restrictive"
}
값은 most_restrictive 또는 least_restrictive 입니다. 기본값은 AND·WITH에서
가장 제한적인 하위 라이선스를, OR 에서는 가장 덜 제한적인 쪽을 유지합니다(이중
라이선스 의존성 의 통상적 해석). 부분 객체는 기본값과 병합되므로 바꾸려는 연산자만
보내면 됩니다.
포털에서 정책 편집
Policies 화면(사이드바 → 정책, 경로 /policies)은 정책을 코드 없이
작성하는 방법으로, 아래에 문서화된 동일한 REST API를 사용합니다.
- 범위 선택. 툴바의 팀 선택기에서 팀을 고르고 팀 정책 편집을
누르거나, 정책 표의 행을 클릭해 해당 범위를 편집합니다.
super_admin은 조직 전체 폴백을 위한 조직 기본값 편집 버튼도 볼 수 있습니다. 선택한 범위는 URL(/policies?policy=team:<id>/?policy=org:<id>)에 인코딩되므로 북마크나 강력 새로 고침 후에도 같은 편집기가 다시 열립니다. - 드로어에서 편집. 편집기가 오른쪽에서 슬라이드되며 모든 필드를 노출합니다.
- 정책 사용 — 마스터 토글. 끄면 정책 내용을 유지한 채 팀이 조직 기본값 또는 정적 카탈로그로 폴백합니다.
- 이름 — 선택적 표시 라벨.
- 미분류 라이선스 처리 — 카탈로그·오버라이드 어디에도 없는 라이선스의 분류.
- 복합 표현식 전략 —
AND/OR/WITH해석 방식. - 분류 재정의 — SPDX id를 분류에 매핑하는 행을 추가·편집·삭제.
- 라이선스 예외 — 면제 추가·삭제(SPDX id·사유 필수, 만료일·컴포넌트 PURL 선택).
- 저장 또는 초기화. 정책 저장은 upsert
PUT을 보내고, 기본값으로 초기화(팀 범위 전용)는DELETE로 팀 정책을 삭제해 조직 기본값/정적 카탈로그로 되돌립니다. 서버 검증 실패(과대 맵, 잘못된 식별자)는 오류 토스트로 표시됩니다.
편집 권한
| 역할 | 팀 정책 | 조직 기본값 |
|---|---|---|
super_admin | 모든 팀 | 가능 |
team_admin(해당 팀) | 해당 팀 | 불가 |
| 팀 구성원(관리자 아님) | 읽기 전용 | 불가 |
team_admin이 아닌 팀 구성원은 편집기를 열어 유효 정책을 볼 수 있지만 모든
컨트롤이 비활성화되고 읽기 전용 안내가 표시됩니다 — 내부 읽기가 403을 반환하며
UI가 우아하게 저하됩니다.
동적 게이트 평가
빌드 차단 게이트(CI 연동 참고)는 프로젝트에 금지 라이선스 컴포넌트가 하나라도 있으면 빌드를 차단합니다. 유효 정책이 없으 면 "금지"는 스캐너가 스캔 시점에 내장 카탈로그로 저장한 라이선스 카테고리를 뜻합니다 — 동작은 변하지 않습니다.
프로젝트 소유 팀에 유효하고 enabled 인 정책이 있으면, 게이트는 집계 전에 각 컴포넌트의 라이선스 식을 동적으로 재분류합니다.
- 각 컴포넌트의 저장된 SPDX 식을 하드닝된 복합 SPDX 평가기가 파싱합니다(단일 id,
A AND B,A OR B,A WITH exc, 괄호, 중첩). - 각 피연산자 id를 정책 순서로 해석합니다 — 매칭되고 만료되지 않은 예외(
allowed강제) → 명시적 오버라이드 → 내장 카탈로그 → 카탈로그에 없는 경우unknown_license_category자세. - 피연산자는 연산자별
compound_operator_strategy(기본:AND·WITH가장 제한적,OR가장 덜 제한적)로 접습니다. - 식이
forbidden으로 해석되는 컴포넌트를 집계하고, 1개 이상이면 게이트가 실패합니다.
따라서 팀은 평소 허용되는 라이선스를 금지하거나, 평소 금지되는 라이선스를 특정
의존성에 대해 예외 허용하거나, 이중 라이선스 A OR B를 관대하게 읽도록 할 수
있습니다 — 모두 재배포 없이. 정책을 비활성화(enabled: false)하거나 삭제하면 게이트는
정적 카탈로그로 되돌아갑니다.