CI integration¶
GitHub Action¶
name: PQC policy
on: [pull_request]
permissions:
contents: read
security-events: write # to upload SARIF
jobs:
cbomctl:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Bring a CBOM from whatever generator you use. CBOMkit-action takes no
# `with:` inputs — it is configured by environment variables, and writes
# the consolidated CBOM to `cbom/cbom.json`: the directory comes from
# `CBOMKIT_OUTPUT_DIR`, which defaults to `cbom`, and the consolidated
# file inside it is `cbom.json`. Set `CBOMKIT_OUTPUT_DIR` if you want it
# somewhere else. Their step also exports `outputs.pattern`
# (`<dir>/cbom*.json`), which is what to hand to upload-artifact if you
# want the per-module CBOMs as well.
- uses: cbomkit/cbomkit-action@main
id: cbom
env:
CBOMKIT_LANGUAGES: java, python
# v0 is a moving major tag: patch fixes arrive, breaking changes do not.
# Pin an exact release (v0.1.4) instead if you want the tool frozen.
- uses: lvlrSajjad/cbomctl@v0
with:
cbom: cbom/cbom.json
jurisdictions: bsi-de,anssi-fr,asd-au
config: cbomctl.yaml
format: sarif
output: cbomctl.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: cbomctl.sarif }
The action always prints the matrix to the job log, even when the machine-readable report goes to a file — the matrix is the part a human reads.
How much of this workflow is checked
scripts/check_commands.py resolves every step on every run, ours and
theirs. For lvlrSajjad/cbomctl it asserts that @v0 is a tag this
repository has and that each with: key is an input action.yml declares.
For cbomkit/cbomkit-action, actions/checkout and
github/codeql-action/upload-sarif it fetches their own action.yml at
the ref named above and checks the same thing — and because a Docker action
declares no inputs at all, the env: names are checked against
CBOMkit-action's README at
that ref. An action the checker does not know how to resolve is reported
unchecked by name rather than passed over.
Until 2026-09-06 this page passed with: { output: cbom.json } to an
action that declares no inputs at all. That was fixed by hand, and then
nothing could re-check it; now something does.
What is still not checked: that the workflow runs. Nothing here starts
a container or scans a repository, so cbom/cbom.json is read from
CBOMkit-action's Main.java (CBOMKIT_OUTPUT_DIR, default cbom) and
their README, on 2026-09-06 — transcribed, not observed. The reasoning for
not building a scheduled job that would run it is in
docs/roadmap.md.
Exit codes¶
| code | meaning |
|---|---|
0 |
no mandatory rule violated |
1 |
a mandatory rule failed, or --fail-on-warn and a recommendation was violated |
2 |
usage or parse error |
3 |
unresolved or indeterminate findings under --strict |
WARN does not fail the build by default, and that is deliberate. A
technical guideline that recommends hybrid is not a gate; treating it as one
trains people to ignore the tool. Use --fail-on-warn when your organisation
has decided to hold itself to recommendations.
Choosing your gate¶
Start permissive. Run without --strict and read the output for a few
weeks. The unresolved section tells you how good your generator is, and that is
usually the first thing worth fixing.
Then close the loop on unknowns. --strict turns unresolved purposes and
undeclared facts into exit 3. This is the setting that forces the question back
to a human — which is where it belongs, since a CBOM cannot say what a key is
for and a config file can.
--require-verified-policy refuses to run at all against a pack containing
rules that have not been read from a primary source. If you are using the output
for anything that resembles a compliance claim, turn this on. It currently
excludes nothing — every shipped rule is verified — which is exactly when it is
cheapest to switch on and keep on.
SARIF¶
The SARIF output carries only cbomctl's risk and conflict findings, so
they sit beside your other tools' results in the GitHub Security tab rather than
duplicating them. Locations come from evidence.occurrences, so findings are
clickable to the file and line where the algorithm was found.
Unverified rules are marked [UNVERIFIED RULE] and draft-sourced rules
[DRAFT SOURCE] inline in the finding text, because a SARIF result detached
from its banner should still say what it is.
Determinism¶
Same CBOM, same config, same pack version, same --crqc-year produces
byte-identical JSON. Nothing in the verdict path reaches the network, consults a
clock beyond the evaluation date, or calls a language model. Diff two runs and
any difference is a real change.