Software Bill of Materials (SBOM)¶
SolidSyslog publishes a CycloneDX 1.5 SBOM for the shipped library. SBOMs come in three flavours that answer three different questions; this document is only concerned with the first.
| Flavour | Question it answers | Status here |
|---|---|---|
| Product SBOM | "What am I linking against in my deployment?" | Covered by this workflow (see below). |
| Build / dev-env SBOM | "What tools, containers, and test harnesses were used to produce the release?" | Not yet — deferred to a separate story. Meanwhile, every container image is pinned by digest at the point it is used, and Container images names them. |
| Source SBOM | "What third-party source code is embedded in the product?" | Empty — SolidSyslog vendors no third-party source. |
Product SBOM scope¶
In scope:
Core/: Tier 1 (full support, stable API).Platform/: Tier 2 (supported; API may evolve per target).- Root
CMakeLists.txt+CMakePresets.json: the build contract an integrator invokes directly. Tampering here affects the built library. - Root
LICENSE.mdandLICENSES/: the licence terms we are legally bound by and that downstream integrators inherit (seeLICENSE.md). Tampering here is a compliance issue.
Out of scope:
Tests/,Bdd/: test harnesses (Bdd/Targets/holds the BDD-driven binaries, test infrastructure, not product).ci/,docs/,.devcontainer/,.github/,.vscode/: dev/CI infrastructure.sbom/: the SBOM template itself (meta; including it would be self-referential).scripts/: utility scripts not consumed by the integrator.- Other root-level meta files (
CLAUDE.md,SKILL.md,README.md,CHANGELOG.md,.clang-format,.clang-tidy,.gitattributes,.gitignore,.release-please-manifest.json). Informational / agent-facing / git configuration, not library source.
What the SBOM says¶
The SBOM is a single-component document. The subject (metadata.component) is
SolidSyslog, the components array is empty, and the dependency graph records
that it depends on nothing.
That is the whole point of it. SolidSyslog vendors no third-party source, and
Core reaches no further than a C99 compiler and four standard headers —
<stddef.h>, <stdbool.h>, <stdint.h> and <string.h>. It calls memcpy
and strlen, and makes no operating-system calls at all: no OS, no network
stack, no filesystem, no heap. The c99 lane proves it on every pull request
by building Core alone, and the FreeRTOS cross lanes build it with no host
platform present.
Why the platform backends are not components¶
Reference adapters ship for POSIX, Windows, FreeRTOS, lwIP, FreeRTOS-Plus-TCP, ChaN FatFs, FreeRTOS-Plus-FAT, OpenSSL and Mbed TLS, and an integrator may supply their own instead. None of them is a component of SolidSyslog. Each is software the integrator chooses, versions, links and licenses, and the choices are mutually exclusive per role — no build links both OpenSSL and Mbed TLS, or both lwIP and FreeRTOS-Plus-TCP.
Listing them would therefore describe a product that does not exist, and would do real harm: a scanner reading an OpenSSL dependency against a FreeRTOS build that links Mbed TLS reports vulnerabilities in software that is not there. We could pin no version for any of them either, so each entry would look like coverage while carrying nothing a scanner can use.
They are recorded instead as metadata.component.properties
(solidsyslog:runtime-environment and solidsyslog:platform-backends),
alongside an externalReferences link to Adding it to your
build for the capability matrix. Whichever packs you
select are your dependencies and belong in your product SBOM — which is the
document that can state them correctly, because it knows which build you
shipped.
Key fields worth reading:
| Field | Meaning |
|---|---|
metadata.tools.components[0] |
The workflow that rendered this document, versioned by its own commit SHA and linked via a build-system reference. Distinct from metadata.component.purl, which pins the source being described: one says what produced the SBOM, the other what it describes. |
metadata.component.name |
SolidSyslog. |
metadata.component.version |
The value from .release-please-manifest.json at the time of generation. |
metadata.component.purl |
Package URL keyed to the exact commit SHA — unambiguous pointer back to the source. |
metadata.component.supplier.name |
Cozens Software Solutions Limited (COSOSO). |
metadata.component.externalReferences[type=license] |
One per PolyForm term, so a scanner resolves each to its canonical text instead of leaving it unknown. The commercial term has no licence document to point at and carries an other reference to the enquiry route instead. See reading the licence expression. |
metadata.component.licenses[0].expression |
PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial — an SPDX expression, because the library is offered under three alternative licences and the recipient chooses. Only the Noncommercial identifier is on the SPDX License List; the other two are LicenseRef-. |
metadata.properties[solidsyslog:source-tree-sha256] |
Content-tree hash: SHA-256 of a sorted list of <content-sha256> <path> lines for every tracked file in Core/ + Platform/ plus the root-level CMakeLists.txt, CMakePresets.json, LICENSE.md, and LICENSES/, at the commit. Reproducible byte-for-byte from any clone, with no dependency on git archive output format or git version. |
Reading the licence expression¶
Written for a compliance reviewer holding SolidSyslog as an item in a review
queue. If you are choosing a licence rather than reviewing one,
LICENSE.md is the document you want instead.
metadata.component.licenses[0].expression reads:
PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
OR is a choice, not an accumulation. SPDX defines the operator as
alternatives: a recipient relies on one term and complies with that one. The
component is not encumbered by all three at once, and nothing obliges a
recipient to satisfy the noncommercial term if they hold a different one.
This expression is the whole licence question for the component. SolidSyslog
vendors no third-party source, so the components array is empty and there is
no transitive licence graph underneath it — see
why the platform backends are not components.
Why two of the three are LicenseRef-¶
Only PolyForm-Noncommercial-1.0.0 appears on the
SPDX License List. The other two do not, for
different reasons:
| Term | Why it is a LicenseRef- |
|---|---|
LicenseRef-PolyForm-Internal-Use-1.0.0 |
A published, unmodified PolyForm licence that SPDX has not listed. The verbatim text ships in the repository and is linked from LICENSE.md. |
LicenseRef-COSOSO-Commercial |
A negotiated agreement between COSOSO and the licensee. It has no single public text, so no published identifier could describe it. |
If your scanner reports these identifiers as unknown or custom, that result is
expected. A LicenseRef- is how SPDX names a licence its list does not cover,
so there is no template in any scanner's corpus for it to match — and for the
PolyForm term the verbatim text ships in the repository, for a reviewer who
needs to read it.
An unknown identifier is not, by itself, evidence of a licence defect. Neither is it a resolution: the item stays open until you have confirmed which of the three terms your organisation relies on, and hold the evidence for it.
So that the terms resolve to documents rather than to nothing, the SBOM carries
an externalReferences entry of type license for each PolyForm licence,
pointing at its canonical URL. An SPDX expression has nowhere to put a URL,
which is why they are attached to the component instead.
LicenseRef-COSOSO-Commercial has no such entry. There is no public document to
point at, and type license means the URL of a licence file — labelling an
enquiry form as one would resolve the identifier to something that is not a
licence. The enquiry route is carried as an other reference instead, with a
comment saying what it is.
Which term applies¶
| If your organisation is | The term you rely on |
|---|---|
| Using SolidSyslog in the internal business operations of you and your company — which covers evaluation, porting, integration and testing, and internal deployment, commercial organisations included | LicenseRef-PolyForm-Internal-Use-1.0.0 |
| Distributing it for a noncommercial purpose | PolyForm-Noncommercial-1.0.0 |
| Supplying, selling or otherwise making available a commercial product, device, firmware or service containing it | LicenseRef-COSOSO-Commercial |
If you arrived here from a source file rather than from the SBOM, the same
answer applies. Every file under Core/ and Platform/ opens with a
SPDX-License-Identifier carrying this same three-term expression, for the case
where a file is copied into another build and leaves LICENSE.md behind. It is
the same disjunction and it resolves the same way — establish which term your
organisation relies on, and record that one.
This table says only which term to read. The conditions each one attaches are
in LICENSE.md, which is the authoritative statement.
What to record in your own SBOM¶
The expression above is SolidSyslog's declared licence — what COSOSO offers to any recipient. It is not a statement about your organisation's position.
Once you have taken one of the three, record that single term as the concluded licence for this component in your own product SBOM. Carrying the disjunction forward re-raises the same review item on every rebuild, and misstates your position to anyone reading your SBOM downstream.
PolyForm-Noncommercial-1.0.0 is on the SPDX list, so an expression carries
everything a tool needs to identify it:
The other two are LicenseRef- terms, and an expression has nowhere to put a
name or a URL for them — which is the whole reason they resolve as unknown. Use
CycloneDX's named-licence form instead. PolyForm Internal Use is a published
document, so its canonical URL is enough:
"licenses": [
{
"license": {
"name": "PolyForm Internal Use License 1.0.0",
"url": "https://polyformproject.org/licenses/internal-use/1.0.0"
}
}
]
LicenseRef-COSOSO-Commercial needs more again. It names a class of negotiated
agreement rather than your particular contract, so on its own it tells a
downstream reader of your SBOM nothing about what was granted. CycloneDX has
fields for exactly this — use the named-licence form:
"licenses": [
{
"license": {
"name": "COSOSO Commercial Licence",
"url": "https://www.cososo.co.uk/#contact",
"licensing": {
"licensor": {
"organization": { "name": "Cozens Software Solutions Limited" }
},
"licensee": {
"organization": { "name": "<your organisation>" }
},
"purchaseOrder": "<your agreement or PO reference>",
"licenseTypes": ["oem"],
"expiration": "<RFC 3339 timestamp, if your agreement has a term>"
}
}
}
]
licenseTypes takes values from CycloneDX's own enumeration — oem,
appliance, perpetual, subscription and others — so pick whichever
describes your agreement. A licenses array is either a list of named
licences or exactly one expression; the two forms cannot be mixed.
Do not put the agreement itself in license.text. A negotiated commercial
agreement is confidential between the parties, and an SBOM is a document you
distribute. The reference is what belongs here; the terms are not.
The same question applies to purchaseOrder and licensee. They are exactly
right in an internal compliance record, but if you pass this SBOM on to your
own customers, your commercial arrangements travel with it. Decide which of
these fields belong in the copy you distribute and which stay in the copy you
keep — name and url alone are enough to resolve the identifier.
Whichever term applies, keep the evidence for it — the agreement reference for a commercial licence, or a record of the permitted purpose relied on for a PolyForm one — in your compliance record alongside the SBOM entry. That is what lets the next reviewer resolve this without repeating your work.
A policy rule you can adopt¶
Stated in prose rather than a vendor syntax, because the encoding differs across scanning platforms:
SolidSyslog is a disjunctively multi-licensed component. Resolve it to the single term this organisation holds, record that as the concluded licence together with the evidence supporting it, and close the item on that basis. Do not assess the component against the noncommercial term unless that is the term being relied on.
If your review turns up a question this page does not answer, ask before escalating it internally: https://www.cososo.co.uk/#contact.
How to generate one (rehearsal)¶
Each run produces a CycloneDX 1.5 JSON file, validated against the spec by
cyclonedx-cli, and uploaded as
a workflow artifact.
- Open the Actions tab.
- Select the Generate SBOM workflow.
- Click Run workflow, pick the ref (usually
mainor a release tag), and Run workflow. - When the run completes, scroll to Artifacts at the bottom of the run
page and download
sbom-cyclonedx-<version>. - Unzip; the file inside is
sbom.cdx.json.
The workflow uses only the default GITHUB_TOKEN: no repo secrets required.
Sanity-check a generated SBOM¶
cyclonedx validate --input-file sbom.cdx.json --input-format json --input-version v1_5 --fail-on-errors
The CI workflow already runs this; the command is useful if you've fetched the artifact locally and want to re-verify independently.
Verifying a signed SBOM¶
Every GitHub Release created by Release Please gets four assets attached:
| Asset | Contents |
|---|---|
sbom.cdx.json |
The SBOM itself. |
sbom.cdx.json.sigstore |
sigstore/cosign signature bundle — signature + ephemeral signing certificate + Rekor inclusion proof, in a single JSON blob. |
source-tree-sha256.txt |
The content-tree SHA-256 with a human-readable header. Reproducible from any clone at the SBOM's commit with git ls-tree + git show + sha256sum + sort. |
source-tree-sha256.txt.sigstore |
cosign bundle for the above. |
Signing is keyless via GitHub OIDC: no private keys live in this repo.
The signature commits to the specific workflow run (sbom.yml in this repo
at the tagged commit) that produced the SBOM; a verifier checks the
certificate identity against an expected workflow identity to tell "this
SBOM" apart from any other CycloneDX document.
To verify a downloaded asset set:
cosign verify-blob \
--bundle sbom.cdx.json.sigstore \
--certificate-identity "https://github.com/cososo-ltd/solid-syslog/.github/workflows/sbom.yml@refs/tags/v<version>" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
sbom.cdx.json
The same pattern verifies source-tree-sha256.txt.sigstore against source-tree-sha256.txt.
Every cosign signature is also logged to Rekor, Sigstore's public transparency log. Anyone can look up the signature entry by its hash and confirm it was issued at the stated time, independent of whether GitHub, Sigstore, or this project still exist at the time of audit.
For a step-by-step verification guide aimed at downstream integrators, see
release-verification.md.
Out of scope¶
- Signed SLSA provenance attestation. An attestation states "this SBOM was produced by this workflow from these inputs"; the signature above states only "this SBOM was signed by this workflow". The difference matters for a binary the consumer cannot inspect. This project is source-only and the content-tree hash is reproducible from any clone, so the integrator checks the source itself.
- Binary-artefact signing. The project is source-only; nothing to sign beyond the SBOM and content-tree hash.