provenance-protocol 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SPEC.md +22 -0
- package/package.json +2 -2
- package/src/cli.js +0 -0
- package/src/verify.js +20 -0
package/SPEC.md
CHANGED
|
@@ -251,8 +251,30 @@ provenance:npm:@scope/package-name
|
|
|
251
251
|
provenance:pypi:package-name
|
|
252
252
|
provenance:huggingface:owner/space-name
|
|
253
253
|
provenance:clawmarket:listing-id
|
|
254
|
+
provenance:domain:agent.example.com
|
|
255
|
+
provenance:domain:example.com/agents/research
|
|
254
256
|
```
|
|
255
257
|
|
|
258
|
+
### Domain identifiers
|
|
259
|
+
|
|
260
|
+
`provenance:domain:<hostname>` is for an agent that runs as a service and has no
|
|
261
|
+
public repository — which is most commercial agents. Control is proven exactly as
|
|
262
|
+
it is for a repository: the declaration is served from the location the
|
|
263
|
+
identifier names, and only someone with write access to that location could have
|
|
264
|
+
put it there.
|
|
265
|
+
|
|
266
|
+
The hostname must match exactly. A subdomain is a different party for this
|
|
267
|
+
purpose, and treating `agent.example.com` as covered by `example.com` would be
|
|
268
|
+
the whole attack.
|
|
269
|
+
|
|
270
|
+
Optional path segments allow several agents under one domain — those segments
|
|
271
|
+
must appear in the retrieval path.
|
|
272
|
+
|
|
273
|
+
Without this form, a hosted service serving its own declaration could never be
|
|
274
|
+
verified as its operator's: the signature would check out while the location
|
|
275
|
+
check reported `unchecked`, so no verifier could conclude the declaration was
|
|
276
|
+
genuinely theirs.
|
|
277
|
+
|
|
256
278
|
Add `provenance_id` to your PROVENANCE.yml to link your file to your
|
|
257
279
|
index entry and claim your agent profile.
|
|
258
280
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "provenance-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Provenance Protocol \u2014 open standard for AI agent identity \u2014 and its reference SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -58,6 +58,6 @@
|
|
|
58
58
|
"node": ">=14.0.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
|
-
"test": "node test/verify.test.mjs && node test/declaration-signature.test.mjs && node test/payload-separation.test.mjs"
|
|
61
|
+
"test": "node test/verify.test.mjs && node test/declaration-signature.test.mjs && node test/payload-separation.test.mjs && node test/location.test.mjs"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/src/cli.js
CHANGED
|
File without changes
|
package/src/verify.js
CHANGED
|
@@ -131,6 +131,26 @@ export function checkLocation(provenanceId, retrievedFrom) {
|
|
|
131
131
|
return 'unchecked';
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// provenance:domain:<hostname>[/<path>] — for an agent that runs as a service
|
|
135
|
+
// and has no public repository. Control is proven the same way as with a
|
|
136
|
+
// repo: whoever put the file there had write access to the location. Most
|
|
137
|
+
// commercial agents are this shape, so without it they could never be
|
|
138
|
+
// verified as their operator's.
|
|
139
|
+
if (id.platform === 'domain') {
|
|
140
|
+
const [declaredHost, ...declaredPath] = id.path.split('/').filter(Boolean);
|
|
141
|
+
if (!declaredHost) return 'unchecked';
|
|
142
|
+
// Exact host match. A subdomain is a different party as far as this is
|
|
143
|
+
// concerned, and treating it as the same would be the whole attack.
|
|
144
|
+
if (url.hostname.toLowerCase() !== declaredHost.toLowerCase()) return 'mismatch';
|
|
145
|
+
if (declaredPath.length === 0) return 'match';
|
|
146
|
+
const segs = url.pathname.split('/').filter(Boolean).map((x) => x.toLowerCase());
|
|
147
|
+
const want = declaredPath.map((x) => x.toLowerCase());
|
|
148
|
+
for (let i = 0; i + want.length <= segs.length; i++) {
|
|
149
|
+
if (want.every((part, j) => segs[i + j] === part)) return 'match';
|
|
150
|
+
}
|
|
151
|
+
return 'mismatch';
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
const platform = HOST_PLATFORMS.find(([host]) => host.test(url.hostname))?.[1];
|
|
135
155
|
if (!platform) return 'unchecked';
|
|
136
156
|
if (platform !== id.platform) return 'mismatch';
|