Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions docker/lib/merge-sbom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
# input's root component name, or layer-<index>), so provenance survives the
# merge. Dedup keeps the first occurrence, so the first layer listed wins.
#
# `dependencies` trees are NOT merged: bom-ref namespaces collide across inputs
# and the downstream pipeline only walks `.components[]`, so a merged tree would
# add risk without value.
# The per-layer `dependencies` graphs are merged too (edges unioned by ref), so
# the merged BOM keeps transitive-dependency information — required by the SKT
# conformance check. bom-refs rarely collide across ecosystems; identical refs
# have their dependsOn lists unioned.
set -e

OUTPUT="$1"
Expand Down Expand Up @@ -65,6 +66,9 @@ for f in "$@"; do
| select((.name // "") != "")
| .properties = ((.properties // []) + [{name: "bomlens:layer", value: $L}]) ]' \
"$f" > "$WORK/comps-$i.json"
# Keep each layer's dependency graph so the merged BOM retains transitive
# edges (a mandatory SKT conformance check; dropping them fails it).
jq -c '[ .dependencies[]? ]' "$f" > "$WORK/deps-$i.json"
valid=$((valid + 1))
i=$((i + 1))
done
Expand All @@ -85,9 +89,23 @@ jq -s '

NTOTAL=$(jq 'length' "$WORK/merged.json")

# --slurpfile reads the merged components from a file (again, ARG_MAX safe).
# Merge the per-layer dependency graphs. Edges are preserved so the merged BOM
# keeps transitive-dependency information. bom-refs rarely collide across
# ecosystems (pkg:rpm vs pkg:npm …); when the same ref appears in more than one
# layer, its dependsOn lists are unioned. Entries with no edges are dropped.
jq -s '
add
| group_by(.ref)
| map({ ref: .[0].ref, dependsOn: ([ .[].dependsOn[]? ] | unique) })
| map(select((.ref != null) and ((.dependsOn | length) > 0)))
' "$WORK"/deps-*.json > "$WORK/deps.json"

NEDGES=$(jq '[.[].dependsOn[]?] | length' "$WORK/deps.json")

# --slurpfile reads the merged components/dependencies from files (ARG_MAX safe).
jq -n \
--slurpfile comps "$WORK/merged.json" \
--slurpfile deps "$WORK/deps.json" \
--arg name "$NAME" \
--arg version "$VERSION" \
--arg ts "$GEN_AT" '
Expand All @@ -100,7 +118,8 @@ jq -n \
tools: { components: [ { type: "application", name: "bomlens-merge" } ] },
component: { type: "application", name: $name, version: $version }
},
components: $comps[0]
components: $comps[0],
dependencies: $deps[0]
}' > "$OUTPUT"

echo "[merge] SBOM written: $OUTPUT (components=${NTOTAL} from ${valid} layer(s))"
echo "[merge] SBOM written: $OUTPUT (components=${NTOTAL}, dependency edges=${NEDGES}, from ${valid} layer(s))"
4 changes: 2 additions & 2 deletions docs/guides/server-delivery.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ done

각 층에서 두 값은 비슷해야 합니다. 차이가 크면 purl 없는 컴포넌트가 많다는 뜻이고, 보통 원시 디렉터리 스캔이나 수기 작성이 원인입니다. 그다음 [CycloneDX validator](https://github.com/CycloneDX/cyclonedx-cli)로 스키마 유효성을 확인합니다.

층을 분리해 두는 것이 기본인 이유가 있습니다. 검토자가 어느 층이 빠졌는지, 취약점이 어디 있는지 한눈에 보고, 각 SBOM이 자체 의존성 그래프(`dependencies`)를 그대로 유지하기 때문입니다.
층을 분리해 두는 것이 기본인 이유가 있습니다. 검토자가 어느 층이 빠졌는지, 취약점이 어디 있는지 한눈에 보고, 각 층의 의존성 그래프가 그 층 범위로 깔끔하게 유지되기 때문입니다.

## 선택: 단일 SBOM으로 병합

Expand All @@ -103,7 +103,7 @@ $SBOM --project mms-relay-server --version 1.0.0 \

이 명령은 `mms-relay-server_1.0.0_bom.json`을 만들고, `metadata.component`를 서버 제품으로 설정하며, 병합된 컴포넌트 집합 위에 고지문과 위험분석보고서를 생성합니다. 각 컴포넌트에는 `bomlens:layer` 속성이 남으므로 층별로 걸러 볼 수 있습니다(`jq '.components[] | select(.properties[]?.value == "centos")'`).

한 가지 절충이 있습니다. 병합은 층별 `dependencies` 트리를 버립니다(`bom-ref` 네임스페이스가 충돌하기 때문). 전이 의존성 그래프가 검토에 중요하면 층을 분리해 제출하세요.
병합본은 각 층의 `dependencies` 그래프를 보존합니다(ref 기준으로 엣지를 합침). 따라서 전이 의존성 정보가 그대로 남아 SKT 적합성 검증의 전이 의존성 항목을 통과합니다. 생태계가 달라 `bom-ref` 충돌은 드물고, 같은 ref는 dependsOn 목록을 합칩니다.

## 서버 SBOM이 반려되는 경우

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/server-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ done

The two counts should be close for each layer. A large gap means many components lack a purl, which usually points to a raw-directory scan or a hand-written entry. Then validate the schema with the [CycloneDX validator](https://github.com/CycloneDX/cyclonedx-cli).

Keeping the layers separate is the default for a reason: a reviewer sees at a glance which layer is missing or where a vulnerability sits, and each SBOM keeps its own dependency graph (`dependencies`).
Keeping the layers separate is the default for a reason: a reviewer sees at a glance which layer is missing or where a vulnerability sits, and each layer's dependency graph stays cleanly scoped to that layer.

## Optional: merge into one SBOM

Expand All @@ -103,7 +103,7 @@ $SBOM --project mms-relay-server --version 1.0.0 \

This writes `mms-relay-server_1.0.0_bom.json` with `metadata.component` set to the server product, plus the notice and risk report over the merged set. Each component keeps a `bomlens:layer` property, so you can still filter by layer (`jq '.components[] | select(.properties[]?.value == "centos")'`).

One trade-off: the merge drops the per-layer `dependencies` trees (their `bom-ref` namespaces collide). If the transitive-dependency graph matters for review, submit the layers separately instead.
The merge preserves each layer's `dependencies` graph (edges unioned by ref), so the merged BOM keeps its transitive-dependency information and passes the SKT conformance check. Cross-ecosystem `bom-ref` collisions are rare; identical refs have their dependsOn lists unioned.

## What gets a server SBOM rejected

Expand Down
Loading