fix(ror): reject ids outside the Crockford alphabet and bad checksums - #150
Conversation
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.
|
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 |
|
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:
So the narrow range is correct today, and the wider one is what they have reserved. A validator pinned to This PR's change is a different claim, and it holds regardless of which range they use. 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 Disclosure: this comment and the change were drafted with an AI assistant (Claude Opus 4.8). |
|
Let's leave this open for more feedback from others. |
Description
is_roraccepts strings ROR cannot issue, becauseror_regexpmatches 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".
\wadmitsi,l,o,uand_, and the checksum is never verified, so on masteris_ror("0iiiiii99"),is_ror("0______12")andis_ror("05dxps056")— one digit off Caltech's real05dxps055— all pass.That matters downstream:
idutils.is_roris 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_roradds the negative cases; all four fail on master.The reference URL is updated as well —
https://ror.org/facts/#core-componentsnow returns 404.Checklist
Ticks in all boxes and 🟢 on all GitHub actions status checks are required to merge:
Frontend