L4.9 — Build an AI-BOM for a stack (Lab, Optional)¶
Type: Lab · Duration: ~45 min · Status: Optional Module: Module 4 — Data Poisoning, Backdoors & Supply Chain Framework tags: OWASP LLM05 · NIST AI RMF Govern 1.3, Map 4.1 · EU AI Act Article 11
Goal of the lab¶
Generate a CycloneDX-AI Bill of Materials for the Module 1 RAG application. Hand-augment it with the seven AI-specific artifact classes (L4.5.2). End with a queryable JSON artifact you could hand an auditor or a CISO.
Why this matters¶
AI-BOM is the "fix it once, defend everything downstream" practice that most teams haven't done yet. This lab shows you the minimum-bar end-to-end so you can replicate it on real systems.
Prerequisites¶
- Skills: shell, JSON, YAML.
- Lessons: L4.5.1, L4.5.2.
- Labs completed: L1.7 (M1 RAG built).
What you'll build¶
runs/lab4_9/sbom.cdx.json— auto-generated software BOM frompyproject.toml.runs/lab4_9/aibom.cdx.json— hand-augmented to include the seven AI artifact classes.- A small triage report identifying the riskiest provenance gaps.
Steps¶
Step 1 — Generate the software-side SBOM¶
cd /workspace/ai-sec-course
uv run cyclonedx-py environment --output-file runs/lab4_9/sbom.cdx.json --output-format JSON
Verify:
jq '.components | length' runs/lab4_9/sbom.cdx.json
# Expected: 30-50 entries (Python deps from pyproject.toml)
Step 2 — Inventory the AI-specific artifacts (the gap)¶
The auto-generated SBOM covers Python packages. It does not cover:
- Model weights (Ollama-cached, HuggingFace-cached)
- Datasets (corpora/asfela-handbook/, fine-tune datasets)
- Embedding models (all-MiniLM-L6-v2)
- Vector store (Chroma + the populated index)
- System prompts (referenced by rag.py)
We extend the BOM manually for these. Open runs/lab4_9/ai-artifacts.yaml and fill in:
ai_artifacts:
- kind: base_model
name: llama3.2:3b
provider: meta-llama
version: "3.2"
license: Llama-3.2-Community
retrieval_date: 2026-05-16
signature_status: unsigned
use_case: "Local LLM for RAG L1.7"
- kind: embedding_model
name: all-MiniLM-L6-v2
provider: sentence-transformers
version: "1.0"
license: Apache-2.0
retrieval_date: 2026-05-16
signature_status: unsigned
use_case: "Embedding for Chroma vector DB in L1.7"
- kind: dataset_corpus
name: asfela-handbook
provider: internal
version: git-sha-<commit>
license: proprietary
sensitivity: low (fictional content)
write_population: "engineering team (Asfela)"
use_case: "RAG knowledge base"
- kind: vector_store
name: chroma
backend_version: "0.5.x"
collection: "asfela_handbook"
refresh_cadence: manual (on corpus change)
use_case: "Retrieval for L1.7"
- kind: system_prompt
name: rag-system-prompt
location: src/ai_sec/solutions/lab1_7_rag_solution.py (lines NN-NN)
version: git-sha-<commit>
contains_secrets: no
use_case: "Behavioral scaffold for the RAG"
Step 3 — Merge AI artifacts into the BOM¶
uv run python scripts/lab4_9_merge_aibom.py \
--sbom runs/lab4_9/sbom.cdx.json \
--ai-artifacts runs/lab4_9/ai-artifacts.yaml \
--out runs/lab4_9/aibom.cdx.json
This produces a CycloneDX-AI document combining the software SBOM and the AI-artifact extension.
Sanity-check:
jq '.components | group_by(.type) | map({type: .[0].type, count: length})' runs/lab4_9/aibom.cdx.json
# Expected output shows multiple types: library, ml-model, dataset, etc.
Step 4 — Query the AI-BOM¶
Practice queries an auditor or CISO would actually ask:
# "Which models do we use and what's their license?"
jq '.components[] | select(.type=="ml-model") | {name, version, licenses}' runs/lab4_9/aibom.cdx.json
# "Which artifacts are unsigned?"
jq '.components[] | select(.properties[]?.name=="signature_status" and .properties[]?.value=="unsigned") | {name, type}' runs/lab4_9/aibom.cdx.json
# "Which artifacts haven't been refreshed in 12 months?"
# (Requires a date filter you'd write against retrieval_date.)
Step 5 — Identify provenance gaps¶
For the BOM you just built, what are the riskiest provenance gaps? Write a short triage in runs/lab4_9/gaps.md:
# AI-BOM gaps for Module 1 RAG
## Highest-risk gaps
1. **llama3.2:3b — unsigned.** No signature verification on the model pulled from Ollama registry. If Ollama
is compromised or typosquatted, no detection signal.
2. **asfela-handbook corpus write population — broad.** Any engineering team member can modify any handbook
file with no review process. This is the indirect-PI surface from L3.7.
3. **Chroma vector index — no provenance trail.** We embed and store; we don't track which embeddings
came from which corpus version. If a corpus rollback is needed, we can't surgically remove embeddings.
## Recommended actions
- Add signature verification when Ollama supports it (in roadmap).
- Add corpus write-review process: only `silas-asfela` org owner can land changes to /handbook.
- Track corpus version in Chroma metadata; ability to "purge by version."
Step 6 — Wire it into the release pipeline¶
The closing question: how often does this BOM need to refresh, and where does it live in your release pipeline?
Sketch in runs/lab4_9/pipeline.md:
On every git tag (release):
1. Run cyclonedx-py to generate sbom.cdx.json
2. Run merge-aibom against the curated ai-artifacts.yaml
3. Upload aibom.cdx.json to release artifact store
4. Diff against previous release; alert on new unsigned components
On any change to ai-artifacts.yaml:
1. CI gate that requires review
2. Re-generate AI-BOM artifact for the dev branch
What just happened (debrief)¶
You produced an end-to-end AI-BOM for a real (small) system. Three takeaways:
The gap between SBOM and AI-BOM is real. The auto-generated software SBOM was 50 components. The AI-BOM you produced added 5 more — but those 5 are the artifacts most likely to be the source of a supply-chain incident in 2026. If you only have an SBOM, you have ~90% of the inventory but ~50% of the risk coverage.
Hand-augmentation is acceptable for v1; automation matters for v∞. You filled in ai-artifacts.yaml by hand. In a production setting, that file should be generated from your stack's actual configuration (Ollama API for cached models, code-scan for system prompts, etc.). Build to automate over time.
The BOM is only useful if it's queryable. The jq queries you ran are the use cases that justify the BOM's existence. If your BOM can't answer "which artifacts haven't been updated in 12 months?" in 30 seconds, it's not yet doing the job.
Extension challenges (optional)¶
- Easy. Add a sixth AI artifact (e.g., the Garak probe set) to the YAML and re-merge.
- Medium. Build a small CI step (
scripts/lab4_9_diff_aibom.sh) that diffsaibom.cdx.jsonbetween two git commits and alerts on new unsigned components or changed versions. - Hard. Connect the AI-BOM to a security advisory feed (MITRE ATLAS case studies, vendor security pages) and produce a "potentially-affected components" report when a new advisory drops.
References¶
- L4.5.2 (theory).
- CycloneDX-AI — https://cyclonedx.org/capabilities/mlbom/
cyclonedx-py— https://pypi.org/project/cyclonedx-bom/
Provisioning spec (for lab platform admin)¶
Container base image: aisec/labs-base:0.1
Additional Python deps: cyclonedx-bom>=4.0 (the canonical cyclonedx-py package), pyyaml (already present).
Additional pre-installed files:
- /workspace/ai-sec-course/scripts/lab4_9_merge_aibom.py
- /workspace/ai-sec-course/templates/ai-artifacts-template.yaml
Network: none needed (BOM generated from local lockfile + manual YAML).
Resource use: trivial.
Wallclock: 30–45 min.
Notes for platform admin:
- This lab requires L1.7's RAG to have been built (the corpus + Chroma index must exist) so the AI-artifacts YAML reflects a real configuration.
- The cyclonedx-bom package CLI name varies (cyclonedx-py vs cyclonedx-bom); verify the exact invocation matches your installed version when building the image.