Skip to main content

License scanning

License scanning answers the what am I allowed to ship question — complementing the SBOM’s what’s in this image and the dependency scan’s what’s vulnerable. fremforge runs Trivy licence detection on every PR, classifies each resolved dependency’s licence into an SPDX-policy severity bucket, and surfaces the result next to the PR and in a dedicated org dashboard. It is on by default for every org — no enable step.

How it works

fremforge’s central scanner (the same one that runs SAST, dependency scanning, and image scanning) runs Trivy’s licence scan over your resolved dependencies on each PR. It runs on fremforge’s own runners — nothing is installed in your repository. Unlike SAST or dependency scanning — which post a problems-only list — license scanning posts a full licence inventory: one finding per (package × licence) across every resolved dependency. The workflow then POSTs the inventory to fremforge:

POST https://frem.sh/_app/api/v1/license-scan/findings

The runner PAT needs the findings:write scope (minted per-repo by the runner controller at workflow-install time). On a successful ingest, fremforge:

  1. Stores the findings. One row per finding in license_scan_findings. Each scan supersedes the prior scan for the same (tenant, repo, pr_number) wholesale — a PR that drops a dependency stops surfacing its stale rows. Push scans (pr_number null) live in their own bucket so they don’t cross-replace PR scans.
  2. Posts a PR comment summarising the findings (severity, licence, package, manifest), via the Forgejo issues API.
  3. Sets the fremforge/license-scan commit status on the PR head SHA — success when advisory-only or all-allowlisted, failure when the policy gate is hit.

The Forgejo-side comment and status are best-effort: a Forgejo outage logs and downgrades to advisory but never fails the ingest. The stored findings are the source of truth; the PR surface is a convenience layer.

Severity buckets

License scanning uses a 7-bucket severity vocabulary (not the 4-tier CRITICAL/HIGH/… used by CVE scanning), matching Trivy’s SPDX-policy classification — ordered from least to most encumbering:

BucketMeaningExample licences
unknownLicence could not be determined (still surfaced for review)NOASSERTION
unencumberedPublic-domain-equivalentCC0-1.0, Unlicense
permissiveUse freely, minimal obligationsMIT, BSD-3-Clause, Apache-2.0
noticePermissive but with attribution / notice obligationsApache-2.0 (notice tier)
reciprocalWeak copyleft — file-level share-alikeMPL-2.0, EPL-2.0
restrictedStrong copyleftGPL-2.0, LGPL-3.0
forbiddenStrictest copyleft / network-copyleftAGPL-3.0, SSPL-1.0

Policy controls

Configure at Org admin → Code security → Licenses:

  • Enabled — when off, ingest returns 202 {accepted: 0, status: 'disabled'} (the workflow still runs, the findings are just dropped). Default-on.
  • Block severity — the threshold at or above which a finding blocks merge. Default forbidden. Any active (non-dismissed) finding at or above this bucket sets the fremforge/license-scan commit status to failure; combine with branch protection requiring the fremforge/license-scan check to actually block merge.
  • Allowlist — a list of SPDX licence IDs that are auto-dismissed at ingest. An allowlisted licence still gets a finding row (audit trail), but with dismissed_at pre-set and actor tenant_allowlist. The allowlist is re-derived fresh on every scan, so removing a licence from the allowlist correctly re-surfaces it on the next scan.

Findings at forbidden (→ critical) or restricted (→ high) that are newly introduced and not dismissed by a scan also fire the real-time security-alert hook; lower buckets are advisory only.

Dismissing findings

An org admin can dismiss individual findings (with a required reason, written to the audit log) from the Licenses tab. Manual dismissals are carried forward across the supersede-on-ingest re-scan, keyed on (licence ID, package, manifest path) — re-scanning a PR doesn’t resurrect a finding an admin already accepted. (Allowlist auto-dismissals are not carried forward; they’re re-derived from settings each scan, as above.)

Where findings surface

Org admin → Code security → Licenses lists active findings and recent dismissals. Each finding shows the severity bucket, the SPDX licence ID, the ecosystem:package, the licence category (declared vs concluded), and the manifest path. The PR comment links back to this dashboard.

Retention

Findings follow the per-tenant audit-retention policy, consistent with the other code-security scanners. Closed-PR findings are swept by the daily /jobs/license-scan-closed-pr-cleanup job, since a closed PR never re-scans.

See also