licensegate

Reading a LICENSE file nobody declared

Most npm packages put an SPDX id in package.json and you never look further. Some don’t: the field is missing, or it says SEE LICENSE IN LICENSE. Then the file is the licence, and a few words in it decide whether you owe the author a notice.

Install licensegate

This page explains how these licences are commonly read. It is not legal advice; for a product decision with money on it, ask a lawyer who handles open source.

What package.json can say instead of an id

npm’s documentation asks for an SPDX expression such as MIT or (MIT OR Apache-2.0), and allows two strings that are not licences at all:

"license": "SEE LICENSE IN LICENSE.txt"
    a licence with no SPDX id, or a custom one: read the file

"license": "UNLICENSED"
    no permission to use it under any terms

"licenses": [{ "type": "MIT" }]
    the old array and object styles, now deprecated

(no "license" field at all)
    the label is missing; the LICENSE file may still be there

A checker that only reads the field reports the first and the last as unknown, and every one of them lands on someone’s desk. Usually the package has a perfectly ordinary LICENSE file next to its package.json. The question is which licence that file actually is.

Look-alikes with different obligations

Several short licences share most of their words. The few that differ are the ones that tell you what to ship:

LicenceHow it differsNotice in your app?
MITThe base text: the copyright notice and permission notice “shall be included in all copies or substantial portions of the Software.”Yes
MIT-0The same grant, with that condition removedNo
X11MIT plus: the name of the X Consortium “shall not be used in advertising or otherwise to promote” the software without written permissionYes, and no use of the name
JSONMIT plus one line: “The Software shall be used for Good, not Evil.” The FSF lists it as non-free because that line restricts useYes, and the use restriction
ISCPermission granted “provided that the above copyright notice and this permission notice appear in all copies”Yes
0BSDThe ISC text without that provisoNo
BSD-3-ClauseBinary redistributions reproduce the notice in the documentation; no use of the authors’ names to endorse your productYes
BSD-4-ClauseBSD-3 plus the advertising clause: advertising materials that mention the software must display an acknowledgement. The FSF lists it as incompatible with the GPLYes, and in advertising

Calling MIT-0 “MIT” costs you a line in a notices file you didn’t need. Calling JSON or BSD-4-Clause “MIT” or “BSD-3” hides a condition that someone should have read.

Why matching a phrase is not enough

MIT, MIT-0, X11 and JSON all open with the same sentence. Put side by side, the only lines that differ are these:

MIT     ... to whom the Software is furnished to do so,
        subject to the following conditions:
        The above copyright notice and this permission notice
        shall be included in all copies or substantial portions...

MIT-0   ... to whom the Software is furnished to do so.

X11     (the MIT text, then)
        Except as contained in this notice, the name of the
        X Consortium shall not be used in advertising ...

JSON    (the MIT text, then)
        The Software shall be used for Good, not Evil.

ISC and 0BSD are the same kind of pair, one proviso apart, and BSD-4-Clause is BSD-3-Clause with one clause more:

ISC     Permission to use, copy, modify, and/or distribute this
        software for any purpose with or without fee is hereby
        granted, provided that the above copyright notice and
        this permission notice appear in all copies.

0BSD    Permission to use, copy, modify, and/or distribute this
        software for any purpose with or without fee is hereby
        granted.

BSD-4   3. All advertising materials mentioning features or use
        of this software must display the following
        acknowledgement: This product includes software
        developed by the organization.

A checker that recognises MIT by its opening sentence calls all four MIT. licensegate did exactly that up to version 0.5.0, and read a BSD-4-Clause file as BSD-3-Clause for the same reason. Real files make it harder: authors rewrap the lines, swap quote marks, write “licence” for “license”, add a title and a copyright line, or paste two licences into one file.

How licensegate 0.6.0 compares the text

  1. Normalise. Lower case, punctuation and line breaks dropped, “licence” read as “license”, list numbers ignored, close to what the SPDX matching guidelines ask. Placeholders in the reference texts, such as the copyright holder’s name, are left out.
  2. Compare by three-word sequences against the reference texts of the SPDX licence list 3.29.0 for 17 short licences: MIT, MIT-0, X11, JSON, ISC, 0BSD, the BSD-2, BSD-3, BSD-3-Clear and BSD-4 clauses, Zlib, Unlicense, BSL-1.0, WTFPL, NCSA, PostgreSQL and UPL-1.0. The score is the share of the reference text found in the file, and it has to reach 75%. A title or a copyright line on top costs nothing.
  3. Prefer the longer relative only when its extra words are there. An X11 or JSON file contains all of MIT, so MIT scores 100% on it too. The longer licence wins when the file holds at least half of what it adds to the shorter one; an MIT-0 file is missing the MIT condition, so MIT doesn’t reach the top score there.
  4. Long licences by their title line. GPL, LGPL, AGPL, Apache, MPL, EPL, EUPL, CDDL and others are recognised by a heading that stands alone, so a GPL title quoted inside another licence’s text doesn’t count.
  5. Two licences in one file are joined with OR when the file says you may choose (“dual licensed”, “at your option”), and with AND otherwise.

The same release knows every id on the SPDX list (740 licences and 86 exceptions) in any casing, so apache-2.0 in a package.json is read as Apache-2.0. About a third of those ids are rare one-off licences licensegate doesn’t put in a category; they come out as review, never as ok.

What a run shows

Four dependencies with no usable licence field. Only one needs a person:

npx -y https://licensegate.org/dl/licensegate.tgz --mode distributed --prod
licensegate 0.6.0 · mode distributed · production only · 4 packages from node_modules
  REVIEW json-min@0.4.0  JSON
         source-available, non-commercial or use-restricted terms: check what the licence forbids
  3 ok · 1 to review · 0 blocking
  Not legal advice. Grey cases (linking, plug-ins, dual licences) say REVIEW on purpose.

With --format json every row says where the licence came from. "source": "text" and the file name mark the ones read from a LICENSE file instead of package.json:

{ "name": "dual-util", "version": "3.0.1", "license": "MIT OR Apache-2.0",
  "source": "text", "file": "LICENSE", "verdict": "ok" },
{ "name": "json-min", "version": "0.4.0", "license": "JSON",
  "source": "text", "file": "LICENSE.txt", "verdict": "review" },
{ "name": "str-pad", "version": "2.1.0", "license": "0BSD",
  "source": "text", "file": "LICENSE.md", "verdict": "ok" },
{ "name": "tiny-hash", "version": "1.0.3", "license": "MIT-0",
  "source": "text", "file": "LICENSE", "verdict": "ok" }

The rows are trimmed to the fields that matter here; the full output also carries the category, the path and whether the package is a dev dependency.

What still needs a person

  • Added terms. The score measures how much of a known licence the file contains, not what the author added to it. An MIT text with a sentence of the author’s own at the end is still reported as MIT. If a package matters to you, open the files marked "source": "text"; there are usually few.
  • No match. A file that resembles none of the reference texts and has no recognisable title comes out as review, with the declared string (SEE LICENSE IN…) or “no licence”. Read it, ask the author, or replace the package.
  • UNLICENSED or no file at all. Without a licence, the default is that you have no permission to redistribute the code. A missing label is not a grant.

Matching text by similarity is not new: GitHub’s licensee compares licence files with a Sørensen–Dice coefficient. What licensegate adds is the step after: the licence it finds is judged against how you ship, and the result goes into the same CI exit code, SBOM and sealed report as every declared licence.