The AT-URI Syntax Mess

This is a thread to talk about the AT-URI syntax situation, as described in this blog post:

Folks are encouraged to participate on the IETF ATP working group mailing list (I’ll start a thread there soon). But you can also share thoughts here.

8 Likes

I don’t have that strong opinions, there are just a few things I’d add:

  1. I’m not totally sold if having the DID in the URI, since when have an identity in there, you’d have to hop some pretty weird loops for anonymity. I can live with it, since we can have pseudonymity and I think that’s far more important, and that anonymity is really hard in this day and age.
  2. It may adviceable to make a new scheme, let atkeep the current structure, but make the new one RFC-compliant.
  3. I had some concerns that relative URI resolution of collections would be messy, I think I’d like "at://" AUTHORITY [ "/" COLLECTION "/" [ RKEY ] ] for that reason, but I didn’t actually try it out to see if it would be a problem.

Just to chime in, I feel like the at:/// would probably be my go to.

file:/// is everywhere and we don’t really think about it. It seems easy to support.

3 Likes

I kind of love mirroring file:///

Three slashes! We’re turning this protocol up to 11 3!

3 Likes

Ideally, I’d love to see us move mountains for at://{did}. The did is to people (via the plc) as the hostname:port is to businesses (via the dns). We’re over 30 years with DNS. It’s time for some innovation :slight_smile: in the URI standard.

Granted, there are some strong parallels to file:///{volume}/path vs at:///{did}/path

2 Likes

I want to make the case for percent-encoding as the strict form of AT-URIs, with the colon syntax we all write today defined as a lax form that normalizes to it. Bryan’s writeup does a good job showing why the current situation can’t stand still; this is my preferred way out.

Strict and lax forms of the same identifier

Take one of my own posts. The lax form is what everyone writes today:

at://did:web:ngerakines.me/app.bsky.feed.post/3jwdwj2ctlk26

The strict form percent-encodes the colons in the authority:

at://did%3Aweb%3Angerakines.me/app.bsky.feed.post/3jwdwj2ctlk26

These name the same record. The difference is that the strict form is a valid RFC 3986 URI and the lax form isn’t. Reg-name, the grammar for a hostname, allows unreserved characters, percent-encoded bytes, and sub-delims. A raw colon isn’t in that set, because colons in the authority are delimiters for userinfo and port. Percent-encoded bytes are explicitly allowed, so the strict form conforms with no changes to 3986 at all.

Why strict should be the percent-encoded form

It’s the only option that gets us conformance without side effects elsewhere. RFC 3986 stays untouched. There’s no new escaping convention to invent, like mapping colons to dashes, which just trades one encoding problem for another. And the identifier keeps its shape, DIDs in the authority and all.

And generic tooling starts working. Right now the colon form is rejected by some stock URI parsers and misparsed by others, which read everything after the last colon as a port. Different parsers disagreeing about the same input is a worse property than any single failure mode, because parser differentials are where security bugs live.

There’s also the standards question. A working group chartered to produce an IETF spec will have a hard time writing “this identifier violates RFC 3986 on purpose” into an RFC. Percent-encoding makes that sentence unnecessary.

Lax is an input format, not a wire format

Nobody has to stop typing at://did:plc:xyz. The proposal is to accept lax everywhere, normalize to strict at the boundaries that matter, and emit strict when writing records.

This pattern has good precedent. RFC 3987 defines IRIs and a deterministic mapping to URIs via percent-encoding; systems pick one form for storage and comparison and convert at the edges. The WHATWG URL standard has browsers accept messy input and serialize exactly one canonical form: parse liberally against a defined grammar, serialize strictly.

The cautionary tale is leniency without a grammar. When lax parsing is folklore instead of spec, every implementation invents its own, and the differences between them become attack surface. RFC 9413 covers this failure mode well. So the lax form needs its own ABNF and exactly one mapping function to strict, published in the spec, not left to each SDK.

The ugly

did:web with a port is the ugly case. The DID did:web:localhost%3A8080 already contains %3A, so the strict authority becomes did%3Aweb%3Alocalhost%253A8080. That double-encoding looks bad, but it’s correct and unambiguous. One decode at the at-uri layer returns the DID byte for byte, and neither layer has to guess who owns the escape.

It’s also rarer than it first appears. The atproto DID spec only allows did:web ports for localhost, in testing and development, so %253A shouldn’t show up in production data at all. The spec mechanism still has to handle it, since generic DID syntax permits percent signs, but the cost lands on dev tooling rather than on the network.

Readability is a real cost too. Strict form is worse to look at. That’s fine, because that’s what the lax form is for. Humans read and write lax; software stores and compares strict.

Migration is the big one. The installed base is overwhelmingly lax, and those URIs live inside signed records that can’t be rewritten. That means equivalence has to be defined over the normalized form rather than raw bytes: two references to the same record match after normalization even if one is lax and one is strict. Dedup, likes, and subject refs all key on the canonical form. The normalization itself is mechanical and cheap.

What I’d want the spec to say

Concretely: ABNF for both forms, exactly one normalization function from lax to strict (colon to %3A in the authority, percent to %25 within the DID, uppercase hex digits, encode nothing that doesn’t need it), and conformance language along the lines of MUST accept lax, SHOULD emit strict, MUST normalize before comparing or deriving keys.

I’d especially like to hear from other implementers about where mandatory normalization would hurt. My read is that it’s cheap everywhere that matters, but that’s exactly the kind of claim that deserves pushback.

2 Likes

I like this!

Does the requirement to encode before comparing break most existing badge.blue signatures?

1 Like

Yeah, the migration for this means that any records that have or use strong refs or attestations to other records that have AT-URIs in them would need to be rebuilt. The big caveat and important thing to note is that it is only for records that migrate.

IMO there’s nothing wrong with leaving existing records as-is. It’s probably the smart thing to do.

1 Like

I meant more that, if the spec requires that libraries “MUST normalize before comparing keys” I see there being a failure to produce the same signature on unmigrated records.

I think you’re suggesting that the normalisation would be to the strict form. Wouldn’t that mean that a badge.blue signature checker that’s compliant in this way would look at an unmigrated “lax form” DID, normalise it (to the strict form), then attempt to validate the signature with that, declaring the sig incorrectly invalid?

(Or perhaps the badge.blue signatures are a layer above, and just operate on the records as they are, without the normalisation!)

1 Like

Yeah, I’m specifically saying that AT-URI values should be normalized before records are committed to repositories, and then some amount of backwards compatibility allowed on validation to support libraries and applications that are late to (or refuse to) adopt strict mode.

I think that badge award (or any attestation record or lexicon record in general) should allow for strict vs lax lexicon schema validation.

1 Like

Voicing my support for the “move the mountains” approach, despite the belief it would “face pushback so significant that you should not even consider the possibility” (as stated by Ted Hardie in the IETF ATP mailing list). The usefulness of DID-as-authority can, and should, extend beyond ATP, and the URI spec will most likely have to face this possibility eventually. Modifying the generic syntax in this way will not require all existing HTTP URI parsers to adapt, as believed by Ted. HTTP libraries, tools, and applications in other ecosystems are free to restrict the range of valid authorities down to the traditional selection of domain names and IP addresses. Opening up the possibility of DID-as-authority only concerns the ATP ecosystem at this moment, and as well as any future applications that explicitly choose to use DID-as-authority.

3 Likes