# SBOM License Policy Checker (`afkdigital/sbom-license-policy-checker`) Actor

Check SPDX 2.3 and CycloneDX 1.6 JSON license assertions against your explicit policy. AND/OR/WITH evidence, JSON/CSV and summary. No source verification or legal advice.

- **URL**: https://apify.com/afkdigital/sbom-license-policy-checker.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / successfully analyzed sbom

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SBOM License Policy Checker

Compare license assertions in your SBOM with your own explicit policy. Get component-level PASS, REVIEW or BLOCK results, exact license-expression evidence, JSON/CSV exports and a policy summary. Designed for release gates, supplier intake and software inventory triage.

**This Actor evaluates SBOM assertions. It does not verify source-code licenses, give legal advice or guarantee license compliance.** A PASS means the supplied assertion matches your configured policy, not that every applicable legal obligation has been met.

### Supported documents in version 1.0.0

- **SPDX 2.3 JSON**: `packages[]`, `licenseDeclared`, `licenseConcluded`, version and package-url external references. File/snippet-level entries are outside this version's analysis scope.
- **CycloneDX 1.6 JSON**: `components[]`, nested components and `metadata.component`; `licenses[].expression` and license `id`/`name`. `acknowledgement: concluded` remains concluded; declared or unspecified acknowledgement is reported in the declared field as an SBOM assertion.
- UTF-8 JSON only. SPDX 2.2/3.x, CycloneDX 1.4/1.5/1.7, XML, YAML, tag-value, RDF, archives, external BOMs, services, license-text scanning and CycloneDX evidence assertions are not supported.

Official versioned JSON Schemas are bundled and validated offline. This is structural validation with additional duplicate-identifier and safety checks, not a complete semantic conformance certification. Version 1.6's pinned SPDX enumeration can reject IDs introduced after that schema snapshot. No schema, package, license URL or external reference from the customer document is fetched.

### Input

Provide exactly one of `sbomJson` (a JSON object) or `sbomFile` (a JSON file uploaded using Apify's file picker). For API use, `sbomFile` must be an HTTPS Apify key-value-store record URL such as `https://api.apify.com/v2/key-value-stores/STORE_ID/records/bom.json`. Your run token must have access, or the URL must carry a valid Apify signature. Redirects, arbitrary websites, local paths and compressed files are rejected. The file is downloaded once from Apify storage and analyzed inside the Actor container.

| Field | Meaning |
| --- | --- |
| `allowedLicenses` | IDs or complete `license WITH exception` pairs that PASS |
| `reviewRequiredLicenses` | IDs/pairs that require REVIEW |
| `blockedLicenses` | IDs/pairs that BLOCK |
| `unknownLicenseAction` | `review` (default), `block`, or `allow` for unknown/custom/missing assertions |
| `maxComponents` | Integer 1–5,000; default 5,000; exceeding it fails, with no silent truncation |

Policy lists default to empty in the engine. The Console example contains an editable demonstration policy. Select rules appropriate for your organization. A recognized license absent from every policy list requires REVIEW. Contradictory lists fail validation. Rules must be single IDs, `LicenseRef` values, `NONE`, `NOASSERTION`, or complete WITH pairs; compound AND/OR policies are not rule-list entries. IDs are matched without case sensitivity; operators are uppercase. `+` is preserved as an exact or-later rule suffix; there is no automatic equivalence mapping to `-or-later` or deprecated IDs.

```json
{
  "sbomJson": {
    "bomFormat": "CycloneDX", "specVersion": "1.6", "version": 1,
    "components": [{
      "type": "library", "bom-ref": "example-lib", "name": "example-lib", "version": "1.2.3",
      "purl": "pkg:npm/example-lib@1.2.3", "licenses": [{"expression": "MIT OR GPL-3.0-only"}]
    }]
  },
  "allowedLicenses": ["MIT", "Apache-2.0"],
  "reviewRequiredLicenses": ["MPL-2.0"],
  "blockedLicenses": ["GPL-3.0-only"],
  "unknownLicenseAction": "review", "maxComponents": 5000
}
```

### Expression and decision rules

- `AND`: every requirement applies; use the strictest result.
- `OR`: alternatives remain alternatives; use the best available policy result. `MIT OR GPL-3.0-only` can PASS when MIT is allowed and GPL is blocked. Both branches remain visible in evidence. This is an available policy choice, not proof the customer has made or fulfilled that choice.
- `WITH`: license plus exception is one exact policy key. A rule for the base license does not automatically allow or block the combined license. Unlisted combinations require REVIEW. Unrecognized or malformed exception expressions require REVIEW, never an inferred PASS.
- Precedence is parentheses, WITH, AND, OR. Expression length, tokens and nesting are bounded.
- `NOASSERTION`, `NONE`, local `LicenseRef-*`, external `DocumentRef-*:LicenseRef-*`, unknown IDs and missing values retain distinct evidence kinds. NONE is not treated as public domain. An explicitly configured atomic rule takes precedence over `unknownLicenseAction`.
- Free-text CycloneDX license names are unverified labels. They are not converted to SPDX IDs.
- Declared and concluded fields are evaluated separately; the component result is the strictest reported result. A concluded value is only the SBOM producer's conclusion; this Actor has not independently verified it.
- Multiple licenses in the same CycloneDX assertion category without an explicit AND/OR expression require REVIEW. Their logical relationship is not inferred.
- Repeated package coordinates with distinct identifiers are retained separately. Duplicate SPDX IDs or CycloneDX bom-ref identifiers are rejected as ambiguous evidence. Missing optional licenses produce a policy result; missing schema-required fields fail document validation.

### Results and exports

The default Dataset has one row per analyzed package/component:
`component_id`, `package_name`, `version`, `purl`, `declared_license`, `concluded_license`, `policy_result`, `matched_rule`, `evidence_pointer`.

`matched_rule` includes per-assertion decisions and the AND requirements or OR alternatives. `evidence_pointer` is a JSON Pointer to the component; `license_evidence_pointers` points to its original license fields. Source values are retained; absent values are null. CycloneDX components lacking bom-ref use their JSON Pointer as an identifier.

Key-value store records: **OUTPUT.json**, **OUTPUT.csv**, **SUMMARY**. SUMMARY includes counts, document/version, data snapshots, limits and the disclaimer. The Dataset also supports Apify JSON/CSV export. The dedicated OUTPUT.csv serializes evidence as JSON and neutralizes spreadsheet formula prefixes; use JSON for exact original scalar values.

### Limits, privacy and execution

Maximum SBOM size: **10 MiB**. Full input: 12 MiB. Up to **5,000 components** including nested and metadata components. JSON: 250,000 nodes, depth 60 and 1 MiB per string. Expressions: 4,096 characters, 256 tokens, depth 32. Worker: 120 seconds wall time, 90 seconds CPU and 192 MiB virtual memory. Container: 256 MiB. Process wall deadline: 240 seconds. Default run timeout: 300 seconds. Generated output has a 20 MiB safety ceiling. Oversized, excessively complex or resource-exhausting documents fail rather than being partially billed.

No customer package is installed or run. Analysis is deterministic and local to the container; no AI service, source repository or package registry receives your SBOM. Runtime network access is limited by the implementation to Apify input/output/billing. Customer names, licenses, document bodies, upload URLs and tokens are not printed to logs. Inputs/results remain in your Apify storage under your account's access and retention settings; this is not zero-retention processing.

### Pricing

**$0.20 per successfully analyzed SBOM**, up to the component limit. One `sbom-analyzed` event per document/run. No per-component, startup or automatic Dataset-item fee. Standard run platform usage is included. Apify's post-run storage/export usage may still apply under its platform terms.

A structurally valid SBOM producing REVIEW or BLOCK results is a successful, paid analysis. Invalid documents, unsupported formats/versions, input safety failures and analysis/storage failures before completion do not trigger the analysis event. All output records are saved before the event is charged. A manual interruption after output completion and accepted billing can still leave a failed/aborted run status; completed work may already have been charged. Do not use run status alone to infer whether an analysis event occurred. Spend caps below $0.20 are rejected before analysis. Each separate successful run is a separate paid analysis.

### Versions, data and acknowledgements

Engine 1.0.0, Python 3.13. `jsonschema 4.26.0`, `attrs 26.1.0`, `referencing 0.37.0`, `rpds-py 2026.9.1`, `jsonschema-specifications 2025.9.1` are hash-pinned, MIT licensed runtime dependencies; original notices are bundled. No package installation scripts from the SBOM run.

SPDX 2.3 JSON Schema: SPDX contributors, CC-BY-3.0, original license preserved. CycloneDX 1.6 JSON Schema and companion schemas: OWASP Foundation, Apache-2.0. License ID data: **spdx-license-ids 3.0.24**, CC0-1.0 (including deprecated IDs). Exception ID data: **spdx-exceptions 2.5.0**, CC-BY-3.0, from [spdx-exceptions.json](https://github.com/kemitchell/spdx-exceptions.json). Data is bundled unchanged. These snapshots were checked against SPDX License List 3.29.0; the exception package contains a subset, and newer exception IDs are conservatively reviewed as unrecognized. No automatic list update occurs during customer runs. Full attribution and license texts are in the image's `notices/` directory.

Sources: [SPDX 2.3](https://spdx.github.io/spdx-spec/v2.3/), [CycloneDX 1.6](https://cyclonedx.org/docs/1.6/json/), [SPDX License List](https://spdx.org/licenses/), [Apify pay per event](https://docs.apify.com/actors/monetize/pay-per-event). SPDX and CycloneDX are their respective owners' marks; this Actor is an independent AFK Digital product.

# Actor input Schema

## `sbomJson` (type: `object`):

Paste a SPDX 2.3 or CycloneDX 1.6 JSON object. Provide this OR sbomFile.

## `sbomFile` (type: `string`):

Apify-uploaded UTF-8 JSON file, max 10 MiB. Remove sbomJson when using a file.

## `allowedLicenses` (type: `array`):

Exact license IDs or complete WITH pairs. Contradictory lists are rejected.

## `reviewRequiredLicenses` (type: `array`):

Exact license IDs or complete WITH pairs. Contradictory lists are rejected.

## `blockedLicenses` (type: `array`):

Exact license IDs or complete WITH pairs. Contradictory lists are rejected.

## `unknownLicenseAction` (type: `string`):

Known but unlisted licenses and malformed expressions always require review.

## `maxComponents` (type: `integer`):

Exceeding this cap fails the entire document without an analysis event.

## Actor input object example

```json
{
  "sbomJson": {
    "bomFormat": "CycloneDX",
    "specVersion": "1.6",
    "version": 1,
    "components": [
      {
        "type": "library",
        "bom-ref": "pkg-0",
        "name": "pkg-0",
        "licenses": [
          {
            "expression": "MIT OR GPL-3.0-only"
          }
        ]
      },
      {
        "type": "library",
        "bom-ref": "pkg-1",
        "name": "pkg-1",
        "licenses": [
          {
            "expression": "MIT AND GPL-3.0-only"
          }
        ]
      },
      {
        "type": "library",
        "bom-ref": "pkg-2",
        "name": "pkg-2",
        "licenses": [
          {
            "expression": "NOASSERTION"
          }
        ]
      }
    ]
  },
  "allowedLicenses": [
    "MIT",
    "Apache-2.0"
  ],
  "reviewRequiredLicenses": [
    "MPL-2.0"
  ],
  "blockedLicenses": [
    "GPL-3.0-only"
  ],
  "unknownLicenseAction": "review",
  "maxComponents": 5000
}
```

# Actor output Schema

## `components` (type: `string`):

No description

## `json` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sbomJson": {
        "bomFormat": "CycloneDX",
        "specVersion": "1.6",
        "version": 1,
        "components": [
            {
                "type": "library",
                "bom-ref": "pkg-0",
                "name": "pkg-0",
                "licenses": [
                    {
                        "expression": "MIT OR GPL-3.0-only"
                    }
                ]
            },
            {
                "type": "library",
                "bom-ref": "pkg-1",
                "name": "pkg-1",
                "licenses": [
                    {
                        "expression": "MIT AND GPL-3.0-only"
                    }
                ]
            },
            {
                "type": "library",
                "bom-ref": "pkg-2",
                "name": "pkg-2",
                "licenses": [
                    {
                        "expression": "NOASSERTION"
                    }
                ]
            }
        ]
    },
    "allowedLicenses": [
        "MIT",
        "Apache-2.0"
    ],
    "reviewRequiredLicenses": [
        "MPL-2.0"
    ],
    "blockedLicenses": [
        "GPL-3.0-only"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("afkdigital/sbom-license-policy-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "sbomJson": {
        "bomFormat": "CycloneDX",
        "specVersion": "1.6",
        "version": 1,
        "components": [
            {
                "type": "library",
                "bom-ref": "pkg-0",
                "name": "pkg-0",
                "licenses": [{ "expression": "MIT OR GPL-3.0-only" }],
            },
            {
                "type": "library",
                "bom-ref": "pkg-1",
                "name": "pkg-1",
                "licenses": [{ "expression": "MIT AND GPL-3.0-only" }],
            },
            {
                "type": "library",
                "bom-ref": "pkg-2",
                "name": "pkg-2",
                "licenses": [{ "expression": "NOASSERTION" }],
            },
        ],
    },
    "allowedLicenses": [
        "MIT",
        "Apache-2.0",
    ],
    "reviewRequiredLicenses": ["MPL-2.0"],
    "blockedLicenses": ["GPL-3.0-only"],
}

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/sbom-license-policy-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sbomJson": {
    "bomFormat": "CycloneDX",
    "specVersion": "1.6",
    "version": 1,
    "components": [
      {
        "type": "library",
        "bom-ref": "pkg-0",
        "name": "pkg-0",
        "licenses": [
          {
            "expression": "MIT OR GPL-3.0-only"
          }
        ]
      },
      {
        "type": "library",
        "bom-ref": "pkg-1",
        "name": "pkg-1",
        "licenses": [
          {
            "expression": "MIT AND GPL-3.0-only"
          }
        ]
      },
      {
        "type": "library",
        "bom-ref": "pkg-2",
        "name": "pkg-2",
        "licenses": [
          {
            "expression": "NOASSERTION"
          }
        ]
      }
    ]
  },
  "allowedLicenses": [
    "MIT",
    "Apache-2.0"
  ],
  "reviewRequiredLicenses": [
    "MPL-2.0"
  ],
  "blockedLicenses": [
    "GPL-3.0-only"
  ]
}' |
apify call afkdigital/sbom-license-policy-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/sbom-license-policy-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uwtSCp64RxiL2yyNG/builds/P4J7klx75yfqiX3Va/openapi.json
