diff --git a/docs/security/sbom.md b/docs/security/sbom.md index 14925c67..5122f698 100644 --- a/docs/security/sbom.md +++ b/docs/security/sbom.md @@ -79,9 +79,171 @@ Key fields worth reading: | `metadata.component.version` | The value from `.release-please-manifest.json` at the time of generation. Pre-release: `0.0.0`. | | `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](#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 ` ` 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`](../../LICENSE.md) is the document you want instead. + +`metadata.component.licenses[0].expression` reads: + +```text +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-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](https://spdx.org/licenses/). 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`](../../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` | + +This table says only which term to read. The conditions each one attaches are +in [`LICENSE.md`](../../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: + +```json +"licenses": [ + { "expression": "PolyForm-Noncommercial-1.0.0" } +] +``` + +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: + +```json +"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: + +```json +"licenses": [ + { + "license": { + "name": "COSOSO Commercial Licence", + "url": "https://www.cososo.co.uk/#contact", + "licensing": { + "licensor": { + "organization": { "name": "Cozens Software Solutions Limited" } + }, + "licensee": { + "organization": { "name": "" } + }, + "purchaseOrder": "", + "licenseTypes": ["oem"], + "expiration": "" + } + } + } +] +``` + +`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: . + ## How to generate one (rehearsal) Each run produces a CycloneDX 1.5 JSON file, validated against the spec by diff --git a/sbom/sbom.cdx.json.template b/sbom/sbom.cdx.json.template index c6f089e2..f624ce75 100644 --- a/sbom/sbom.cdx.json.template +++ b/sbom/sbom.cdx.json.template @@ -51,6 +51,21 @@ } ], "externalReferences": [ + { + "type": "license", + "url": "https://polyformproject.org/licenses/noncommercial/1.0.0", + "comment": "Full text for PolyForm-Noncommercial-1.0.0. A verbatim copy also ships at LICENSES/PolyForm-Noncommercial-1.0.0.md and is covered by solidsyslog:source-tree-sha256." + }, + { + "type": "license", + "url": "https://polyformproject.org/licenses/internal-use/1.0.0", + "comment": "Full text for LicenseRef-PolyForm-Internal-Use-1.0.0, which SPDX does not list. A verbatim copy also ships at LICENSES/PolyForm-Internal-Use-1.0.0.md and is covered by solidsyslog:source-tree-sha256." + }, + { + "type": "other", + "url": "https://www.cososo.co.uk/#contact", + "comment": "How to obtain LicenseRef-COSOSO-Commercial. Deliberately not type=license: that type means the URL of a licence file, and this agreement is negotiated per licensee with no public document to link. Labelling an enquiry form as a licence would resolve the identifier to something that is not one." + }, { "type": "vcs", "url": "https://github.com/cososo-ltd/solid-syslog"