An SPDX license expression combines license identifiers with operators. OR means the recipient may choose one of the licenses, AND means all of them apply at once, WITH attaches a named exception, and a plus suffix means that version or any later one. An SCA license report that shows these strings is giving you real information.
Why SCA reports show expressions
A single package often cannot be described by one license name. SPDX handles that with license expressions, defined in Annex D of the specification. The spec gives two typical cases: software offered under a choice of licenses, such as GPL-2.0-only OR BSD-3-Clause, and a binary built from files under different licenses, such as LGPL-2.1-only AND BSD-3-Clause.
If your SCA tool flattens these into a single label, or splits them into separate findings, you can end up with a wrong policy result.
The four operators
| Operator | Meaning | Example |
|---|---|---|
| OR | The recipient picks one license from the choice | MIT OR Apache-2.0 |
| AND | Terms of every listed license apply | LGPL-2.1-only AND BSD-3-Clause |
| WITH | A license plus a listed exception | A license identifier WITH an exception identifier |
| + | That version of the license or any later version | CDDL-1.0+ |
Parentheses set the order. The spec also says operators are matched case sensitively, so write them in capitals, and that there must be no space between an identifier and a trailing plus.
User-defined references use the LicenseRef- prefix. If you see one, the license text is not on the SPDX list, and someone has to read it.
How a policy should treat each form
Your allow list should be written against how the expression resolves, not against each word in it.
- For OR, a policy can pass the package if at least one option is allowed. Record which option you rely on, so a later audit sees the choice.
- For AND, every listed license must be allowed. One disallowed term fails the whole package.
- For WITH, check the exception too. An exception changes the obligations of the base license, so do not treat the base name alone as the answer.
- For LicenseRef, route it to a person. A tool cannot judge a custom text.
This is a policy-design aid, not legal advice. Have counsel confirm how your organization reads each license.
Test your tool in ten minutes
Make a small test repository with three dependencies whose metadata you know: one with an OR expression, one with AND, and one with a LicenseRef. Run the scan and check three things. Does the report show the full expression? Does the policy engine evaluate it as above? Does a LicenseRef end up in a review queue rather than a silent pass?
A worked example with four packages
Imagine your scan returns four packages, each with a license string.
| Package | License expression | How to read it |
|---|---|---|
| alpha | MIT | One license, permissive |
| beta | MIT OR Apache-2.0 | You choose one of the two |
| gamma | LGPL-2.1-only AND BSD-3-Clause | Terms of both apply |
| delta | LicenseRef-vendor-eula | A custom text, not on the SPDX list |
Suppose your policy allows MIT, Apache-2.0, and BSD-3-Clause, and sends LGPL to review. Alpha passes. Beta passes, and you note that you rely on either option. Gamma goes to review because one of its two licenses is outside the allow list. Delta goes to a person, since a tool cannot judge a custom text.
If your tool had flattened beta to "MIT" and gamma to "BSD-3-Clause", the report would look clean while gamma carried an LGPL obligation. That is the failure to test for.
Step by step: write a policy that handles expressions
- List the licenses your organization has already approved, using SPDX identifiers rather than informal names.
- Decide the treatment of OR: pass if any option is approved, and record the chosen option.
- Decide the treatment of AND: pass only if every license is approved.
- Decide the treatment of WITH: look up the exception and approve it separately.
- Decide the treatment of LicenseRef: always send to review.
- Decide what happens to expressions the tool cannot parse: send to review, never pass silently.
- Write each decision into the policy document and the tool configuration, and keep them in sync.
Common parsing pitfalls
The specification sets small rules that tools sometimes get wrong. Operators are matched case sensitively, so "or" in lowercase is not a valid operator. There must be no space between an identifier and a plus sign. WITH needs spaces on both sides. Identifiers are matched case insensitively, but the canonical case on the SPDX list is preferred.
Older packages may carry informal strings such as "BSD" or "Apache 2", which are not valid SPDX identifiers. A good tool normalizes these where it can, and flags what it cannot. Ask how yours handles ambiguous names like "BSD", which could mean several different licenses.
Metadata is not the same as the license file
The license string in package metadata is declared by the maintainer. It can be missing, out of date, or different from the license text in the repository. For packages that matter to you, spot check the actual license file against the declared expression, and record the result.
Who should own the decision
Engineering can run the scan and enforce the policy, but the interpretation of a license belongs with someone qualified, usually counsel or a compliance owner. Engineering's job is to give them a clear list of expressions that need a decision, not to guess.
A note for SBOM consumers
SPDX and CycloneDX SBOMs can both carry license expressions. When you receive an SBOM from a supplier, run the same normalization before you apply your policy, so one set of rules covers internal and supplier components.
Try it yourself
Click through it at your own pace, or open it full screen.
Open a component and find where its license is shown. Compare that to the expression forms above.
Quick check
If a package says MIT OR Apache-2.0, do I have to comply with both?
No. OR is a choice. You can rely on either license, though it is wise to record which one you chose.
Is GPL-2.0-or-later the same as GPL-2.0+?
SPDX has both forms in different versions of its list, and the plus suffix is the older style for "or any later version". Check that your tool normalizes them to the same meaning.
What should a tool do with a license string it cannot parse?
It should flag the component for review rather than passing it silently. An unparsed string is missing information, not a clean result.
Sources
Last reviewed: 19 October 2026.