114 lines
6.1 KiB
Markdown
114 lines
6.1 KiB
Markdown
# VD-09: 공급망 inventory, license, vulnerability, SBOM과 provenance
|
|
|
|
- 상태: Accepted
|
|
- 결정일: 2026-07-26
|
|
- 적용 브랜치: `feature-frontend-supply-chain-verification`
|
|
- 재검토: 조직 vulnerability scanner, signing/attestation provider와 dependency
|
|
exception 승인 체계가 선정될 때
|
|
|
|
## 배경
|
|
|
|
기존 release script는 `package.json`의 직접 dependency 이름과 버전, lockfile
|
|
전체 digest, `dist` checksum만 기록했다. 전이 dependency, 패키지별 integrity와
|
|
license, 실제 baseline diff가 없었고 `highRiskUnreviewed: []`는 계산 결과가 아닌
|
|
고정값이었다. secret scan도 `src`와 `dist`만 검사해 config, scripts, test와
|
|
generated release metadata를 놓쳤다.
|
|
|
|
반면 저장소에는 조직이 선택한 vulnerability source, severity exception 승인자,
|
|
signing identity와 attestation 저장소가 없다. 외부 provider가 없는 상태를 빈
|
|
finding과 서명 성공으로 표현하면 local 검증과 release promotion을 혼동한다.
|
|
|
|
## 결정
|
|
|
|
1. `pnpm-lock.yaml`의 모든 `packages` row와 `pnpm list --depth Infinity`의 실제
|
|
graph를 결합해 직접/전이, production/development, required/platform-optional,
|
|
version, SHA-512 SRI, license와 dependency edge를 기록한다.
|
|
2. inventory row 수는 lockfile package row 수와 같아야 한다. 누락된 전이
|
|
dependency, malformed integrity와 non-optional `NOASSERTION`은 local gate를
|
|
실패시킨다.
|
|
3. license는 설치된 package manifest에서 읽고 closed allow/deny policy로
|
|
검사한다. 현재 OS에 materialize되지 않은 platform optional만
|
|
`NOASSERTION`과 그 이유를 명시적으로 허용한다.
|
|
4. 승인 dependency baseline과 approval digest를 보존하고 현재 lock inventory와
|
|
actual add/remove/change/upgrade diff를 계산한다. 새 direct production
|
|
dependency는 owner와 서로 다른 reviewer, reason과 rollback evidence가
|
|
필요하다.
|
|
5. inventory를 CycloneDX 1.6 SBOM으로 투영한다. component 수, lockfile digest,
|
|
SRI, license와 dependency edge가 inventory와 일치해야 한다.
|
|
6. local in-toto/SLSA 형태 provenance statement는 source set, lockfile, SBOM과
|
|
`dist` digest를 연결하되 `LOCAL_UNSIGNED`로 표시한다. 이 문서는 외부
|
|
provenance를 대신할 수 없다.
|
|
7. `immutable_build`는 raw `pnpm-lock.yaml`, `dist`, build/module inventory와
|
|
모든 local verification evidence를 한 번만 archive한다. Candidate manifest는
|
|
raw lock bytes SHA-256, dependency inventory lock digest와 manifest
|
|
`lockfileSha256`의 exact 일치를 요구한다.
|
|
8. 두 provider job은 동일 archive를 각각 받아 외부 command를 실행한다.
|
|
Vulnerability report는 raw lock digest와 `distSha256`, provenance attestation은
|
|
`{name: "dist", digest.sha256}`를 포함한다. 두 문서 모두 strict schema와
|
|
별도 trust path/key ID로 선택한 실제 Ed25519 public key 서명을 통과해야 한다.
|
|
9. provider report나 trusted key가 없으면 local
|
|
inventory/license/SBOM/coherence는 `PASS`, promotion은
|
|
`FAIL_UNVERIFIED`다. 저장소 generator나 fixture가 production용 빈 finding 또는
|
|
signed PASS를 만들지 않는다.
|
|
10. secret scan은 source, scripts, tests, tracked config/schema, public, `dist`와
|
|
generated release metadata를 검사한다. allowlist는 test path에만 허용하며
|
|
owner, reason과 expiry가 필요하다. 발견한 secret 원문은 artifact에 쓰지 않고
|
|
rule, path, line과 fingerprint만 남긴다.
|
|
11. `SOURCE_DATE_EPOCH`를 지원하고 supply-chain timestamp도 build manifest의
|
|
동일 epoch에 결합한다. 같은 source/lock/config의 production build를
|
|
두 번 실행해 전체 dist digest 일치를 검증한 뒤 일반 build를 복원한다.
|
|
|
|
## 실행 경계와 증적
|
|
|
|
```text
|
|
package.json + frozen pnpm-lock.yaml + installed graph
|
|
-> deterministic dependency inventory
|
|
-> license policy + approved actual baseline diff
|
|
-> CycloneDX SBOM
|
|
|
|
source/config/lock + production dist
|
|
-> local provenance statement
|
|
-> immutable archive + candidate manifest + distSha256
|
|
-> external vulnerability provider + external provenance provider
|
|
-> read-only local revalidation + signature/digest verification
|
|
-> promotion PASS | FAIL_UNVERIFIED
|
|
```
|
|
|
|
- policy: `config/security/`
|
|
- generator: `scripts/generate-supply-chain.ts`
|
|
- coherence: `scripts/verify-supply-chain-artifacts.ts`
|
|
- secret scan: `scripts/security-scan.ts`
|
|
- reproducibility: `scripts/verify-reproducible-build.ts`
|
|
- inventory: `artifacts/release/dependency-inventory.json`
|
|
- SBOM/provenance: `artifacts/release/sbom.cdx.json`,
|
|
`artifacts/release/provenance.json`
|
|
- local/promotion status:
|
|
`artifacts/security/supply-chain-verification.json`
|
|
|
|
## 검증
|
|
|
|
- 현재 lockfile의 561개 package row와 inventory row가 양방향 일치한다.
|
|
- ordering-only digest, removal, integrity tamper, baseline tamper, high-risk
|
|
self approval, denied license, critical vulnerability와 만료 exception,
|
|
provider/digest 오류, SBOM/provenance 불일치 fixture를 검사한다.
|
|
- isolated temporary candidate/PEM/report fixture는 실제 environment path wiring을
|
|
통해 valid immutable 입력만 promotion `PASS`임을 증명한다. Production artifact를
|
|
덮어쓰거나 generator를 provider 모드로 재실행하지 않는다.
|
|
- frozen install은 manifest/lock mismatch fixture를 실제 pnpm으로 거절한다.
|
|
- source/config/dist 각각의 synthetic secret fixture가 실제 scan을 실패시키고
|
|
scoped test allowlist만 통과한다.
|
|
|
|
## 한계와 재검토 조건
|
|
|
|
로컬 manifest license는 법률 검토가 아니며 vulnerability report도 외부 scanner가
|
|
제공한 데이터의 최신성 자체를 보증하지 않는다. 실제 프로젝트는 provider 버전,
|
|
database freshness, network outage, exception 승인 조직, signing identity,
|
|
attestation transparency/retention과 비밀 관리를 결정해야 한다.
|
|
|
|
## Rollback
|
|
|
|
외부 scanner/attestor command, report path 또는 trusted key 설정을 제거하면 즉시
|
|
`FAIL_UNVERIFIED`로 돌아간다. local inventory, lock integrity, license, SBOM,
|
|
secret, reproducibility와 actual diff gate는 유지한다. scanner 장애를 이유로
|
|
promotion을 PASS로 변경하지 않는다.
|