Skip to content

Repository files navigation

License Laundering — Replication Package

Code and data for the IEEE Software paper "Don't Trust the Label: License Laundering in AI Supply Chains" (Jewitt, Li, Rajbahadur, Adams, and Hassan; Queen's University).

Every number and figure in the paper is reproducible from this repository.

Quick start

unzip data.zip                 # creates data/ (~217 MB unpacked)
pip install -r requirements.txt
cd analysis

python3 prune_data.py                  # scope the data -> data/filtered_chains_clean.jsonl
python3 category_distribution.py       # license-string -> category distributions
python3 rq1_false_clarity.py           # RQ1: Unknown laundering
python3 rq2_selective_laundering.py    # RQ2: category laundering
python3 e2e_category_survival.py       # end-to-end survival matrices
python3 named_examples.py              # MiniLM / wukong100m / Wikipedia examples

python3 figure_data_dump.py            # -> ../figure_data.md
python3 sankey_rq1.py                  # -> ../figure_1.pdf
python3 rq2_break_pairs_experiment.py  # -> ../figure_2.pdf

python3 report_paper_numbers.py        # prints the table below

Run them in that order. The six analysis scripts write into results.json; the two figure scripts read it. prune_data.py must run first — it produces data/filtered_chains_clean.jsonl, which every other script reads.

Runtime is a few minutes end to end, dominated by prune_data.py.

Layout

LicenseLaundering/
├── LICENSE                  # Apache-2.0 (code)
├── LICENSE-DATA             # CC BY 4.0 (data), with scope note
├── data.zip                 # raw scope + license-category mapping; unzip in place
├── requirements.txt
├── analysis/
│   ├── prune_data.py                   # scoping and filtering
│   ├── category_distribution.py        # license strings -> seven categories
│   ├── rq1_false_clarity.py            # Unknown laundering (RQ1)
│   ├── rq2_selective_laundering.py     # category laundering (RQ2)
│   ├── e2e_category_survival.py        # end-to-end survival matrices
│   ├── named_examples.py               # the named examples in the paper
│   ├── figure_data_dump.py             # plain-text dump of every figure datum
│   ├── sankey_rq1.py                   # Figure 1
│   ├── rq2_break_pairs_experiment.py   # Figure 2
│   ├── report_paper_numbers.py         # regenerates the table below
│   └── results_manager.py              # shared results.json helper
├── results.json             # every value the paper cites (generated)
├── figure_data.md           # every node, flow and matrix cell as text (generated)
├── figure_1.pdf             # Figure 1 in the paper (generated)
└── figure_2.pdf             # Figure 2 in the paper (generated)

results.json and figure_data.md are committed so the numbers can be checked without unpacking the data or running anything.

Numbers in the paper

Generated by python3 analysis/report_paper_numbers.py --markdown, run against a clean unpack of data.zip. Nothing below is typed by hand — re-run the pipeline and this table regenerates from results.json and the figure scripts.

Scope (prune_data.py, category_distribution.py)

Quantity Value
Datasets in analysis scope 3,120
Models in analysis scope 5,556
Applications in analysis scope 24,076
Chains before license filtering 264,431
Chains excluded (unrecognised licence strings) 32,161
Distinct unrecognised licence strings 495 of 765
Chains analysed 232,270

Figure 1 — Unknown laundering (sankey_rq1.py)

Quantity Value
Total D→M→A chains analysed 232,270
Chains through ≥1 Unknown artifact 144,631 (62.3%)
Fully-Known chains 87,639 (37.7%)
Unknown datasets → Known models 91,067 of 102,607 (88.8%)
Unknown models → Known applications 19,243 of 23,951 (80.3%)
Known models → Unknown applications (drawn cohort) 51,975 of 120,680 (43.1%)
Known models → Unknown applications (all chains) 51,975 of 208,319 (24.9%)
Applications ending Unknown 56,683 (39.2%)
Top 10% of Unknown datasets, dataset→model 89.5%
Top 10% of Unknown models, model→application 76.0%
Top 10% of Unknown datasets, end-to-end 84.3%

Figure 2 — category laundering (rq2_break_pairs_experiment.py)

Quantity Value
Dataset→Model retention (transition-level) 62,160 of 95,076 (65.4%)
Model→Application retention (transition-level) 75,703 of 87,658 (86.4%)
End-to-end Dataset→Application retention (transition-level) 62,839 of 95,076 (66.1%)
Copyleft→Permissive (a) 4,901 of 5,265 (93.1%)
ML License retained (a) 158 of 283 (55.8%)
ML License→Permissive (b) 2,155 of 2,768 (77.9%)
ML License retained (b) 116 of 2,768 (4.2%)
Permissive end-to-end (c) 61,234 of 64,369 (95.1%)
Sharealike end-to-end (c) 739 of 15,678 (4.7%)
Fully-Known chains dropping ≥1 category 32,901 of 87,639 (37.5%)
preserve all categories 54,738 (62.5%)
drop at dataset→model only 20,952 (23.9%)
drop at model→application only 4,823 (5.5%)
drop at both hops 7,126 (8.1%)

Named examples (named_examples.py)

Quantity Value
MiniLM applications 1,656
MiniLM declared training datasets 21, 9 with no licence, 3 Sharealike
wukong100m downstream models 89, 0 carry CC-Restrictive forward
Wikipedia chains 4,849, 86 preserve both (1.8%)

How the two laundering measures are defined

Unknown laundering (Figure 1) — an artifact with no declared license whose downstream artifact carries a definitive one. Figure 1 draws only the 144,631 chains passing through at least one Unknown artifact, so every node percentage in it is a share of that subset, not of all 232,270 chains.

Category laundering (Figure 2) — a declared license category that does not survive a transition. Measured over the 87,639 fully-Known chains, where all three artifacts carry a known category.

Two denominators appear in the results and are easy to conflate:

  • Chains — one dataset→model→application path. 232,270 in scope.
  • Category transitions — one per category per chain. Artifacts can carry several categories, and each is tracked independently, so the 87,639 fully-Known chains yield 95,076 dataset→model and 87,658 model→application category transitions.

Figure 2's panels (a)–(c) count transitions; panel (d) counts chains.

License categories

Seven categories (see the paper's AI Supply Chain Composition section for sources):

Category Examples
PERMISSIVE MIT, Apache-2.0, BSD-3-Clause
COPYLEFT GPL, AGPL
SHARE_ALIKE CC BY-SA, LGPL, MPL
ML_LICENSE OpenRAIL, Llama Community License
CC_RESTRICTIVE CC NC, NC-SA, NC-ND, ND
PUBLIC_DOMAIN CC0, Unlicense
UNKNOWN absent / blank / "other" / "unknown"

Data

data.zip unpacks to data/ (~217 MB):

  • filtered_complete_chains_cleaned.jsonl — the raw scope inherited from our prior audit, including base-model paths.
  • matrix.json — the license-string → category mapping used during scoping.

prune_data.py writes a third file, data/filtered_chains_clean.jsonl, which is the dataset every analysis script reads. It is generated, not shipped.

Two pruning steps scope the data: models serving only as base models with no application invoking them are removed, so every chain passes through exactly two transitions; then chains containing a license string that does not map to our classification are excluded. Both counts appear in the table above.

Notes on reproducibility

  • The pipeline is deterministic. Figure 1 resolves multi-category artifacts by random choice seeded at 42, and report_paper_numbers.py uses the same seed, so repeated runs give identical output.
  • results.json is keyed by question number: Q1–Q19 scope and distributions, Q20–Q44 Unknown laundering, Q45–Q66 category laundering, Q67–Q68 end-to-end survival, Q69–Q71 named examples. Each entry carries a description saying what it measures.
  • Both figure scripts self-check before drawing: they re-derive every printed percentage from the counts printed beside it and fail rather than emit a figure whose labels disagree with its own data. Figure 1 runs 27 such assertions, Figure 2 runs 175.
  • Two estimators of "top 10% concentration" appear in the code. The figure interpolates the Lorenz curve at exactly 10% (89.5% for dataset→model); Q39/Q40 in results.json take the top floor(10%) artifacts, which lands 0.1 points lower because 10% of 1,059 datasets truncates to 105. The paper quotes the figure's.

License

What License
Code in analysis/ Apache License 2.0 — see LICENSE
Data: data.zip, results.json, figure_data.md CC BY 4.0 — see LICENSE-DATA
Figures: figure_1.pdf, figure_2.pdf Outputs of the Apache-2.0 code, regenerable from it

The dataset records metadata about third-party Hugging Face and GitHub artifacts — identifiers, declared license strings, and the notices ScanCode extracted. It contains no source code, weights, or dataset content from those artifacts, and the CC BY grant covers our collection and curation rather than the underlying facts. See LICENSE-DATA for the full scope note.

Citation

@article{jewitt2026licenselaundering,
  title   = {Don't Trust the Label: License Laundering in AI Supply Chains},
  author  = {Jewitt, James and Li, Hao and Rajbahadur, Gopi Krishnan and
             Adams, Bram and Hassan, Ahmed E.},
  journal = {IEEE Software},
  year    = {2026}
}

About

Repo For License Laundering Paper Scripts

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages