Skip to content

Commit 20bb02d

Browse files
authored
fix: authority binding accepts exact name-host match (#627)
The Authority Binding rule required `name` to begin with `authority_prefix` followed by a `.`, forcing a non-empty label beyond the authority. That rejected entities whose name is itself a controlled domain — e.g. a payment handler `com.example.pay` served from `pay.example.com`, where the reversed host equals the name — despite provenance being fully proven. The requirement, introduced in #530, effectively baked the {reverse-domain}.{service}.{capability} naming convention (scoped to capabilities and services) into the provenance binding, which applies to every entity including payment handlers. Split step 4 into an exact-match case and a prefixed case, clarify that binding is provenance-only (no local segment required), and note that a name binds from its exact host or a parent authority. Examples are neutral; the label-boundary anti-spoofing guarantee is unchanged.
1 parent e1b4917 commit 20bb02d

1 file changed

Lines changed: 49 additions & 28 deletions

File tree

docs/specification/overview.md

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,9 @@ from any host (e.g. a docs subdomain or third-party docs host). Only the
297297

298298
The authority is derived **from the `schema` URL host** — which names the
299299
owning domain directly, with no ambiguity about where the domain ends — and
300-
validated as a label prefix of the entity's name. For the `schema` URL of an
301-
entity whose name is `name`, a platform **MUST** apply the following:
300+
validated as a label-aligned prefix of, or an exact match for, the entity's
301+
name. For the `schema` URL of an entity whose name is `name`, a platform
302+
**MUST** apply the following:
302303

303304
1. Parse the URL with a conformant (WHATWG) URL parser. It **MUST** parse,
304305
**MUST** use the `https` scheme, and **MUST NOT** contain userinfo (a
@@ -312,32 +313,52 @@ entity whose name is `name`, a platform **MUST** apply the following:
312313
strip a trailing `.`; internationalized domains in A-label / punycode form),
313314
and **reverse its labels** to form the `authority_prefix` (host `ucp.dev`
314315
`dev.ucp`).
315-
4. The binding is valid if and only if `name` begins with `authority_prefix`
316-
followed by a `.` (a literal trailing dot). The trailing-dot boundary is
317-
required so that `com.example` (from host `example.com`) cannot satisfy a
318-
neighboring namespace like `com.examplecorp.*`, where it is a textual but
319-
not label-aligned prefix; it also guarantees a non-empty remainder after the
320-
prefix.
321-
322-
The remaining labels after the authority prefix are treated as opaque by this
323-
check; they are not inspected or split.
324-
325-
| Capability name | `schema` host | `authority_prefix` | Result |
326-
| ----------------------------------- | ------------------ | ------------------ | ---------- |
327-
| `dev.ucp.shopping.checkout` | `ucp.dev` | `dev.ucp` | **accept** |
328-
| `dev.ucp.shopping.checkout` | `shopping.ucp.dev` | `dev.ucp.shopping` | **accept** |
329-
| `com.example.payments.installments` | `example.com` | `com.example` | **accept** |
330-
| `com.example.pay` | `evil.example` | `example.evil` | **reject** |
331-
| `dev.ucp.shopping.checkout` | `evil.example` | `example.evil` | **reject** |
332-
| `com.examplecorp.pay` | `example.com` | `com.example` | **reject** |
333-
| `com.example.pay` | `cdn.example.com` | `com.example.cdn` | **reject** |
334-
335-
An entity's `schema` is served from a host whose reversed labels are a prefix of
336-
its name. A canonical apex host (`example.com` for `com.example.*`) always
337-
satisfies this; a subdomain satisfies it only when its labels line up with the
338-
namespace path (`shopping.ucp.dev` for `dev.ucp.shopping.*`). Unrelated
339-
subdomains such as a shared CDN do **not** satisfy it — host the canonical
340-
schema on a name-aligned origin.
316+
4. The binding is valid if and only if **either** of the following holds:
317+
- **Exact match**`name` equals `authority_prefix`. The name is itself the
318+
reversed host, so the publisher demonstrably controls the entire namespace.
319+
This is the shape for an entity whose identity is a bare controlled domain,
320+
such as a payment handler `com.example.pay` served from `pay.example.com`
321+
(reversed host `com.example.pay` equals the name).
322+
- **Prefixed**`name` is `authority_prefix`, then a `.`, then one or more
323+
further labels; that is, the character immediately after `authority_prefix`
324+
in `name` is a `.`. Requiring that separating `.` keeps the match on a
325+
label boundary — it stops `com.example` (host `example.com`) from matching
326+
a neighboring namespace like `com.examplecorp.*`, where `com.example` is a
327+
textual prefix but not a label-aligned one.
328+
329+
Authority binding establishes **provenance only** — that the name is controlled
330+
by the party serving its `schema`. It does **not** require any label beyond the
331+
authority itself. The `{reverse-domain}.{service}.{capability}` shape is a
332+
separate [Naming Convention](#naming-convention) that governs capability and
333+
service names — it does not apply to payment handlers — and is validated
334+
independently of this check.
335+
336+
Any labels after the authority prefix are treated as opaque by this check; they
337+
are not inspected or split.
338+
339+
| Entity name | `schema` host | `authority_prefix` | Result |
340+
| ----------------------------------- | ------------------ | ------------------ | ------------------- |
341+
| `dev.ucp.shopping.checkout` | `ucp.dev` | `dev.ucp` | **accept** (prefix) |
342+
| `dev.ucp.shopping.checkout` | `shopping.ucp.dev` | `dev.ucp.shopping` | **accept** (prefix) |
343+
| `com.example.payments.installments` | `example.com` | `com.example` | **accept** (prefix) |
344+
| `com.example.pay` | `pay.example.com` | `com.example.pay` | **accept** (exact) |
345+
| `com.example.pay` | `example.com` | `com.example` | **accept** (prefix) |
346+
| `com.example.pay` | `evil.example` | `example.evil` | **reject** |
347+
| `dev.ucp.shopping.checkout` | `evil.example` | `example.evil` | **reject** |
348+
| `com.examplecorp.pay` | `example.com` | `com.example` | **reject** |
349+
| `com.example.pay` | `cdn.example.com` | `com.example.cdn` | **reject** |
350+
351+
An entity's `schema` is served from a host whose reversed labels either **equal**
352+
its name or are a **label-aligned prefix** of it. A host whose reversed labels
353+
are exactly the name (`pay.example.com` for `com.example.pay`) satisfies the
354+
exact case; a canonical apex host (`example.com` for `com.example.*`) satisfies
355+
the prefix case; a subdomain satisfies the prefix case only when its labels line
356+
up with the namespace path (`shopping.ucp.dev` for `dev.ucp.shopping.*`). Because
357+
a parent domain's reversed labels are also a prefix, a name such as
358+
`com.example.pay` binds equally from its exact host (`pay.example.com`) or a
359+
parent authority (`example.com`) — both prove control. Unrelated subdomains such
360+
as a shared CDN do **not** satisfy any case — host the canonical schema on a
361+
name-aligned origin.
341362

342363
The check uses the `schema` URL host directly and does not consult the
343364
[Public Suffix List](https://publicsuffix.org/), so it treats a **public

0 commit comments

Comments
 (0)