Skip to content

Fix TID validation rejecting valid TIDs starting with a-j - #695

Merged
MarshalX merged 5 commits into
MarshalX:mainfrom
jacoblapenna:fix/tid-first-char-validation
Jul 18, 2026
Merged

Fix TID validation rejecting valid TIDs starting with a-j#695
MarshalX merged 5 commits into
MarshalX:mainfrom
jacoblapenna:fix/tid-first-char-validation

Conversation

@jacoblapenna

Copy link
Copy Markdown
Contributor

Hi! Great work in this SDK!

While writing spec-conformance tests for an AT Protocol project I'm working on, I used this library's validators as a cross-check and hit a case where we disagreed on a valid TID. I believe the first-character check in validate_tid has a subtle bug:

if not TID_RE.match(v) or (ord(v[0]) & 0x40):

The intent (per the TID spec) is that the high bit of the decoded 64-bit value must be 0, which restricts the first character to 234567abcdefghij. But ord(v[0]) & 0x40 tests bit 6 of the character's ASCII encoding, and every lowercase letter has that bit set — so all spec-valid TIDs starting with a–j are rejected:

TypeAdapter(Tid).validate_python('a222222222222', context={'strict_string_format': True})
# ValidationError, but this is a valid TID

This slipped past CI because the interop test files don't include a letter-leading valid TID. This is true of the upstream bluesky-social/atproto vectors too (I checked; the vendored copies here are identical to upstream), so the suite was rightfully green. I'm thinking of proposing the missing vector upstream as well.

The fix encodes the constraint in the regex itself, matching the reference implementation in @atproto/syntax tid.ts character-for-character (/^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/), and removes the ASCII-bit check. Added parametrized tests for valid first characters (a, j) and invalid ones (k, z, 1, 8). The valid-case tests fail on main and pass with this change. Full suite: 445 passed.

Happy to adjust anything for naming, test placement, whatever fits your conventions best. Thanks!

The previous check (ord(v[0]) & 0x40) was intended to enforce that the
high bit of the decoded 64-bit TID value is 0, but it tested the ASCII
encoding of the first character instead. All lowercase letters have the
0x40 bit set in ASCII, so every spec-valid TID starting with a-j was
rejected (only digit-leading TIDs passed).

Encode the constraint in the regex instead, mirroring the reference
implementation: the first character must be one of 234567abcdefghij.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jacoblapenna

Copy link
Copy Markdown
Contributor Author

Follow-up on the interop gap mentioned above: I've proposed the missing vectors upstream in bluesky-social/atproto#5243. It adds three letter-first valid TIDs (a222222222222, j222222222222, azzzzzzzzzzzz) to tid_syntax_valid.txt — the accepted side of the boundary that let this bug pass the interop suite. If it lands, this SDK's interop-driven tests will cover the a–j first-character range automatically, and the fix in this PR is exactly what makes them pass.

@MarshalX MarshalX left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this and for the fix! I left a few inline comments, all minor

Happy to merge once the docstring wording is adjusted

Comment thread packages/atproto_client/models/string_formats.py Outdated
Comment thread tests/test_atproto_client/models/tests/test_string_formats.py Outdated
Comment thread tests/test_atproto_client/models/tests/test_string_formats.py Outdated
Comment thread tests/test_atproto_client/models/tests/test_string_formats.py Outdated
@jacoblapenna

Copy link
Copy Markdown
Contributor Author

All updated per comments.

@MarshalX
MarshalX merged commit 15b9499 into MarshalX:main Jul 18, 2026
22 checks passed
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