不設註冊表,一樣能回答「這個 capability 名字是誰的」。UCP(Universal Commerce Protocol)的做法只有一條:名字倒著寫,再跟它宣告的 schema 網址對一下 host。

規格寫的是 “Businesses publish their profile at /.well-known/ucp“,platform 讀到 business 宣告的 capability,名字叫 com.example.pay,附一個 schema 網址。手上就這兩個字串。要不要去抓那份 schema?要不要承認這個名字真的歸 example.com?沒有註冊表可以查,也沒有人可以問。

規格的答案寫在一開頭:”UCP uses reverse-domain naming to encode governance authority directly into capability identifiers. This eliminates the need for a central registry.” 我覺得更有意思的是這句話背後的那條規則:它寫了兩次,第一次寫壞了。

一條寫了就沒人能照做的規則

tag v2026-01-11 的規格裡,這一節叫 “Spec URL Binding”。原文是 “The origin of these URLs MUST match the namespace authority”,下面列了兩個對應:dev.ucp.* 對 https://ucp.dev/...,com.example.* 對 https://example.com/...。後面還有一句 “Platform MUST validate this binding and SHOULD reject capabilities where the spec origin does not match the namespace authority.”

驗證用 MUST,拒絕用 SHOULD。同一條規則,兩個強度。我比對過 v2026-01-23 和 v2026-04-08 兩個 tag,同一節還是這個講法。

改寫它的是 PR #530(作者 igrigorik,2026-07-02 合併),描述裡自己就把問題講完了:”Existing spec asserted "origins MUST match the namespace authority" but never said how to compute the authority, and its two normative statements disagreed (MUST validate vs SHOULD reject) — leaving the rule unenforceable.” 同一個 PR 還把章節改名成 Authority Binding,理由是被綁定的東西是 schema 網址,不是 spec 網址。

接著是 #627,2026-07-24 合併,標題 “fix: authority binding accepts exact name-host match”。距離 #530 22 天,補的是名字剛好等於 host 反轉結果的那種情形。演算法版本的規則第一次出現在 release 裡,是 v2026-08-25。我只確認了 tag 的內容與兩個 PR 的合併時間,v2026-04-08 到 v2026-08-25 之間的 main 上最早是哪個 commit 帶進來,我沒查。

這段歷史對我最有用的地方是:一條「必須相符」的規則,如果沒說怎麼算「相符」,每個實作者會各自發明一種算法。從 1 月的初版到 7 月的 #530,算法才補上,補完 22 天內又補了一個洞。

倒著寫的網域

演算法分四步,我照規格原意整理。第一步用 WHATWG URL parser 解析 schema 網址,必須是 https、不得帶 userinfo。規格特別講了不准對原始字串做子字串比對,例子是 https://[email protected]/x.json,它的 host 是 evil.example,不是 ucp.dev。第二步,host 至少兩個 label,IP 和單 label 的 host 無效。第三步,把 hostname 正規化後 “reverse its labels to form the authority_prefix“,ucp.dev 變成 dev.ucp。第四步看 capability 名字,合法的形態只有兩種:名字剛好等於 authority_prefix,或者名字裡 authority_prefix 後面緊接的字元是 .。

重點在最後那個條件。規格自己解釋了理由:”it stops com.example (host example.com) from matching a neighboring namespace like com.examplecorp.*, where com.example is a textual prefix but not a label-aligned one.”

com.examplecorp.pay 是 com.example 的文字前綴,這在任何 startswith 眼裡都成立。如果實作者偷懶,用 name.startswith(prefix) 就收工,example.com 的擁有者就能宣告 examplecorp 的名字。差別只在 prefix 後面多不多檢查一個點。這個點是整套規則裡我最意外的一個字元,因為它看起來完全像格式細節。

我把這套演算法用 Python 實作了一遍,用 python3 -I 實跑。核心長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib.parse import urlsplit
def authority_ok(name, schema_url):
u = urlsplit(schema_url)
if u.scheme != "https": return False, "scheme"
if u.username is not None or u.password is not None or "@" in u.netloc: return False, "userinfo"
host = (u.hostname or "").lower().rstrip(".")
labels = host.split(".")
if len(labels) < 2: return False, "single-label"
if all(l.isdigit() for l in labels): return False, "ip-literal"
prefix = ".".join(reversed(labels))
if name == prefix: return True, "exact"
if name.startswith(prefix + "."): return True, "prefix"
return False, "mismatch(prefix=%s)" % prefix

規格附的判例表有 9 列,我的輸出與它標示的 accept 或 reject(含 exact、prefix 分類)9 列全部一致。另外我加了 4 個邊界。下表前兩列的名字取自規格的命名範例,網址路徑 s.json 是我隨手取的,中間四列是規格判例表裡的 reject,最後四列是我加的。

capability 名字 schema 網址 結果 原因
dev.ucp.shopping.checkout https://ucp.dev/s.json accept prefix 為 dev.ucp,後面接 .
com.example.payments.installments https://example.com/s.json accept prefix 為 com.example,後面接 .
com.example.pay https://evil.example/s.json reject prefix 為 example.evil
dev.ucp.shopping.checkout https://evil.example/s.json reject prefix 為 example.evil
com.examplecorp.pay https://example.com/s.json reject com.example 後面接的是 c,不是 .
com.example.pay https://cdn.example.com/s.json reject prefix 為 com.example.cdn
dev.ucp.shopping.checkout https://[email protected]/x.json reject 帶 userinfo
dev.ucp.shopping.checkout https://203.0.113.10/s.json reject IP
dev.ucp.shopping.checkout https://localhost/s.json reject 單 label host
dev.ucp.shopping.checkout http://ucp.dev/s.json reject 非 https

cdn 那一列值得多看一眼。cdn.example.com 明明是 example.com 底下的子網域,卻被擋掉。規格的態度很明確:”Unrelated subdomains such as a shared CDN do not satisfy any case — host the canonical schema on a name-aligned origin.” 想用名字 com.example.pay,schema 就得放在 example.com 或名字對得上的位置,不能因為「都是我家的」就放到別的子網域去。

我寫的檢查器是照規格文字重述的實作,用的是 Python 的 urlsplit,不是 WHATWG parser,IP 判斷也只做了「每個 label 都是數字」這個簡化版。它只證明規格的規則自洽、判例表與規則一致,不是官方驗證器的輸出,我也沒有跑 ucp-schema 的任何實作。

想看真實 profile 的那條死路

我對 https://ucp.dev/.well-known/ucp 打了 curl,想看一份真正的 profile,回 HTTP 404,content-type 是 text/html。規格範例裡的兩條路徑 https://ucp.dev/v2026-08-25/schemas/shopping/checkout.json 與 .../services/shopping/rest.openapi.json 也都是 404。

我沒查原因。可能是站台路徑和規格範例裡 {{ ucp_version }} 這類樣板的展開方式不同,也可能是別的,我不下判斷,也不認為這代表規格站有問題。下次再想看真實檔案,我不會再拿規格範例的路徑去打線上網址,那不保證對應到實際存在的檔案。所以這份文章裡沒有任何一份真實部署的 profile,採用情形我也沒查到可驗證的一手來源,一律不寫。

規格自己劃的界線

比演算法更值得讀的是它承認自己不做什麼。

第一,binding 只證明出處,不證明可信。原文是 “This binding guarantees provenance, not trust“,後面接著 “Whether to negotiate, trust, or implement it is always the client’s decision; this binding only tells the client who is making the claim.”

第二,只有 schema 網址綁定。spec 網址被當成文件,”MUST be https but MAY be served from any host”。

第三,它也不是 SSRF 防護。”Binding validates the declared hostname for provenance; it is not a fetch-safety control and does not authorize dereferencing.” 解析後的位址另有 fetch-safety 要求,兩個檢查互相獨立,都要做。抓 schema 時也 “MUST NOT follow redirects”。

第四,它不查 Public Suffix List,會把 public suffix 當成普通的 authority。規格給宣告者的建議是只在自己獨佔的 registrable domain 底下宣告實體。這個縫隙實際會造成什麼後果,我沒構造攻擊案例,只引用規格文字。

執行面是對稱的。Platform 端 “MUST reject capabilities that fail this binding”,business 端雖然通常不會去抓 platform 宣告的 schema,規格仍要求 “SHOULD nonetheless verify the namespace binding … as defense in depth.”

治理的部分很短:dev.ucp.* 屬於 ucp.dev,由 “UCP governing body” 核可;廠商自訂的 capability 必須放自己的 reverse-domain 底下。

我的立場,與會讓我改口的條件

我認為這個設計的方向是對的:把「誰在說」交給網域所有權這個已經存在的東西,把「要不要信」留給 client。註冊表要有人營運、有人審核,而 DNS 的擁有關係本來就在那裡。代價也很清楚,整套安全性都壓在 platform 把那四步做對。差一個點,就等於沒有 binding。

有兩種情況我會改口。其一,如果 ucp-schema 裡實作這個演算法的實作,行為與規格文字對不上,那條規則就只存在於文件。PR #530 提到有一個對應的 enforcement PR(ucp-schema#34),它合併了沒有、現在驗證器的行為如何,我沒查。其二,如果有人實際展示共租戶網域(規格說不查 PSL 的那一類)被拿來冒用名字,我會認為「交給宣告者自己挑 registrable domain」這個分工太樂觀。

本站之前寫過的 AP2、x402、Web Bot Auth,這裡不重述。UCP 規格自己的 Scenario C 標題就是 “Autonomous Agent (AP2)”,兩者在規格裡有交會。

名字屬於誰,和要不要相信這個人,是兩條線。UCP 只把第一條畫成演算法,第二條明寫留給 client。以後讀協定的治理章節,我會先問它畫了哪一條,另一條有沒有人接。

(規格條文取自 main 分支,2026-10-31 抓取;最新 release 是 v2026-08-25,我沒有逐字比對 main 與該 tag 在這幾節是否完全相同。來源:規格主檔、PR #530、PR #627、repo。)