# Legacy Code License Scanner (`codeclouds/legacy-code-license-scanner`) Actor

Scans a git repository or pasted package.json/requirements.txt for declared and dependency licenses, and reports license-compatibility conflicts (e.g. GPL alongside proprietary code).

- **URL**: https://apify.com/codeclouds/legacy-code-license-scanner.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 license report generateds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Legacy Code License Scanner

Scans a git repository — or a pasted `package.json` / `requirements.txt` — for declared and
dependency licenses, and reports **license-compatibility conflicts**: which combinations of
licenses across the project actually clash (e.g. a GPL-licensed dependency pulled into an
otherwise proprietary/closed-source codebase, or two incompatible GPL-family versions).

### When should an AI agent use this?

- "Before I add this npm package as a dependency, does its license conflict with our project?"
- "Audit this repository for GPL dependencies that shouldn't be in a proprietary codebase."
- "What license is this GitHub repo actually under, and does it match what the README claims?"
- "Check this list of Python dependencies (requirements.txt) for license conflicts before we
  vendor them."
- "I'm reviewing a legacy/inherited codebase — what licenses does it depend on, and are any of
  them a legal risk?"
- "Generate a license-compliance report for this repository as part of an acquisition/audit
  checklist."

A coding agent that adds dependencies or takes over/refactors a repository needs a deterministic
license check before proposing a package or generating an audit report — this is exactly the kind
of factual, repeatable verification a tool should perform rather than an LLM "recognizing"
license text from training data (with the risk of stale or wrong assumptions).

### What this Actor does

- Accepts a public git repository URL (shallow-cloned, no `git` binary required) **and/or** one or
  more manifest files pasted directly as text (`package.json`, `requirements.txt`)
- Detects the **project's own declared license** from its `LICENSE`/`COPYING`/`NOTICE` file (text
  pattern matching against ~15 common OSS license texts) or its `package.json` `"license"` field
- Detects the **license of every dependency** by querying the public npm registry and PyPI JSON
  API live (no local install, no `node_modules`/virtualenv needed)
- Runs a **license-compatibility check** — its own compact compatibility matrix — across the
  project's license and every dependency's license, flagging:
  - A copyleft license (GPL/AGPL) alongside a proprietary/unlicensed project — a real conflict
  - GPL-2.0-only alongside Apache-2.0 — the well-documented one-way incompatibility (patent clause)
  - Two version-locked GPL-family licenses that can't be combined (e.g. GPL-2.0-only + GPL-3.0-only)
  - A weak-copyleft license (LGPL/MPL) alongside proprietary code — flagged as a warning to review,
    since the actual conflict depends on how the code is combined (dynamic linking vs. static)
  - Any dependency whose license could not be determined — flagged for manual review
- Returns clean, flat JSON records: one project-summary record, one record per checked dependency,
  and one record per conflict/warning finding

### Input

| Field | Type | Description | Example |
|---|---|---|---|
| `repoUrl` | string | Public git repository to clone (`https://`/`http://` only) | `"https://github.com/expressjs/express.git"` |
| `gitRef` | string | Branch/tag/commit to check out (default: repo's default branch) | `"main"` |
| `manifests` | array of `{filename, content}` | Manifest contents pasted directly, instead of or alongside `repoUrl` | see below |
| `checkDependencyLicenses` | boolean | Look up each dependency's license via npm/PyPI (default `true`) | `true` |
| `maxDependencies` | integer | Cap on dependencies checked in one run, 1-500 (default `200`) | `200` |
| `assumeProprietaryIfUnlicensed` | boolean | Treat an undeclared license as proprietary/all-rights-reserved — the real legal default (default `true`) | `true` |

At least one of `repoUrl` or `manifests` is required.

```json
{
  "manifests": [
    {
      "filename": "package.json",
      "content": "{\"name\":\"my-app\",\"dependencies\":{\"some-gpl-lib\":\"^1.0.0\"}}"
    }
  ]
}
```

### Output

One `"project"` record per run:

```json
{
  "recordType": "project",
  "scanId": "b1e6...",
  "input": "1 inline manifest(s)",
  "projectLicenseSpdxId": "UNLICENSED",
  "projectLicenseSource": "manifest-declared",
  "projectLicenseConfidence": "low",
  "assumedProprietary": true,
  "totalDependenciesFound": 2,
  "totalDependenciesChecked": 2,
  "licensesDetected": ["GPL-3.0-only", "ISC", "UNLICENSED"],
  "conflictCount": 1,
  "errorCount": 1,
  "warningCount": 0,
  "highestSeverity": "error",
  "error": null
}
```

One `"dependency"` record per checked dependency:

```json
{
  "recordType": "dependency",
  "scanId": "b1e6...",
  "name": "some-gpl-lib",
  "ecosystem": "npm",
  "versionRange": "^1.0.0",
  "resolvedVersion": "1.2.0",
  "spdxId": "GPL-3.0-only",
  "rawLicense": "GPL-3.0-only",
  "category": "strong-copyleft",
  "source": "npm-registry",
  "lookupError": null
}
```

One `"conflict"` record per finding:

```json
{
  "recordType": "conflict",
  "scanId": "b1e6...",
  "severity": "error",
  "licenseA": "UNLICENSED",
  "licenseB": "GPL-3.0-only",
  "reason": "GPL-3.0-only is a copyleft license that requires derivative/combined works to be released under a compatible copyleft license. Combining it with UNLICENSED (proprietary/closed-source, or no license declared) is a licensing conflict unless the proprietary code is dual-licensed or the copyleft component is isolated as a genuinely separate program.",
  "affected": ["(project)", "some-gpl-lib"]
}
```

`severity` is one of `error` (a real, well-documented conflict), `warning` (needs manual review —
an unknown license, or a weak-copyleft/proprietary combination that depends on how the code is
combined), or `info` (no conflicts found).

### Using this Actor as an MCP tool for AI agents

Every public Apify Actor is automatically exposed as a callable "tool" via Apify's MCP server.
This Actor is designed for that use case specifically:

- **Flat, typed output** — no nested blobs; every field is a string/number/boolean/array of
  strings, easy for an agent to reason about or filter on (`severity === "error"`)
- **Two independent input modes** — an agent that already has manifest text in context (e.g. from
  a file it just read) can skip the repo clone entirely and pass `manifests` directly
- **Deterministic, source-cited output** — `source` on each dependency record says exactly where
  the license came from (`npm-registry`, `pypi-registry`, `license-file`, `package.json-field`,
  `manifest-declared`), so an agent (or a human reviewing its work) can verify the claim
- **Bounded cost/runtime** — `maxDependencies` and a per-request timeout on every registry call
  prevent a single run from becoming unexpectedly large or slow

### Use cases

- Pre-merge/pre-dependency-add CI check: does this new package introduce a license conflict?
- Due-diligence license audit before an acquisition, open-sourcing, or code-sale
- Legacy/inherited codebase triage: "what are we actually running, license-wise?"
- Spot-checking a vendored or forked repository's license before redistribution

### How license detection works (and its limits)

This Actor deliberately does **not** wrap a heavyweight tool like the Python-based ScanCode
Toolkit — see the architecture note in the project's internal documentation. Instead it combines:

1. **Text-pattern matching** against the full text of ~15 common OSS licenses (MIT, Apache-2.0,
   the BSD/ISC family, the GPL/LGPL/AGPL family with version and "or-later" detection, MPL-2.0,
   EPL, Unlicense, CC0-1.0) for the project's own `LICENSE` file — high-confidence when the file is
   a near-verbatim copy of a standard license text, as the vast majority of open-source projects
   use.
2. **Live registry metadata** (`registry.npmjs.org`, `pypi.org/pypi/<name>/json`) for every
   dependency — the same source npm/pip themselves rely on, no local install needed.
3. **A compact, hand-written compatibility matrix** covering the well-documented conflict patterns
   (copyleft vs. proprietary, GPLv2/Apache-2.0, GPL-family version mismatches) rather than
   attempting exhaustive SPDX-expression algebra.

This is a pragmatic, fast MVP-grade scanner — not a substitute for a legal opinion. A license the
registry has no metadata for, or a custom/modified license text, is reported as **unknown** rather
than guessed. Always have a qualified professional review a genuine compliance-critical finding.

### Legal

This Actor only clones **public** repositories you provide a URL for, or scans manifest text you
paste directly — it never accesses private repositories, credentials, or personal data. License
metadata is technical/legal metadata about software packages, not personal data. The
compatibility findings are informational, not legal advice — see "How license detection works"
above.

### FAQ

**Q: Does this replace ScanCode Toolkit / a full SCA (software composition analysis) tool?**
A: No. It's a fast, dependency-light MVP that covers the two most common manifest ecosystems
(npm, pip) and the ~15 most common license texts. For exhaustive, forensic-grade license
detection across every ecosystem, a dedicated tool like ScanCode Toolkit or a commercial SCA
product (FOSSA, Snyk License Compliance) is more thorough.

**Q: What happens if a dependency's license can't be determined?**
A: It's reported with `spdxId: null` and a `lookupError`, and shows up as a standalone `warning`
conflict record — never silently ignored or guessed.

**Q: Why does an unlicensed project get flagged as `UNLICENSED` even though no LICENSE file exists?**
A: Under copyright law, no declared license means "all rights reserved" by default — the
strictest possible position, and a genuine conflict risk if a GPL dependency is combined with it.
Set `assumeProprietaryIfUnlicensed` to `false` to disable this and leave it unclassified instead.

**Q: Can this scan a private repository?**
A: No — only `https://`/`http://` public repository URLs are accepted (no authentication is
performed). For a private repo, paste its manifest contents via `manifests` instead.

**Q: Which ecosystems are supported?**
A: `package.json` (npm) and `requirements.txt` (pip) for this first version. Other ecosystems
(Maven `pom.xml`, Ruby `Gemfile`, Rust `Cargo.toml`, Go `go.mod`) are out of scope for now.

### Keywords

license, license-compliance, spdx, gpl, open-source, dependency-audit, sca,
software-composition-analysis, compliance, legal-tech, npm, pypi

### Changelog

#### 0.1.0

- Initial release: git-clone or inline-manifest scanning, npm/PyPI dependency license lookup,
  own text-based license classifier, own compatibility matrix.

# Actor input Schema

## `repoUrl` (type: `string`):

Public git repository to clone and scan (https:// or http:// only). Provide this, or fill in "manifests" below, or both. Example: "https://github.com/expressjs/express.git".

## `gitRef` (type: `string`):

Branch, tag, or commit to check out. Leave empty to use the repository's default branch.

## `manifests` (type: `array`):

One or more dependency manifests pasted directly, instead of (or in addition to) a repository URL. Each item needs a "filename" ("package.json" or "requirements.txt") and its "content".

## `checkDependencyLicenses` (type: `boolean`):

Query the public npm registry / PyPI registry for the license of every dependency found. Disable to only check the project's own declared license (faster, no per-dependency network calls, no per-dependency charge).

## `maxDependencies` (type: `integer`):

Safety cap on how many dependencies get a live registry lookup in one run. Example: 200.

## `assumeProprietaryIfUnlicensed` (type: `boolean`):

If no LICENSE file or package.json "license" field is found, treat the project as proprietary/all-rights-reserved (the real legal default) for the purposes of the compatibility check, instead of leaving it unclassified.

## Actor input object example

```json
{
  "manifests": [],
  "checkDependencyLicenses": true,
  "maxDependencies": 200,
  "assumeProprietaryIfUnlicensed": true
}
```

# Actor output Schema

## `results` (type: `string`):

All results in the default dataset.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/legacy-code-license-scanner").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/legacy-code-license-scanner").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 '{}' |
apify call codeclouds/legacy-code-license-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/legacy-code-license-scanner"
        }
    }
}

```

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/Cae862gM09DXpykCd/builds/a0NP8I1wTCKiDMw81/openapi.json
