Skip to content

SBOM Scope and Process

This page defines the SBOM scope, generation process, storage, and refresh cadence for rag-sample. It is the standards document, not a generated artifact. The generated SBOMs land alongside CI build outputs and are linked from assessment once they exist.

The motivating gap is the assessment line item “Dependency vulnerability scanning not in CI”. SBOM generation is the inventory side of that gap; vulnerability scanning is the analysis side. Both are addressed here.

Two reasons, both small but real:

  1. Supply-chain attacks on permissive-license Python packages have happened. The current assessment relies on “pinned lower bounds in pyproject.toml” (RAG002). That is upper-bound exposure, not inventory. An SBOM is the inventory.
  2. The corpus is third-party data and the embeddings live in our database. The dataset itself is not a software component, but the huggingface-hub, pyarrow, and Bedrock SDK paths that ingest it are. They are in scope.

The scope is small because the system is small. The process below is sized to stay in proportion.

LayerComponentsGenerator
API runtime (Python)All packages installed into the API Lambda zip from pyproject.toml plus transitive closure: FastAPI, Mangum, aws-lambda-powertools, boto3, pydantic, the embedding/retrieval helpers.pip-audit --format=cyclonedx-json and cyclonedx-py (CycloneDX format).
Authorizer runtime (Python)The authorizer Lambda’s pinned dependencies (per RAG009). Likely a much smaller tree than the API: boto3 + a hashing primitive + Powertools Logger.Same as above; separate SBOM file.
Ingestion runtime (Python)The one-off ingestion Lambda or local script: huggingface-hub, pyarrow, boto3. Per RAG003 the tree is deliberately minimized.Same as above; separate SBOM file.
SPA build (Node)Production-runtime packages from package.json plus transitive closure: React, the HTTP client, any UI library. Dev dependencies are listed but tagged “dev” so they do not feed runtime vulnerability triage.npm sbom --sbom-format=cyclonedx (Node 24 supports this natively); fall back to @cyclonedx/cyclonedx-npm if a flag changes.
Lambda runtime imageThe AWS Python managed runtime in use for each Lambda (currently python3.12 per RAG002). Not generated by us, but the runtime version is recorded in each SBOM file’s metadata.Terraform aws_lambda_function.runtime attribute, surfaced into the SBOM metadata block by the CI job.
Terraform providersThe pinned provider versions from each versions.tf. AWS provider, archive, null, random, any others.terraform providers schema plus a small script that produces a CycloneDX tools array; alternatively trivy fs --scanners license,vuln over the Terraform tree.
Container layersNone today. Lambda runs the AWS managed Python runtime, not a custom container. If Lambda ever moves to container packaging, the image layers come into scope and Trivy joins the toolchain.n/a today.
  • Per CI run: every PR triggers SBOM generation for the components the PR touches. The job uploads the CycloneDX JSON as a build artifact and posts a one-line diff summary to the PR (new packages, removed packages, version bumps).
  • Per release: the production deploy pipeline regenerates SBOMs for every component listed above, against the resolved pinned versions, and uploads them to the release-artifact bucket described below. The release tag is the SBOM’s primary key.
  • Local development: developers can run the same generators locally via a make sbom or equivalent script that lives in the code repo. The point is parity with CI, not a separate process.

The CycloneDX JSON format is the chosen schema across all generators so the artifacts compose. SPDX-tag-value is rejected because the Node and Python generators we standardize on emit CycloneDX natively and a single format makes the diff scripts trivial.

ArtifactLocationRetention
Per-PR SBOMsGitHub Actions build artifacts on the PR run90 days (GitHub default for artifacts)
Per-release SBOMsS3 bucket rag-sample-release-artifacts-<env> under prefix sbom/<release-tag>/, alongside the Lambda zips and the SPA bundleIndefinite, lifecycle to Glacier after 1 year
SBOM-derived vulnerability reportsSame S3 prefix, file vulnerabilities.json, regenerated on each releaseSame lifecycle

The release bucket is the same bucket Terraform reads deployment artifacts from during a Prod apply (or a sibling bucket with the same access controls). Read access is operator-only; write access is the GitHub Actions release role.

TriggerAction
Every PRRegenerate the SBOMs for the changed components; post the diff summary.
Every release (QA promotion and Prod promotion)Regenerate all SBOMs against the released pinned versions; archive to S3.
Weekly scheduled CI jobRe-run vulnerability scans against the most recent release SBOMs without regenerating them, so newly published CVEs against unchanged dependencies are caught. Findings open as issues per the flow below.
On Dependabot or Renovate bumpSame as PR flow; the bump is the PR.

The weekly job is the lightweight equivalent of “continuous vulnerability monitoring” without standing up a SaaS scanner.

Two layers:

  1. Generation-time scanning runs as part of SBOM generation. pip-audit for the Python trees, npm audit --json for the SPA tree. Findings of high or critical severity are surfaced to the PR.
  2. Standing scan: the weekly job runs pip-audit --requirement <pinned> and npm audit against the latest released SBOMs. If Trivy is added later for container layers, it joins this job.

Severity policy at start: noise-floor mode for the first two releases, where findings post as PR comments without blocking merge. Once the operator has walked the noise floor down (false positives logged, version conflicts understood), severity is promoted: critical blocks merge, high requires an acknowledgment comment, medium and below are informational. This matches the assessment gap’s “non-blocking PR check; promote to blocking once noise level is known”.

Tools are chosen for two reasons: each is freely available, and each is the default in its ecosystem so onboarding a future contributor is the empty set of new steps to learn.

Component layerVulnerability toolNotes
Python (API, authorizer, ingestion)pip-auditUses the PyPI Advisory Database; OSV-compatible.
Node (SPA)npm auditBacked by the GitHub Advisory Database.
Terraform providerstfsec and checkov are already in CI for IaC misconfiguration. Provider CVEs are rare; the SBOM is the inventory and the standing job catches them.No additional tool for provider CVEs at this scale.
Lambda runtimeAWS publishes deprecation timelines; the operator watches the AWS Lambda runtime support page (calendar reminder).No automated tool.
Container layersTrivy if and when containers enter the picture.Not present today.

How findings flow back into the risk register

Section titled “How findings flow back into the risk register”

The risk register today is the assessment page’s “Gaps to close” table plus the threat model’s priority threats. SBOM-derived findings flow back in via:

  1. PR comment at generation time. The PR author triages: fix in the PR, file a follow-up issue, or accept with a comment that names the assessment row or threat-model row the acceptance maps to.
  2. GitHub issue for findings from the weekly standing scan. The issue uses the existing security label and is auto-assigned to the operator. The issue body links to the SBOM artifact and the advisory.
  3. Assessment update if a finding represents a control gap rather than a single bad version. For example, “pip-audit blocked by a CVE in a transitively-pulled package we cannot upgrade without breaking the API” is an assessment row, not a single issue. The architect-reviewer is the approver of additions to that table.
  4. Threat-model re-run if the finding changes a boundary’s posture. This is rare. An example would be a Mangum CVE that lets a crafted request bypass FastAPI routing; the Browser to Lambda boundary would gain a tampering row.

The flow is explicit so a finding does not silently become an open issue with no home. Every finding ends up in exactly one of: a merged PR, a closed issue with a stated acceptance, a row in the assessment table, or a re-run threat model.

  • A signed SBOM attestation (in-toto, Sigstore). The operator-issued-key model in RAG009 implies the operator trusts their own pipeline; signing the SBOM adds a verification surface (key management, cosign tooling) that the demo does not warrant.
  • A vendor SBOM platform (FOSSA, Snyk, Anchore Enterprise). Same reasoning as RAG008 for observability: vendor accounts and API keys are non-trivial maintenance for a personal demo.
  • The corpus dataset itself. The corpus is content, not a software component. Its provenance is recorded under the data-classification rule and Feature 66/67, not here.

The SBOM scope changes when any of these change:

  • Lambda moves to container packaging — add Trivy, add image layers to scope.
  • A second runtime joins the system (Go, Rust, Java) — add the matching generator and scanner.
  • Compliance scope appears — vendor SBOM platforms become worth pricing, and signed attestations become a likely requirement.
  • The corpus pipeline pulls additional third-party data sources — Feature 66/67 records the data provenance; this page records the new software paths if any.