Skip to content

fix: collect all subset namespace prefixes when filtering ancestor namespaces - #541

Open
msheby wants to merge 3 commits into
node-saml:masterfrom
msheby:fix/ancestor-ns-multi-prefix-filter
Open

fix: collect all subset namespace prefixes when filtering ancestor namespaces#541
msheby wants to merge 3 commits into
node-saml:masterfrom
msheby:fix/ancestor-ns-multi-prefix-filter

Conversation

@msheby

@msheby msheby commented Aug 11, 2026

Copy link
Copy Markdown

Fixes #538

Problem

findNSPrefix returned only the first xmlns:* attribute found on a subset
element. findAncestorNs then used that single prefix to decide which ancestor
namespace declarations to suppress — so any subset element that declared more
than one namespace would silently let the others through.

The concrete trigger is SMPTE ST 430-3 ETMs, where the XML signature covers
<AuthenticatedPrivate xmlns:enc="http://www.w3.org/2001/04/xmlenc#"> as a
subset reference. That element is in the default namespace inherited from the
document root, and also declares xmlns:enc. findNSPrefix returned "enc",
leaving {prefix: "", namespaceURI: "…ETM…"} in the ancestor list. The C14N
serializer then rendered the default namespace twice — once from the element
itself via the defaultNs != currNs branch, and once from the hoisted ancestor
entry — producing a digest that no conformant implementation would ever match.

Fix

Replace findNSPrefix (returns a single string) with findSubsetNSPrefixes
(returns a Set) that collects every xmlns:* attribute on the subset
element and always includes the element's own namespace prefix ("" for the
default namespace). findAncestorNs now uses Set.has() for the filter.

The change is backward-compatible: it can only suppress more ancestor entries
than before — no previously-hoisted namespace starts being retained.

Tests added

  • findAncestorNs: default namespace is not hoisted when the subset also
    declares a prefixed namespace
  • findAncestorNs: non-default ancestor namespaces are still hoisted correctly
    in the same scenario
  • C14n: end-to-end check that the canonical output contains exactly one
    xmlns="…" declaration

Summary by CodeRabbit

  • Bug Fixes

    • Improved XML canonicalization for elements inheriting default namespaces while declaring prefixed namespaces.
    • Prevented duplicate or incorrectly hoisted namespace declarations.
    • Preserved valid ancestor namespace declarations.
    • Correctly handled ordinary attributes beginning with xmlns.
  • Tests

    • Added regression coverage for default and prefixed namespace handling.
    • Verified canonical output contains exactly one default namespace declaration.
    • Confirmed unrelated ancestor namespaces remain preserved.

…mespaces

findNSPrefix returned only the first xmlns:* attribute on a subset element,
so findAncestorNs filtered only that one prefix when deciding which ancestor
namespace declarations to hoist. When a subset element in the default
namespace also declared a prefixed namespace (e.g.
<AuthenticatedPrivate xmlns:enc="…">), findNSPrefix returned "enc" and left
the inherited default namespace in the ancestor list. The C14N serializer
then rendered the default namespace twice — once from the element itself and
once from the hoisted ancestor entry — producing a digest that no other
implementation would ever match.

Replace findNSPrefix with findSubsetNSPrefixes, which collects every xmlns:*
attribute on the subset element into a Set and always includes the element's
own namespace prefix (empty string for the default namespace). findAncestorNs
now uses Set.has() to filter, so all already-declared prefixes are suppressed
regardless of how many xmlns:* attributes appear on the subset.

The change is backward-compatible: it filters more ancestor entries than
before, so no previously-hoisted namespace starts being suppressed.

The concrete trigger is SMPTE ST 430-3 ETMs, where the XML signature covers
<AuthenticatedPrivate xmlns:enc="http://www.w3.org/2001/04/xmlenc#"> as a
subset reference.

Fixes node-saml#538
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46781e91-9fa7-4e4c-a411-a0f09c1baf6d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d04fd6 and fce45a2.

📒 Files selected for processing (1)
  • src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils.ts

📝 Walkthrough

Walkthrough

The change updates non-exclusive C14N namespace filtering. It collects all namespace prefixes declared on the subset element, filters matching ancestor namespaces, and adds regression tests for inherited default namespaces and duplicate canonical declarations.

Changes

Non-exclusive C14N namespace filtering

Layer / File(s) Summary
Collect and filter subset namespace prefixes
src/utils.ts, test/c14n-non-exclusive-unit-tests.spec.ts
findSubsetNSPrefixes collects all subset namespace prefixes, and findAncestorNs filters ancestors against that set. Tests cover inherited default namespaces, retained non-default namespaces, ordinary xmlnsfoo attributes, and canonical output without duplicate default declarations.

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

Mergeability Score: ⚪ Minimal · up to fce45

The change corrects namespace filtering during canonicalization and adds focused regression coverage; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main fix: collecting all subset namespace prefixes when filtering ancestor namespaces.
Linked Issues check ✅ Passed The changes satisfy issue #538 by collecting all subset prefixes and filtering matching ancestor namespaces, with regression tests for duplicate default declarations.
Out of Scope Changes check ✅ Passed The utility changes and regression tests directly support issue #538 and the stated canonicalization objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils.ts`:
- Around line 242-245: Restrict the namespace detection in the loop over
subsetAttributes to XML namespace declaration names only: match the default
xmlns attribute or names beginning with xmlns: followed by a prefix, not
ordinary names such as xmlnsfoo. Update the condition and preserve the existing
prefix extraction and subsetNsPrefixes behavior for valid declarations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3afa25d4-29d9-4b56-849b-5f3227ba8c2c

📥 Commits

Reviewing files that changed from the base of the PR and between b673581 and d37e6bf.

📒 Files selected for processing (2)
  • src/utils.ts
  • test/c14n-non-exclusive-unit-tests.spec.ts

Comment thread src/utils.ts
Matthew Sheby added 2 commits August 12, 2026 16:45
…Prefixes

The /^xmlns:?/ regex also matched ordinary attributes whose names start
with "xmlns" but have no colon (e.g. xmlnsfoo). Such attributes are not
namespace declarations, but the old code added "foo" to the suppression
set, causing findAncestorNs to incorrectly drop an inherited xmlns:foo
declaration that must be hoisted to the subset root.

Replace the regex test with an exact equality check:
  nodeName === "xmlns" || nodeName.startsWith("xmlns:")

Adds a regression test to cover the xmlnsfoo case.
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.

findNSPrefix returns only the first xmlns declaration → duplicate default xmlns in non-exclusive C14N when subset declares any xmlns:*

1 participant