Skip to content

fix(@callstack/licenses): sanitize unsafe characters in AboutLibraries license filenames#169

Merged
mateusz1913 merged 1 commit into
callstackincubator:mainfrom
alexisloiselle:fix/sanitize-license-type-filename
May 26, 2026
Merged

fix(@callstack/licenses): sanitize unsafe characters in AboutLibraries license filenames#169
mateusz1913 merged 1 commit into
callstackincubator:mainfrom
alexisloiselle:fix/sanitize-license-type-filename

Conversation

@alexisloiselle
Copy link
Copy Markdown
Contributor

Summary

prepareAboutLibrariesLicenseField uses license.type verbatim as part of a filename written under android/config/licenses/ by writeAboutLibrariesNPMOutput. When a dependency declares a legacy SPDX expression like MIT/X11 (e.g. nub@0.0.0 or optimist), the unsanitized / causes the writer to attempt to create android/config/licenses/MIT/X11_<hash>.json. That fails with ENOENT because MIT/ is interpreted as a subdirectory that doesn't exist.

This PR sanitizes any character outside [A-Za-z0-9._-] to _ before constructing both the filename prefix and the sha512 input (when content is absent), so the prefix and hash stay consistent and the filename is always valid.

Repro

Without this fix, against @callstack/licenses@0.3.1:

// package.json depends on nub@0.0.0
const { scanDependencies, writeAboutLibrariesNPMOutput } = require('@callstack/licenses');

const licenses = scanDependencies('./package.json', () => ({
  includeTransitiveDependencies: true,
}));
writeAboutLibrariesNPMOutput(licenses, './android');

Throws:

ENOENT: no such file or directory, open
  '<cwd>/android/config/licenses/MIT/X11_<hash>.json'

After this PR, the same script writes android/config/licenses/MIT_X11_<hash>.json successfully, and libraryJsonPayload.licenses[0] matches licenseJsonPayload.hash.

Behaviour change

license.type Before After
MIT MIT_<hash> MIT_<hash> (unchanged)
Apache-2.0 Apache-2.0_<hash> Apache-2.0_<hash> (unchanged)
MIT/X11 MIT/X11_<hash> (ENOENT on write) MIT_X11_<hash>
(MIT OR Apache-2.0) (MIT OR Apache-2.0)_<hash> (unsafe) _MIT_OR_Apache-2.0__<hash>
LGPL-2.1-only WITH Classpath-exception-2.0 unchanged (unsafe) LGPL-2.1-only_WITH_Classpath-exception-2.0_<hash>

The allowlist approach (vs. blocklisting just /()) is chosen because other compound SPDX expressions can also produce filenames that are invalid on some filesystems.

Why this matters

Without this fix, any project depending (even transitively) on a package with such a license expression will fail Android license metadata generation entirely (the first throw aborts the loop), or produce an incomplete android/config/licenses/ directory, leading to a missing or broken OSS notice screen.

Test plan

  • Added unit tests in packages/licenses-api/src/node/utils/__tests__/packageUtils.test.ts:
    • Missing license type
    • Plain MIT
    • Legacy MIT/X11 (the reproduction case)
    • Compound (MIT OR Apache-2.0)
    • Hash consistency when content is absent (sanitized type is hashed, matching the prefix)
  • yarn workspace @callstack/licenses test → 52 passing
  • yarn typescript
  • yarn lint:js

Checklist

  • Added a changeset (patch bump for @callstack/licenses)
  • Added unit tests
  • Conventional commit message

…s license filenames

License types like `MIT/X11` (e.g. nub@0.0.0, optimist) and compound SPDX
expressions like `(MIT OR Apache-2.0)` were used verbatim as part of the
filename written under `android/config/licenses/`. The slash caused
`writeAboutLibrariesNPMOutput` to fail with ENOENT because the path
component before it was treated as a subdirectory; other characters
(parens, spaces) produced names that are unsafe on some filesystems.

Sanitize anything outside `[A-Za-z0-9._-]` to `_` before using the
license type as both the filename prefix and (when content is absent)
the sha512 input, so the prefix and hash stay consistent.
@mateusz1913
Copy link
Copy Markdown
Collaborator

Hi @alexisloiselle, the changes look good, thanks! Is the PR ready to be undrafted?

@alexisloiselle
Copy link
Copy Markdown
Contributor Author

Hi @alexisloiselle, the changes look good, thanks! Is the PR ready to be undrafted?

Yes! although I'm on my phone right now, will put ready for review whenever I get the chance!

@alexisloiselle alexisloiselle marked this pull request as ready for review May 26, 2026 15:51
@alexisloiselle
Copy link
Copy Markdown
Contributor Author

alexisloiselle commented May 26, 2026

@mateusz1913 Undrafted and ready to merge 🎉

@mateusz1913 mateusz1913 merged commit f300815 into callstackincubator:main May 26, 2026
7 of 8 checks passed
@github-actions github-actions Bot mentioned this pull request May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants