Skip to content

fix(ror): reject ids outside the Crockford alphabet and bad checksums - #150

Open
dylanpulver wants to merge 1 commit into
inveniosoftware:masterfrom
dylanpulver:fix/ror-crockford-alphabet-and-checksum
Open

fix(ror): reject ids outside the Crockford alphabet and bad checksums#150
dylanpulver wants to merge 1 commit into
inveniosoftware:masterfrom
dylanpulver:fix/ror-crockford-alphabet-and-checksum

Conversation

@dylanpulver

Copy link
Copy Markdown

Description

is_ror accepts strings ROR cannot issue, because ror_regexp matches the six character body with \w.

ROR's documentation (https://ror.readme.io/docs/identifier) says the id "uses base 32 Crockford encoding, which excludes letters 'I', 'L', 'O', and 'U'", and that "the last 2 digits of the ROR identifier are a checksum that follows the ISO/IEC 7064:2003 standard". \w admits i, l, o, u and _, and the checksum is never verified, so on master is_ror("0iiiiii99"), is_ror("0______12") and is_ror("05dxps056") — one digit off Caltech's real 05dxps055 — all pass.

That matters downstream: idutils.is_ror is the validator InvenioRDM registers for the ROR scheme it sends to DataCite (invenio_rdm_records/config.py), and invenio-vocabularies uses it for affiliations and funders, so a mistyped organisation id reaches the record.

This builds the character class from the Crockford alphabet and verifies the MOD 97-10 checksum — the same check digit validation this library already does for ISBN, ISSN, ISNI, ORCID, EAN-8, EAN-13 and ISTC. ROR was the outlier.

Measured against the complete ROR registry dump (v2.12, 2026-08-25, 137,398 ids): 0 rejected, so no real id regresses. Over 648,000 single character typos of 2,000 real ids, master accepts 468,000 (72%) and this accepts 0. Fixing only the character class still accepts 408,000, which is why the checksum is here too.

One caveat worth your call: this is a tightening, so an instance holding malformed ROR values would start failing validation on them. In invenio-vocabularies two fabricated funder ids in test fixtures (0aaaaaa11, 0aaaaaa22) would be rejected. If you would rather land only the alphabet half, or gate the checksum, say so and I will split it.

Existing ROR coverage is one positive id, so test_ror adds the negative cases; all four fail on master.

The reference URL is updated as well — https://ror.org/facts/#core-components now returns 404.

Checklist

Ticks in all boxes and 🟢 on all GitHub actions status checks are required to merge:

Frontend

The ROR id pattern used "\w" for the six character body, which admits
"i", "l", "o", "u" and "_" -- characters the Crockford base 32 alphabet
excludes -- and the two trailing digits were never checked against the
ISO/IEC 7064:2003 checksum they carry, so a single character typo in a
real ROR id validated.

Build the character class from the Crockford alphabet and verify the
checksum, matching the check digit validation already done for ISBN,
ISSN, ISNI, ORCID, EAN-8, EAN-13 and ISTC.

All 137398 ids in the ROR registry dump v2.12 (2026-08-25) still
validate. The reference URL is updated because
https://ror.org/facts/#core-components now returns 404.
@tmorrell

tmorrell commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I agree this is a better validator. There was actually some discussion about this in ROR recently, and they actually only use the first part of the integer range. So we might be able to make the check even more restrictive: ror-community/ror-roadmap#407

@dylanpulver

Copy link
Copy Markdown
Author

Thanks — I read that thread, and I think it argues for stopping where this PR stops, though it is genuinely your call.

ROR answered it directly in ror-community/ror-roadmap#407. Adam Buttrick, closing it:

the current intended range is as described in the code. It is possible we could revise to include the full range at some point in the future, but have no such plans or use-case for doing so at present.

So the narrow range is correct today, and the wider one is what they have reserved. A validator pinned to [0, 200000000] would be right now and would start rejecting real identifiers the day they widen it, with no signal to us that it happened. The failure mode is also the bad direction: a legitimate ROR identifier silently refused, rather than a malformed one accepted.

This PR's change is a different claim, and it holds regardless of which range they use. \w admits i, l, o, u and _, which Crockford base-32 excludes by construction, so those are not valid encodings at any range. That part is safe to fix now.

Happy to add the range check as well if you would rather have it, or to open it separately so the two can be judged on their own merits. If you do want it, I would suggest a comment naming ror-api's generaterorid.py as the source, so the next person knows where the bound came from.

Disclosure: this comment and the change were drafted with an AI assistant (Claude Opus 4.8).

@tmorrell

tmorrell commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Let's leave this open for more feedback from others.

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.

2 participants