Writing your own profile
Profiles are YAML data, not code. Writing one needs no Python and no fork.
- How a profile is evaluated
- The smallest useful profile
- Extending an existing profile
- Dropping a rule you disagree with
- Adjusting the score without changing pass or fail
- Adding license policy
- Writing a regional profile
- Where profiles are found
- Checking your work
How a profile is evaluated
Worth understanding before you write one, because it explains why the file looks the way it does.
- Detect. The document’s format, spec version, and encoding are read from the document itself.
- Schema. The document is validated structurally against the version it
declares, then against the profile’s
schemablock (version floors, signature, deprecated versions). - Rules. Each rule in
rulesruns at itsscope: once fordocument, once per component forcomponent, or over the graph fordependency. A rule names afieldand a list ofvalidators. - License policy. If the profile has a
license_policy, every declared license is evaluated for itsuse_case. - Verdict and score. Findings are folded into one verdict by severity, and
into five category scores by
category, weighted by the profile’sscoring.
Every layer is driven by the same file, which is why one --profile argument
answers all three questions.
The smallest useful profile
Three fields of identity and one rule:
id: acme-minimum
name: ACME internal SBOM minimum
rules:
- id: acme-purl
scope: component
severity: MUST
category: Completeness
citation: "ACME Engineering Standard 7.4"
field: purl
validators: [present, purl_wellformed]
Save it as acme-minimum.yaml in a directory of your own and run it:
ossbomer validate --profile acme-minimum --file sbom.json --profile-path ./profiles
The filename should match the id, since that is what the search resolves.
Extending an existing profile
Usually you do not want to start from nothing. You want an existing standard plus your own additions:
id: acme-shipping-bar
name: ACME shipping bar
extends: [eu-cra-annex-i, fedramp-sbom]
rules:
- id: acme-namespace-tag
scope: component
severity: MUST
category: Provenance
citation: "ACME Engineering Standard 7.4"
field: purl
validators: [present]
extends pulls in every rule from those profiles. Your rules are added on top.
Because you reference the public profiles by id rather than copying them, catalog
updates reach you without a merge.
Dropping a rule you disagree with
excludes removes inherited rules by id:
id: acme-shipping-bar
extends: [eu-cra-annex-i]
excludes: [cra-top-level-dependencies]
Rule ids are effectively API, so name yours deliberately if others might extend your profile.
Prefer excludes over lowering a severity. An excluded rule is visibly absent, and
the next person can see the decision was made. A quietly downgraded MUST looks like
the standard simply does not require it.
Adjusting the score without changing pass or fail
Weights change what the score emphasizes, and never change the verdict:
id: acme-provenance-heavy
extends: [ntia-min-elements]
scoring:
weights:
Completeness: 0.10
Accuracy: 0.20
Consistency: 0.10
Provenance: 0.55
Freshness: 0.05
Weights are normalized, so they need not sum to 1.0. Set a category to 0 to keep it reported but out of the composite.
Adding license policy
Bind a use case, and optionally override individual identifiers:
id: acme-mobile
name: ACME mobile release gate
extends: [eu-cra-annex-i]
license_policy:
engine: ospac
use_case: mobile
rules:
- spdx_id: LGPL-2.1-only
allowed: true
reason: "Reviewed 2026-03; dynamically linked only."
Always write the reason. It appears in the report, and it is what stops someone
re-litigating the exception in a year.
Drop engine if you want only your inline rules, which removes the ospac
dependency entirely. Details on
License policy.
Writing a regional profile
If you are filling one of the uncovered jurisdictions, the work is transcription rather than invention:
- Get the actual published guidance.
- For each requirement, decide the scope, the field, and the severity the document itself assigns. Do not promote a recommendation to MUST because it seems important.
- Put the clause in
citation, precisely enough that a reader can find it. - Fill in
sourceswith the document name, reference, and URL.
id: acme-jp-meti
name: METI SBOM guidance (unofficial)
version: "2.0"
sources:
- name: METI
ref: "Guidance on Introduction of Software Bill of Materials (SBOM), v2.0"
url: https://www.meti.go.jp/
Mark unofficial transcriptions as unofficial in the name, as above. A profile
that reads like an authoritative implementation of a standard, but was assembled
from a summary, produces confident and wrong compliance answers.
Where profiles are found
In order:
- Directories passed to
--profile-path - Directories in
OSSBOMER_PROFILE_PATH, separated by the platform path separator - The bundled catalog
The first match wins, so an overlay sharing an id with a shipped profile replaces
it. --profile also accepts a path to a file directly, which skips the search.
export OSSBOMER_PROFILE_PATH=/opt/acme/profiles
ossbomer validate --profile acme-shipping-bar --file sbom.json
Checking your work
ossbomer profiles --profile-path ./profiles # is it found, and is the name right?
ossbomer validators # which validators can I call?
A profile that fails to load is still listed by id, so if you see the id without a name, the file has a syntax problem.
Run it against an SBOM you already understand. A new profile that passes everything
on its first run usually means a typo in a field name rather than a clean bill of
health.
Full key-by-key detail is in Profile format.