Skip to content

license-check

End-to-end license-compliance check: SBOM, SPDX classification, allow/review/deny gate, remediation, NOTICE, and an audit artifact.

Run an end-to-end license-compliance check on the current project per spec/project/license-check/ and produce a license-check audit artifact. Dispatches license-check-scanner agent for the read-only inventory (SBOM with resolved licenses, SPDX identification, category classification), then applies the permissive-leaning allow/review/deny policy gate against the project's own outbound license, drives per-finding remediation (replace / exception with rationale / satisfy the obligation), verifies attribution/NOTICE and REUSE, records AI provenance, and writes the artifact under .audits/license-check/. Invoke when the user asks to "run a license check," "check license compliance," "audit licenses," "do a Lizenzcheck," "prüfe die Lizenzen," or for a pre-PR / pre-release license gate. Don't use for CVE / vulnerability scanning (that's dependency-audit) or for choosing the project's own outbound license. Supports resume on re-invocation per spec/claude/resumable-work/.

Use when

  • you want a full license-compliance check on the current project
  • you want a pre-PR or pre-release license gate with an allow/review/deny policy
  • you want to verify own-code REUSE state, attribution/NOTICE, and AI provenance

Don't use when

See also

Referenced by


License Check

Run the end-to-end license-compliance process on the current project and produce a single license-check audit artifact. This skill reports, gates, and (with confirmation) drives remediation; it never edits a LICENSE, allowlist, or dependency on its own.

Implements spec/project/license-check/ — the spec defines the pipeline, the SPDX anchoring, the license categories and their obligations, the compatibility rules, the default policy, and the AI-provenance requirements. This skill is the license-compliance authority; dependency-audit's license pass implements this spec's policy for the dependency slice only.

German trigger phrases

This skill also triggers on equivalent German-language requests, including:

  • "Lizenzcheck durchführen" / "ausführlichen Lizenzcheck machen"
  • "Lizenz-Compliance prüfen"
  • "Lizenzen auditieren"

User-language policy

Detect the user's language from their message and respond in it. The audit artifact uses English section headings (so downstream tooling can parse it reliably); prose around it is localised.

Inputs

  • Repo root: default is the current working directory.
  • Outbound license: read from the root LICENSE. This is the compatibility anchor; every conveyed dependency is checked against it. Stop and ask when no LICENSE exists rather than guessing.
  • Policy: the permissive-leaning default from spec/project/license-check/ §Default policy, overridable only by a recorded, rationale-bearing repository policy.

The pipeline

Run the spec's ordered chain; each stage consumes the previous one:

Inventory/Discovery → SBOM → SPDX identification → classification → policy gate → remediation → attribution/NOTICE → continuous CI check

The SBOM stage is generated by this skill (it may install and write); the inventory, SPDX-identification, and classification stages are delegated to the read-only scanner agent; the policy gate through the CI check stay in this skill.

Operations

0. Generate the SBOM, then dispatch the read-only scan agent

First generate the SBOM yourself — a skill may install and write, the read-only scanner agent may not. Run the repo's task license:sbom target when it exists; otherwise generate per stack (Python: cyclonedx-py environment against an ephemeral venv, removed after; Node: cyclonedx-node-npm; Go: cyclonedx-gomod). Use the Python environment mode, not requirements mode, because only the former resolves licenses, including transitive ones.

Then dispatch license-check-scanner (Agent) for the read-only inventory, passing the repo root, the generated SBOM path, and the outbound license read in Inputs. The agent reads the SBOM, maps every component to a canonical SPDX identifier, classifies each into a category, resolves any remaining gaps read-only, and returns the project's own outbound license plus REUSE state and AI-provenance hints. Wait for its inventory before applying the gate.

1. Determine conveyed vs. arm's-length

Per spec/project/license-check/ §Scope, mark every component the scanner returned as either conveyed (shipped, linked, or offered over a network as part of the product) or executed at arm's length (build / dev / docs / CI tool, not shipped). When the use context can't be determined, record unknown-use-context and route the component to review. This distinction decides whether a strong-/network-copyleft finding is deny or review.

2. Apply the policy gate

Apply the three-tier default policy from the spec, anchored to the outbound license:

  • allow (passes automatically): the permissive and public-domain categories (the default allowlist is SPDX-anchored and seeded from the CNCF set: 0BSD, BSD-2-Clause, BSD-3-Clause, MIT, MIT-0, ISC, Apache-2.0, PSF-2.0, Python-2.0, PostgreSQL, Zlib, X11, Unlicense, CC0-1.0).
  • review (manual decision, gate is blocked not pass): weak/file-level copyleft; source-available / restricted; open-weight model licenses; the BSD-4-Clause advertising-clause exception; any LicenseRef-* / NOASSERTION; any combination not positively confirmed compatible with the outbound license; strong-/network-copyleft components that are arm's-length only.
  • deny (fails automatically): strong copyleft (GPL family) and network copyleft (AGPL) in any conveyed, linked, or networked component.

Run the compatibility checks from the spec against the outbound license (for example: Apache-2.0 may go into a GPLv3 work but not the reverse; Apache-2.0 is incompatible with GPLv2). Route any combination you can't positively confirm to review. Never downgrade a category on local judgement; disagreement is a recorded exception with rationale, not a reclassification.

3. Remediate (per non-allow finding, with confirmation)

Per spec/project/license-check/ §Remediation, every non-allow finding gets exactly one of three responses inside a bounded window. Don't execute any of these without explicit confirmation:

  • replace: swap the component for a compatibly-licensed alternative.
  • exception with rationale: record a named, time-bounded (valid-until, ISO 8601) exception with a one-line rationale and the approver; permitted for review findings and, only with explicit sign-off, for a deny-tier override. Revisit each on its valid-until date; never renew without a fresh rationale.
  • satisfy the obligation: keep the component and produce what its license requires (attribution/NOTICE entry; source-availability offer; relink-capable form for LGPL).

Never silence a finding by editing the allowlist without a rationale or by reclassifying the license.

4. Own-code, attribution/NOTICE, and AI provenance

  • Confirm the root LICENSE declares a valid outbound SPDX identifier, and report the REUSE state the scanner returned (LICENSES/<id>.txt + per-file or blanket REUSE.toml). When REUSE is not configured, record it as a SHOULD gap.
  • Verify the third-party attribution/NOTICE output the dependency licenses oblige exists. The verified cross-stack default generator is the ORT reporter's NOTICE templates (PlainTextTemplate / NOTICE_SUMMARY); for Go, go-licenses save. For Apache-2.0 dependencies, confirm NOTICE contents are propagated. When the plugin/product ships no third-party code, record that no third-party NOTICE is obligated.
  • Record machine-readable AI provenance for every committed AI-generated artefact (generator / model / tier). Classify open-weight model licenses as review, not allow, and pin the model artefact version. Keep "license laundering" framing labelled as interpretation, not as a primary-source claim.

5. Persist the audit artifact

Per spec/project/license-check/ §Audit artifact, write the result to the portfolio-wide .audits/license-check/ path (default .audits/license-check/license-YYYY-MM-DD.md). The artifact MUST record: date; trigger (pre-PR / pre-release / manifest-change); scope (stacks and subroots checked / skipped, with reasons); the tool(s) and version(s) used; the pinned SPDX License List version; the per-component SPDX identifier, category, policy tier, and response decision; the conveyed/arm's-length status of every strong-/network-copyleft component; and the Git revision checked. Link to the prior artifact so the progression stays traceable.

6. Gate result and CI

Report the gate result: pass (no deny, every non-allow finding has a response) or blocked (open review findings or an unaddressed deny). Per the spec, the check runs in CI on changes to a dependency manifest, lockfile, the LICENSE file, or committed AI-generated artefacts, and before every release tag; it shares cadence and artifact conventions with dependency-audit rather than duplicating it.

Report shape

```text

License Check

Scope: , stacks: , components ( direct, transitive); skipped: Trigger: Outbound license (anchor): REUSE: SPDX List version: Tools (with versions): Git revision:

Policy gate

  • allow:
  • review: (blocked until each has a response)
  • deny: (must be replaced or under a signed-off, time-bounded exception)

Findings (non-allow only, grouped by tier)

deny —

  • @ — use-context: — response: , approver \) | satisfy>

review —

(same structure)

Own code & attribution

  • Outbound LICENSE: (valid: yes/no)
  • REUSE: (LICENSES/, REUSE.toml)
  • Third-party NOTICE:

AI provenance

  • — output license: — provenance recorded: yes/no
  • Open-weight model licenses:

Verdict

```

Gotchas

  • A license SBOM needs resolved licenses, not just a dependency list. cyclonedx-py requirements lists components without licenses; only cyclonedx-py environment (against an installed venv) or the repo's task license:sbom target resolves licenses, including transitive ones. The scanner prefers task license:sbom; verify the SBOM's "components with resolved license" count matches the component count before trusting a clean result.
  • Transitive copyleft hides behind permissive top-level manifests. A top-level-only scan misses weak-copyleft transitives (for example certifi / pathspec under MPL-2.0). Always gate on the transitive SBOM, not the manifest.
  • The conveyed/arm's-length split is the load-bearing decision for copyleft. The same GPL tool is deny when shipped and review when only executed at build time. Decide it per component; default to review (unknown-use-context) when unsure, never to allow.
  • go-licenses and Syft can mislabel. go-licenses warns on non-Go code (which can conceal further requirements) and can fail to resolve a license URL; Syft historically marked unmatched license text as "unlicensed" before its include-unknown-license-content option. Treat any unresolved component as review, never a silent pass.
  • The FSF static=dynamic-linking position is the conservative default, not settled law. Use it to gate, but record in the artifact that it is the FSF interpretation.

Examples

  • Read examples/01-permissive-python-pass.md when running the first check on a permissive project that should pass cleanly.
  • Read examples/02-transitive-mpl-review.md when a transitive dependency carries weak (file-level) copyleft and must be routed to review.
  • Read examples/03-conveyed-gpl-deny.md when a conveyed component carries strong copyleft and the gate must deny it.

Resumability

Per spec/claude/resumable-work/, this skill is resumable: true. State is persisted to .resume/license-check/<run-id>.yml after every successful user-approval gate and after each named phase boundary. On re-invocation, scan that directory for files with status: in_progress whose inputs: snapshot matches the current invocation; if one matches, prompt the operator with Resume run <run_id> from phase <phase> (last checkpoint <last_checkpoint_at>)? [resume / start-new / discard]. The state-file envelope (schema_version, run_id, inputs, phase, decisions[], status, ...) and the fail-closed semantics on schema or YAML errors are load-bearing in the spec; don't duplicate those rules here.

Source triangulation

Per spec/claude/research-triangulate/, before this skill presents any repo-external assertion beyond a tool's own machine-readable output — a license's compatibility ruling, an SPDX identifier resolved by hand, or a generator's output-license terms — triangulate it instead of trusting a single source:

  • Independent sources by blast radius. At least two independent sources; at least three when the assertion will direct a write outside the working copy (a deny-tier override, a sister-repo path).
  • Anchor in SPDX. Resolve a license's full text via its SPDX identifier (spdx.org/licenses/<ID>.json) so the obligations behind any finding are checkable; pin the SPDX List version in the artifact.
  • Surface conflicts, never silent-vote. When sources disagree on a license's category or compatibility, name the most likely explanation and let the operator decide.
  • Mark unverified when under-triangulated. If the required source count is unreachable, mark the assertion unverified and route the component to review.

Hard rules

  • Never edit a LICENSE, REUSE.toml, allowlist, NOTICE, or dependency without explicit user confirmation. This skill reports and gates; mutations are a confirmed follow-up.
  • Never place a conveyed or networked strong-/network-copyleft component in allow. It is deny unless replaced or under a signed-off, time-bounded exception.
  • Never downgrade a license's category on local judgement. Disagreement is a recorded exception with rationale, not a reclassification.
  • Never treat an unresolved or unmatched license as a clean pass. It is review.
  • Never classify an open-weight model license (OpenRAIL / Llama / Gemma custom terms) as allow; it is review, with the model artefact version pinned.
  • Never run a CVE / vulnerability scan or duplicate dependency-audit; consume its license pass as an implementer of this spec's policy, not as a competing authority.
  • Always anchor every finding in a canonical SPDX identifier (or an explicit LicenseRef-* / NOASSERTION) whose full text is resolvable.
  • Always gate on the transitive SBOM, and record the conveyed/arm's-length status of every copyleft finding.
  • Always persist the audit artifact under .audits/license-check/ with the pinned SPDX List version and the Git revision.
  • When spec/project/license-check/ and this skill disagree, the spec wins; this skill needs the update.

Why this is a skill, not an agent

This skill follows the hybrid pattern: the read-only inventory phase is delegated to the license-check-scanner agent (context-window isolation, tool restriction), while the policy gate and follow-up actions stay in the skill.

  • Orchestration role: typical callers run this as one step inside a larger flow (pre-PR gate, release cut, periodic compliance review); the output flows back into the main conversation so the operator can triage.
  • Mid-flow interactivity: the remediation actions in Step 3 need per-finding user approval — replacing a dependency, recording a dated exception, generating a NOTICE — so the interactivity favours the skill side.
  • Persistent artifact: the deliverable is an on-disk audit artifact under .audits/license-check/; skills own persistent state.
  • Hybrid split: the pure inventory half (detect stack, generate SBOM, resolve SPDX, classify) is self-contained and benefits from context-window isolation; the license-check-scanner agent handles it. The policy gate and remediation stay here so the operator can decide each tier interactively.