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 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.4.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';