Skip to content

fix: resolve XML-Enc elements by DOM traversal instead of XPath - #141

Open
elbuo8 wants to merge 1 commit into
auth0:masterfrom
elbuo8:fix/dom-element-resolution
Open

fix: resolve XML-Enc elements by DOM traversal instead of XPath#141
elbuo8 wants to merge 1 commit into
auth0:masterfrom
elbuo8:fix/dom-element-resolution

Conversation

@elbuo8

@elbuo8 elbuo8 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

decryptKeyInfo located elements with XPath expressions that were built by string concatenation and scoped by a leading "//". Both properties made the lookups behave differently from how they read.

"//" is descendant-or-self over the whole document regardless of the context node passed to select(), so lookups that appear scoped were document-wide:

  • the wrapped key was taken from the first CipherValue anywhere in the document rather than from the EncryptedKey being processed
  • DigestMethod was resolved by an absolute path, so with several EncryptedKey elements it could return a digest declared by one key while the ciphertext came from another
  • the absolute DigestMethod path also matched nothing for documents using EncryptedData/KeyInfo/RetrievalMethod, where EncryptedKey sits outside KeyInfo, silently defaulting oaepHash to sha1

Predicates were also assembled by concatenating the RetrievalMethod URI into the expression text, so an Id containing a quote changed what the expression meant instead of being compared as a value.

lib/dom-select.js walks the DOM explicitly. Document values are compared, never concatenated into anything that gets parsed, and scope is a node enforced by the walk itself. EncryptionMethod and CipherValue are now read from one resolved EncryptedKey, so they always describe the same key.

Two input-validation changes come with this:

  • a RetrievalMethod URI must be a same-document fragment reference. substring(1) removed the first character whatever it was, so "/ek1" and "Xek1" both resolved to Id "ek1".
  • a duplicated Id is reported rather than resolved to its first match. Duplicate Ids are invalid XML and resolving them makes the result depend on document order.

xpath moves to devDependencies; the runtime no longer parses expressions. Verified with a packed tarball installed via --omit=dev, where require('xpath') throws MODULE_NOT_FOUND and both fixtures still decrypt.

65 passing (44 before). Each property is pinned by a test that also asserts the previous XPath behaviour for contrast, and 11 mutations of the traversal and the guards were each confirmed to fail a named test.

By submitting a PR to this repository, you agree to the terms within the Auth0 Code of Conduct. Please see the contributing guidelines for how to create and submit a high-quality PR for this repo.

Description

Describe the purpose of this PR along with any background information and the impacts of the proposed change. For the benefit of the community, please do not assume prior context.

Provide details that support your chosen implementation, including: breaking changes, alternatives considered, changes to the API, etc.

If the UI is being changed, please provide screenshots.

References

Include any links supporting this change such as a:

  • GitHub Issue/PR number addressed or fixed
  • Auth0 Community post
  • StackOverflow post
  • Support forum thread
  • Related pull requests/issues from other repos

If there are no references, simply delete this section.

Testing

Describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors.

Please include any manual steps for testing end-to-end or functionality not covered by unit/integration tests.

Also include details of the environment this PR was developed in (language/platform/browser version).

  • This change adds test coverage for new/changed/fixed functionality

Checklist

  • I have added documentation for new/changed functionality in this PR or in auth0.com/docs
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not the default branch

decryptKeyInfo located elements with XPath expressions that were built by
string concatenation and scoped by a leading "//". Both properties made the
lookups behave differently from how they read.

"//" is descendant-or-self over the whole document regardless of the context
node passed to select(), so lookups that appear scoped were document-wide:

  - the wrapped key was taken from the first CipherValue anywhere in the
    document rather than from the EncryptedKey being processed
  - DigestMethod was resolved by an absolute path, so with several
    EncryptedKey elements it could return a digest declared by one key while
    the ciphertext came from another
  - the absolute DigestMethod path also matched nothing for documents using
    EncryptedData/KeyInfo/RetrievalMethod, where EncryptedKey sits outside
    KeyInfo, silently defaulting oaepHash to sha1

Predicates were also assembled by concatenating the RetrievalMethod URI into
the expression text, so an Id containing a quote changed what the expression
meant instead of being compared as a value.

lib/dom-select.js walks the DOM explicitly. Document values are compared,
never concatenated into anything that gets parsed, and scope is a node
enforced by the walk itself. EncryptionMethod and CipherValue are now read
from one resolved EncryptedKey, so they always describe the same key.

Two input-validation changes come with this:

  - a RetrievalMethod URI must be a same-document fragment reference.
    substring(1) removed the first character whatever it was, so "/ek1" and
    "Xek1" both resolved to Id "ek1".
  - a duplicated Id is reported rather than resolved to its first match.
    Duplicate Ids are invalid XML and resolving them makes the result depend
    on document order.

xpath moves to devDependencies; the runtime no longer parses expressions.
Verified with a packed tarball installed via --omit=dev, where
require('xpath') throws MODULE_NOT_FOUND and both fixtures still decrypt.

65 passing (44 before). Each property is pinned by a test that also asserts
the previous XPath behaviour for contrast, and 11 mutations of the traversal
and the guards were each confirmed to fail a named test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@elbuo8
elbuo8 requested a review from a team as a code owner July 31, 2026 20:21
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.

1 participant