Skip to content

Fix dspace-customers 1083 V4: bind vocabulary node id as an XPath variable - #1556

Merged
milanmajchrak merged 3 commits into
dtq-devfrom
fix/v4-vocabulary-xpath-entryid
Oct 28, 2026
Merged

milanmajchrak merged 3 commits into
dtq-devfrom
fix/v4-vocabulary-xpath-entryid

Conversation

@jr-rk

@jr-rk jr-rk commented Oct 27, 2026 •

Copy link
Copy Markdown

References

dspace-customers issue 1083, finding V4.

Two upstream commits, cherry-picked:

  • ceab9526c4: DSpace PR 11075 ported to dspace-7_x (label search).
  • aa419b904f: DURACOM-447, part of DSpace PR 11964. It is only on main, not in dspace-7_x or dspace-9_x.

Description

DSpaceControlledVocabulary put the node id from the request straight into an XPath expression, so a ' in the id could change the query. This PR cherry-picks the two upstream commits that pass the id and the search text as XPath variables instead.

Instructions for Reviewers

List of changes in this PR:

  • ceab9526c4, clean cherry-pick. getMatches and getBestMatch bind the search text as XPath variables. The quoted id template is renamed to idTemplateQuoted.
  • aa419b904f, cherry-pick. getChoice, getChoicesByParent and getParentChoice bind the id as $nodeId. Only context lines differ from upstream (init() instead of init(locale)). Every changed line is the same.
  • New test testNodeIdIsNotEvaluatedAsXPath. The test farm.xml gets a pond node under s40, so the parent and children lookups have a real target.

After this PR the class matches upstream dspace-7_x except for two things: DURACOM-447 (not in 7_x yet) and the accented-letter label search from 7_x (a feature, not included).

Like upstream, getLabel(null) now throws. The only production caller, discovery indexing, checks that the authority is set first.

How to test: mvn -pl dspace-api test -DskipUnitTests=false -Dtest=DSpaceControlledVocabularyTest -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=false

AI Disclosure (REQUIRED)

Were AI tools used in the creation of this PR?

  • No (This PR does NOT include AI-generated or AI-assisted content that requires disclosure.)
  • Yes (This PR includes AI-generated or AI-assisted content that requires disclosure.)

Claude Code cherry-picked the upstream commits, resolved the context conflicts and adapted the test. The changed lines were compared with the upstream commits line by line. DSpaceControlledVocabularyTest passes locally (2/2), and the new test fails when the DURACOM-447 commit is left out.

Checklist

  • My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
  • My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
  • My PR follows all coding best practices based on the Code Conventions Guide.
  • My PR passes Checkstyle validation based on the Code Style Guide.
  • My PR includes Javadoc for all new (or modified) public methods and classes. It also includes Javadoc for large or complex private methods.
  • My PR passes all tests and includes new/updated Unit or Integration Tests based on the Code Testing Guide.
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in any pom.xml), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
  • If my PR modifies REST API endpoints, I've opened a separate REST Contract PR related to this change.
  • If my PR includes new configurations, I've provided basic technical documentation in the PR itself.
  • If my PR fixes an issue ticket, I've linked them together.

🤖 Generated with Claude Code

@jr-rk jr-rk self-assigned this Oct 27, 2026
@jr-rk
jr-rk requested a lite review from Copilot October 27, 2026 14:17

Copilot AI 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.

🟡 Changes recommended

A moderate active finding remains because the security-sensitive getParentChoice path lacks regression coverage for valid nested parent resolution and rejection of an injected child ID.

1 open issue
What changed in this PR

This pull request fixes XPath injection in controlled-vocabulary node lookups by binding request-provided IDs as XPath variables instead of interpolating them into expressions.

Changes:

  • Replaces interpolated node-ID predicates with the $nodeId XPath variable.
  • Adds shared XPath variable-resolver handling, including safe handling of null IDs.
  • Adds regression coverage for valid and injected IDs in choice and child lookups.
  • Adds a moderate test-coverage follow-up: getParentChoice should be tested with both valid and injected child IDs using a nested vocabulary fixture.
File Summary
dspace-api/​src/​test/​java/​org/​dspace/​content/​authority/​DSpaceControlledVocabularyTest.java Adds regression coverage confirming that valid IDs resolve and XPath-injection payloads do not select unintended entries. Parent lookup coverage remains incomplete.
dspace-api/​src/​main/​java/​org/​dspace/​content/​authority/​DSpaceControlledVocabulary.java Binds node IDs through an XPath variable resolver for node, parent, and child lookups.

Review effort: Lite

@jr-rk
jr-rk marked this pull request as ready for review October 27, 2026 14:22
@jr-rk
jr-rk requested a lite review from Copilot October 27, 2026 14:45

Copilot AI 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.

🟡 Changes recommended

A critical type mismatch in the new parent lookup causes a ClassCastException for valid parents, so children cannot be returned until the XPath evaluation uses NODESET.

1 open issue
1 resolved since last review

Review effort: Lite

Copilot AI 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.

🟢 Approval recommended

The security fix is covered by targeted regression tests, with no unresolved blocking issues identified.

No open issues 🎉

1 resolved since last review

Review effort: Lite

@jr-rk
jr-rk requested a review from milanmajchrak October 27, 2026 16:13
mwoodiupui and others added 3 commits October 28, 2026 13:14
(cherry picked from commit ceab952)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit aa419b9)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the upstream DURACOM-447 change in getChoice, getChoicesByParent
and getParentChoice. A 'pond' node under 's40' gives the parent and
children lookups a real target. dspace-customers issue 1083, finding V4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milanmajchrak
milanmajchrak force-pushed the fix/v4-vocabulary-xpath-entryid branch from 76d5415 to b8b3e8d Compare October 28, 2026 13:27
@milanmajchrak
milanmajchrak merged commit 612aa63 into dtq-dev Oct 28, 2026
16 of 17 checks passed
@jr-rk
jr-rk deleted the fix/v4-vocabulary-xpath-entryid branch October 28, 2026 14:34
Kasinhou pushed a commit that referenced this pull request Oct 29, 2026
Resolve overlap with #1556 (XPath variable-binding already landed on
dtq-dev): take the dtq-dev implementation of DSpaceControlledVocabulary
and keep this branch's CSV metadata.hide / formula-injection hardening.
farm.xml keeps both the nested s40>pond fixture and the o'reilly node so
the combined test suite passes.
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.

5 participants