Skip to content

feat!: shrink the public export surface to what it means to publish - #569

Open
cjbarth wants to merge 4 commits into
node-saml:masterfrom
cjbarth:feat/audit-export-surface
Open

feat!: shrink the public export surface to what it means to publish#569
cjbarth wants to merge 4 commits into
node-saml:masterfrom
cjbarth:feat/audit-export-surface

Conversation

@cjbarth

@cjbarth cjbarth commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #551

index.ts did export * from "./utils", so every helper written for signed-xml.ts to use became public API at the same time. That is how findChilds stayed published with no callers, and how each new helper joined the surface without anyone choosing to publish it.

The keep list is informed by the consumer survey on the issue, which checked what dependents and public code actually import rather than what we call.

What stays

Export Why
derToPem, pemToDer, normalizePem key-format conversion is a real consumer need; pemToDer has a confirmed importer, and three separate projects hand-rolled a normalizePem, which says the need is real and the export merely undiscoverable
findAncestorNs already part of the custom-canonicalization contract in practice — akira-io/node-efatura, kacheablecr-jpg/nervus-backend and dressoria/sri-signing-worker all import { C14nCanonicalization, findAncestorNs } from "xml-crypto"

What goes

findAttr, findChildren, findChilds, isDescendantOf, isArrayHasLength, encodeSpecialCharactersInAttribute, encodeSpecialCharactersInText, validateDigestValue, BASE64_REGEX, EXTRACT_X509_CERTS, PEM_FORMAT_REGEX. None has a confirmed importer; every code-search hit was a same-named local helper, a fork, a vendored copy, or a CVE-reproduction corpus.

validateDigestValue goes internal despite the issue's "check before dropping" note. Nobody imports it, so the risk of pushing someone toward a naive === is not realised — and publishing the constant-time comparison is itself what invites reimplementation. It belongs behind the API that already uses it.

BASE64_REGEX and PEM_FORMAT_REGEX lose the export keyword outright since no sibling uses them. The rest stay exported from utils.ts for their siblings but are no longer re-exported to consumers — a sibling and a consumer reaching a helper through the same export keyword is the root cause, and index.ts is now the only place that can widen the surface.

lib/ was public too

index.ts only governs the barrel. With no exports map, every file under lib/ was reachable by path, and consumers do reach: shunkica/fiskalizacija2-js does

import { Sha256 } from "xml-crypto/lib/hash-algorithms.js";

for a class we never exported. So package.json now declares an exports map naming the entry point and package.json and nothing else. Verified against a real package resolution: the barrel resolves, xml-crypto/lib/hash-algorithms.js fails with ERR_PACKAGE_PATH_NOT_EXPORTED, xml-crypto/package.json still resolves. main and types stay for resolvers that ignore exports.

The bundled algorithm classes remain reachable through the registries that name them, which the README now documents:

const Sha256 = new SignedXml().HashAlgorithms["http://www.w3.org/2001/04/xmlenc#sha256"];

Guard against regrowth

test/public-api-tests.spec.ts pins both the runtime export names and the declared subpaths, so widening either shows up as a reviewable diff rather than a side effect of adding a helper or a file. Confirmed it fails on master, listing exactly the names withdrawn here.

README fix found along the way

The README documented an xpath export that 6.x does not have — require("xml-crypto").xpath is undefined, so the verification example would have thrown on select(...). The examples now use the xpath package directly, whose select() takes the expression first; verified the corrected example validates test/static/valid_signature.xml. That section is replaced with the actual export list.

Ordering

#567 deprecates every name withdrawn here, with a runtime warning, and is meant to land on 6.x first. This branch will need a rebase after that.

Verification

npm run build && npm test && npm run lint clean; 243 passing (241 + 2).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added upgrade guidance for version 7.0, including package import changes, XPath usage, and supported API exports.
  • Breaking Changes
    • Deep lib/ imports are no longer supported.
    • Several internal utilities and parsing constants are no longer publicly exported.
    • The deprecated child-finding helper has been removed.
    • Algorithm classes should be accessed through SignedXml registries.
  • Package Improvements
    • Added explicit package entry points and TypeScript declaration mappings.
  • Tests
    • Added coverage to verify the documented public API and package exports.

`index.ts` did `export * from "./utils"`, so every helper written for
`signed-xml.ts` to use became public API at the same time. That is how
`findChilds` stayed published with no callers and how each new helper joined
the surface without anyone choosing to publish it.

The export list is explicit now, and it names the four helpers a consumer has
a real use for: `derToPem`, `pemToDer` and `normalizePem` for key-format
conversion, and `findAncestorNs`, which is already part of the custom
canonicalization contract in practice — three projects on GitHub import it
alongside `C14nCanonicalization` to canonicalize a document subset by hand.
See the survey on node-saml#551.

`validateDigestValue` goes internal despite the "check before dropping" note.
Nobody imports it, and publishing the constant-time comparison is what invites
someone to reimplement it; it belongs behind the API that already uses it.

`BASE64_REGEX` and `PEM_FORMAT_REGEX` lose their `export` keyword outright,
since no sibling module uses them. The rest stay exported from `utils.ts` for
their siblings but are no longer re-exported to consumers — a sibling and a
consumer reaching a helper through the same `export` keyword is what caused
this, and `index.ts` is the only place that can now widen the surface.

`index.ts` only governs the barrel, though. With no `exports` map every file
under `lib/` was reachable by path, and consumers do reach: one published
package imports `xml-crypto/lib/hash-algorithms.js` for a class we never
exported. Declare an `exports` map naming the entry point and `package.json`
and nothing else, so a deep import fails loudly instead of quietly depending on
the build layout. The bundled algorithm classes stay reachable through the
`HashAlgorithms`, `SignatureAlgorithms` and `CanonicalizationAlgorithms`
registries that name them.

`test/public-api-tests.spec.ts` pins both the runtime export names and the
declared subpaths, so widening either is a deliberate edit rather than a side
effect of adding a helper or a file.

The README documented an `xpath` export that 6.x does not have — the
verification example destructured it and would have thrown. Point the examples
at the `xpath` package instead, whose `select()` takes the expression first,
and replace that section with the actual export list.

BREAKING CHANGE: `findAttr`, `findChildren`, `findChilds`, `isDescendantOf`,
`isArrayHasLength`, `encodeSpecialCharactersInAttribute`,
`encodeSpecialCharactersInText`, `validateDigestValue`, `BASE64_REGEX`,
`EXTRACT_X509_CERTS` and `PEM_FORMAT_REGEX` are no longer exported, and
subpaths into `lib/` no longer resolve. See the Upgrading section of the README
for replacements.

Closes node-saml#551

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6842d67b-924a-40ff-92ab-4d18927975d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0409418 and cf87334.

📒 Files selected for processing (5)
  • README.md
  • package.json
  • src/index.ts
  • src/utils.ts
  • test/public-api-tests.spec.ts

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The package now exposes an explicit public API, blocks unlisted deep imports through its exports map, removes deprecated internal exports, adds API surface tests, and documents version 7.0 migration changes.

Changes

Public API surface

Layer / File(s) Summary
Explicit public API surface
src/index.ts, src/utils.ts
Wildcard exports are replaced with selected types and utilities. findChilds, PEM_FORMAT_REGEX, and BASE64_REGEX are no longer public.
Package entry points and API contract tests
package.json, test/public-api-tests.spec.ts
The package declares typed and JavaScript root entries and the ./package.json subpath. Tests pin the runtime export list and package exports map.
Version 7.0 upgrade documentation
README.md
The README documents removed exports, external xpath usage, registry-based algorithm access, and supported package exports.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: shunkica

Merge Risk: ⚪ Minimal · up to cf873

The 7.0 release narrows the public API and blocks unsupported deep imports while documenting migration paths. The change is ready to merge with normal checks.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements the 7.0 export reduction, preserves selected types and utilities, removes internal helpers and constants, adds export-surface safeguards, and updates the README. It does not show the… Add changelog entries for the removed exports and their replacements. Provide evidence that the required 6.x deprecation phase was completed before removing the names in 7.0, or include the missing deprecation changes if they are part of th…
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary breaking change: reducing the public export surface.
Out of Scope Changes check ✅ Passed The source, package metadata, tests, and README changes directly support the public API reduction and package export restrictions described in the linked issue. No unrelated code changes are evident.
Full details: Linked Issues check

Explanation

The PR implements the 7.0 export reduction, preserves selected types and utilities, removes internal helpers and constants, adds export-surface safeguards, and updates the README. It does not show the required changelog updates, and the provided changes do not independently demonstrate the preceding 6.x deprecation work described in issue #551.

Resolution

Add changelog entries for the removed exports and their replacements. Provide evidence that the required 6.x deprecation phase was completed before removing the names in 7.0, or include the missing deprecation changes if they are part of this PR [#551].

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

package.json

Parsing error: ESLint was configured to run on <tsconfigRootDir>/package.json using parserOptions.project: /tsconfig.eslint.json
The extension for the file (.json) is non-standard. You should add parserOptions.extraFileExtensions to your config.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.91%. Comparing base (f5c4d22) to head (cf87334).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #569      +/-   ##
==========================================
+ Coverage   75.95%   76.91%   +0.96%     
==========================================
  Files           9        9              
  Lines        1048     1057       +9     
  Branches      273      275       +2     
==========================================
+ Hits          796      813      +17     
+ Misses        144      138       -6     
+ Partials      108      106       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

cjbarth and others added 3 commits September 10, 2026 12:22
`findChilds` is an alias of `findChildren` with no caller in `src/`, so
withdrawing it from the barrel still left dead code behind for no one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 7.0 upgrade table told callers of the removed encoders to use an XML
serializer, but a serializer does not escape the way C14N requires, so a
custom canonicalizer that followed the advice would change its digests.
Point them at `C14nCanonicalization` and `ExclusiveCanonicalization`, as
the 6.x deprecation notice now does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 7.0 upgrade table said `crypto.timingSafeEqual()`'s length-mismatch
exception "counts as unequal". It throws, so following the advice turns
an ordinary mismatch, possibly caused by untrusted XML, into an
exception where `validateDigestValue()` returned `false`. Check the
lengths first, as the 6.x deprecation notice now says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit the public export surface and deprecate internal helpers ahead of 7.0

1 participant