Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f29a0b0
feat: add centralized org SBOM pipeline
seonghobae Jul 8, 2026
3cf5de1
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 9, 2026
5f77963
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 9, 2026
93bda57
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
6128014
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
d36703e
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
effa491
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
08cdf5b
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
5deec65
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
9dea4cf
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
074fcb0
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
6a1fb60
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
8dc57d8
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
28abbfa
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
d41a7ee
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
e6d4f01
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
9f57f04
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
dc94bea
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
16ff32e
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
86360e9
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
6d1b8a8
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
570d252
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
670210e
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
7e7f4fe
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
7274f72
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
dbfbbe3
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
b68a4f3
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
ccd5daf
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
06608a0
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
c5bee3b
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
1fcf92d
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
fdc62a3
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
0201d8a
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
1b56a51
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
4af580a
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
19daab3
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
0d551b0
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
bf04bfa
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
aaa06e9
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
a6c18b1
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
648ea4a
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
80422ba
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
18e55e7
Merge branch 'main' into feat/central-sbom
opencode-agent[bot] Jul 10, 2026
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
78 changes: 78 additions & 0 deletions .github/workflows/sbom-generation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Central SBOM generation for every ContextualWisdomLab repo.
#
# This is a REQUIRED-style org workflow (mirrors security-scan.yml): same
# pull_request trigger conventions, least-privilege permissions, SHA-pinned
# actions. It complements the Security Scan by producing a Software Bill of
# Materials for every repo's dependencies on each PR and release.
#
# What it does per repo:
# - Generates BOTH a CycloneDX and an SPDX SBOM with anchore/syft (via the
# anchore/sbom-action wrapper; Apache-2.0, permissive tooling only), scanning
# the whole filesystem so every present ecosystem is covered
# (npm / pyproject / uv / cargo / go / maven).
# - Uploads each SBOM as a build artifact.
# - Attaches both SBOMs to GitHub releases (on release: published).
# - Submits the SPDX snapshot to the GitHub dependency submission API so the
# components show up in the repo's dependency graph. That graph is the source
# the central SBOM inventory aggregator reads back out org-wide.
#
# NOTE: contents: write is required for release-asset upload and for the
# dependency submission API. Fork PR heads run without write and simply skip
# those side effects; the artifact is still produced.
name: SBOM Generation

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
branches: [main, master, develop]
release:
types: [published]

concurrency:
group: sbom-generation-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.event.release.tag_name || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
cancel-closed-pr-runs:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- run: echo "PR closed; this run only cancels older runs through workflow concurrency."

generate-sbom:
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
permissions:
# write is needed for release-asset upload and dependency submission.
contents: write
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Generate SPDX SBOM and submit dependency snapshot
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: .
format: spdx-json
output-file: sbom.spdx.json
artifact-name: sbom-spdx-json
upload-artifact: true
upload-release-assets: true
# Feeds the repo dependency graph -> read back by the org aggregator.
dependency-snapshot: true

- name: Generate CycloneDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: .
format: cyclonedx-json
output-file: sbom.cyclonedx.json
artifact-name: sbom-cyclonedx-json
upload-artifact: true
upload-release-assets: true
dependency-snapshot: false
160 changes: 160 additions & 0 deletions .github/workflows/sbom-inventory-scheduler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Central SBOM inventory aggregator.
#
# Scheduled companion to sbom-generation.yml. It reads every managed repo's
# latest SBOM back out of the GitHub dependency graph (populated by the
# per-repo SBOM Generation dependency snapshot) and writes ONE consolidated org
# inventory into this .github repo:
#
# docs/sbom/inventory.json machine-readable component roll-up
# docs/sbom/inventory.md component + license roll-up (flags copyleft /
# NOASSERTION against the commercial-license-only policy)
#
# Cross-repo reads reuse the OpenCode app OIDC token exchange the other
# schedulers use, falling back to github.token. Results land through a PR so the
# central inventory update follows the same review path as everything else.
name: SBOM Inventory Scheduler

on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
inputs:
org:
description: Organization login to inventory
required: false
default: ContextualWisdomLab
type: string

concurrency:
group: sbom-inventory-scheduler-${{ github.repository }}
cancel-in-progress: false

permissions:
contents: read

jobs:
aggregate-sbom-inventory:
runs-on: ubuntu-latest
permissions:
contents: write
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
id-token: write
pull-requests: write
env:
ORG_LOGIN: ${{ inputs.org || vars.SBOM_INVENTORY_ORG || 'ContextualWisdomLab' }}
steps:
- name: Exchange OpenCode app token for cross-repo reads
id: aggregator_app_token
env:
OIDC_AUDIENCE: opencode-github-action
OPENCODE_API_BASE_URL: https://api.opencode.ai
run: |
set -euo pipefail

mark_unavailable() {
echo "available=false" >>"$GITHUB_OUTPUT"
}

if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
echo "OpenCode app token exchange unavailable: OIDC request environment is missing."
mark_unavailable
exit 0
fi

request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}"
separator="&"
case "$request_url" in
*\?*) ;;
*) separator="?" ;;
esac

if ! oidc_response="$(
curl -fsS \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${request_url}${separator}audience=${OIDC_AUDIENCE}"
)"; then
echo "OpenCode app token exchange unavailable: OIDC token request did not complete."
mark_unavailable
exit 0
fi

oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")"
if [ -z "$oidc_token" ]; then
echo "OpenCode app token exchange unavailable: OIDC token response was empty."
mark_unavailable
exit 0
fi

if ! token_response="$(
curl -fsS \
-X POST \
-H "Authorization: Bearer ${oidc_token}" \
"${OPENCODE_API_BASE_URL}/exchange_github_app_token"
)"; then
echo "OpenCode app token exchange unavailable: app token request did not complete."
mark_unavailable
exit 0
fi

app_token="$(jq -r '.token // empty' <<<"$token_response")"
if [ -z "$app_token" ]; then
echo "OpenCode app token exchange unavailable: app token response was empty."
mark_unavailable
exit 0
fi

echo "::add-mask::$app_token"
{
echo "available=true"
echo "token=$app_token"
} >>"$GITHUB_OUTPUT"

- name: Checkout trusted aggregator
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ContextualWisdomLab/.github
ref: main
fetch-depth: 1
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"

- name: Self-test aggregator
run: python3 scripts/ci/sbom_inventory_aggregator.py --self-test

- name: Aggregate org SBOM inventory
env:
GH_TOKEN: ${{ secrets.SBOM_INVENTORY_TOKEN || steps.aggregator_app_token.outputs.token || github.token }}
run: |
set -euo pipefail
generated_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
python3 scripts/ci/sbom_inventory_aggregator.py \
--org "$ORG_LOGIN" \
--output-dir docs/sbom \
--generated-at "$generated_at"

- name: Open or update inventory PR
env:
GH_TOKEN: ${{ secrets.SBOM_INVENTORY_TOKEN || steps.aggregator_app_token.outputs.token || github.token }}
run: |
set -euo pipefail
if git diff --quiet -- docs/sbom; then
echo "No SBOM inventory changes; nothing to publish."
exit 0
fi
branch="automation/sbom-inventory"
git config user.name "cwl-sbom-inventory[bot]"
git config user.email "cwl-sbom-inventory@users.noreply.github.com"
git checkout -B "$branch"
git add docs/sbom
git commit -m "chore: refresh org SBOM inventory"
git push --force-with-lease origin "$branch"
if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then
gh pr create \
--base main \
--head "$branch" \
--title "chore: refresh org SBOM inventory" \
--body "Automated central SBOM inventory refresh. Review the license roll-up in docs/sbom/inventory.md for any flagged copyleft/NOASSERTION components."
fi
12 changes: 12 additions & 0 deletions docs/sbom/inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"schema": "cwl-sbom-inventory/v1",
"summary": {
"repo_count": 0,
"component_count": 0,
"flagged_count": 0,
"policy": "commercial-license-only"
},
"license_totals": {},
"flagged_licenses": [],
"repos": []
}
25 changes: 25 additions & 0 deletions docs/sbom/inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Organization SBOM inventory

Generated: pending first scheduled run

One central view of every managed repository's software components,
versions, and licenses. Feeds license and vulnerability governance
alongside the central Security Scan.

## Summary

- Repositories: 0
- Components: 0
- Policy: commercial-license-only
- Flagged licenses: 0

## License roll-up

| License | Components |
| --- | ---: |

## Flagged components (policy violations)

No copyleft or NOASSERTION components detected.

## Per-repository components
Loading
Loading