Repository navigation
Conversation
added 3 commits
October 27, 2026 14:50
POST/PUT /api/services/handles stored input that the shortener's own host policy did not cover: - @magicLindat@ in title, reportemail, datasetName, datasetVersion or query shifted the parts of the stored magic URL, so the resolver read text from the title as the destination, outside the host rules. - java.net.URL took the host after '@' in https://evil.example\@allowed/ while browsers stop the host at the backslash; file:// and other schemes passed when the host was allowed. - Host rules compared the raw spelling: BLOCKED.example, the root-dot form and fullwidth/Unicode spellings passed a lowercase deny rule. - reportemail was not checked and the record size was unbounded (PostgreSQL handle.url is varchar without a limit). - A failed PUT echoed the SQLException message to the client. The URL is now parsed with java.net.URI (http/https with an ASCII host, IDN as punycode), host rules match case-insensitively without the root dot, reportemail must be a valid address, the stored record is limited to 8192 characters (the H2 column), and PUT errors return a generic message and log the exception.
Each endpoint has its own check; in one test the PUT case never ran once the POST assertion failed.
A too long subprefix reached the database and returned 500; it now returns 400 before anything is written.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
References
dspace-customers issue 1083, finding R03 (shortener input validation).
No upstream fix: ufal/clarin-dspace
clarin-v7has no change toExternalHandleRestRepositorybeyonddtq-dev(git log --remotes=ufal --not origin/dtq-devon the file;gh search prs --repo ufal/clarin-dspaceforshortener,magicLindat,ExternalHandle). DSpace/DSpace has no such controller.Related: the delimiter check was added and reverted in #1508 ("Bad shortener input is a separate issue").
Description
The anonymous shortener (
POST/PUT /api/services/handles) stored input its own host policy did not cover:@magicLindat@intitle(or another metadata field) shifts the parts of the stored record. The resolver takes the last part as the destination, so a title ending inhttps://evil.example/makes the short link redirect there, whatever the allowed hosts are.java.net.URLreadshttps://evil.example\@lindat.cz/as hostlindat.cz(allowed); browsers end the host at\and go toevil.example.file://and other schemes passed when the host was allowed.BLOCKED.example,blocked.example.and fullwidthblocked.examplepassed ablocked\.exampledeny rule. The same applies to the deny rules shipped oncustomer/TUL,sav,uk,zcu-data,zcu-pub(.*\\.com;.*\\.xyz;...):https://EXAMPLE.COM/some/pathwas not denied.reportemailwas not checked; the record size was unbounded (handle.urlisvarcharwithout a limit on PostgreSQL); asubprefixtoo long for thehandlecolumn reached the database (500).PUTreturned theSQLExceptionmessage to the client.Fix:
java.net.URI; onlyhttp/httpswith an ASCII host.@magicLindat@rejected intitle,reportemail,datasetName,datasetVersion,query(reportemailtoo:EmailValidatoraccepts a quoted local part such as"x@magicLindat@y"@example.org).reportemailchecked with commons-validatorEmailValidator.handle.urlcolumn) onPOSTandPUT;<prefix>/<subprefix>-<random>limited to the 256 characters of thehandlecolumn.PUT500 returns a generic message; the exception is logged.Behaviour changes:
",<>,{},|,\,^, backtick or a bare%is rejected (400). Clients must percent-encode.xn--...). Not mapped withjava.net.IDNon purpose: it is IDNA2003, browsers use UTS testIssue 316 #46, and the two disagree (e.g.faß.de).https://LINDAT.CZ/is allowed by the default rule). URL rules still match the raw URL.reportemailor a too longsubprefixreturns 400.PUTdoes not check the handle/PID format or re-validate the stored title, e-mail etc., so legacy handles keep their update path. One exception:PUTreturns 400 when the stored metadata plus the new URL exceed 8192 characters.Instructions for Reviewers
Changes:
ExternalHandleRestRepository: URL/host/e-mail/delimiter/size checks, genericPUTerror.ExternalHandleRestRepositoryIT: 9 tests;POSTfixtures use a valid e-mail so the existing host/URL rule tests fail for the URL, not the e-mail.Tests:
createRejectsMagicDelimiterInMetadata:@magicLindat@in metadata moved the redirect destination outside the host rules.createRejectsUrlWithBackslashBeforeAt:java.net.URLchecked a different host than browsers visit.createRejectsNonHttpUrl: a non-http(s) scheme passed with an allowed host.hostRulesIgnoreCaseRootDotAndUnicodeSpelling: case, root-dot and fullwidth spellings passed a deny rule; an uppercase allowed host was refused.createRejectsInvalidReportEmail: a non-addressreportemailwas stored.createRejectsRecordLongerThanUrlColumn:POSTstored a record larger than the URL column.createRejectsSubprefixLongerThanHandleColumn: a too longsubprefixreached the database.updateRejectsRecordLongerThanUrlColumn:PUTstored a record larger than the URL column.updateErrorDoesNotRevealDatabaseDetails:PUTechoed theSQLExceptionmessage.How to test:
mvn -pl dspace-server-webapp -DskipIntegrationTests=false -Dit.test=ExternalHandleRestRepositoryIT verifyLocal run (JDK 11, Windows),
ExternalHandleRestRepositoryIT: before the fix the 9 new tests fail (200 or 500 instead of 400, or the leaked message) and the 15 existing ones pass; after the fix 24/24 pass. Checkstyle: 0 violations.Not in scope:
http://2130706433/) are not normalised. Host rules run without DNS, so an IP deny rule can always be bypassed by a host name that resolves to that IP. No shipped config has an IP deny rule.shortener.post.host.whitelist.regexps = lindat(\\.mff\\.cuni)?.czhas an unescaped.beforecz. Besides the two intended hosts it only matches names outside public DNS (lindatXcz,lindat.mff.cuniXcz). It is customer configuration and is not changed here.