@kya-os/mcp 1.14.2 → 1.16.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/CONFORMANCE.md +31 -4
  3. package/README.md +30 -160
  4. package/SPEC.md +53 -14
  5. package/dist/delegation/holder-binding.d.ts.map +1 -1
  6. package/dist/delegation/holder-binding.js +8 -1
  7. package/dist/delegation/holder-binding.js.map +1 -1
  8. package/dist/delegation/scope-matcher.d.ts +8 -3
  9. package/dist/delegation/scope-matcher.d.ts.map +1 -1
  10. package/dist/delegation/scope-matcher.js +15 -1
  11. package/dist/delegation/scope-matcher.js.map +1 -1
  12. package/dist/delegation/vc-verification-checks.d.ts.map +1 -1
  13. package/dist/delegation/vc-verification-checks.js +10 -2
  14. package/dist/delegation/vc-verification-checks.js.map +1 -1
  15. package/dist/delegation/vc-verifier.types.d.ts +4 -0
  16. package/dist/delegation/vc-verifier.types.d.ts.map +1 -1
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/middleware/with-kya-os.config-types.d.ts +22 -1
  22. package/dist/middleware/with-kya-os.config-types.d.ts.map +1 -1
  23. package/dist/middleware/with-kya-os.d.ts.map +1 -1
  24. package/dist/middleware/with-kya-os.deps.d.ts +7 -0
  25. package/dist/middleware/with-kya-os.deps.d.ts.map +1 -1
  26. package/dist/middleware/with-kya-os.js +6 -1
  27. package/dist/middleware/with-kya-os.js.map +1 -1
  28. package/dist/middleware/with-kya-os.session.d.ts.map +1 -1
  29. package/dist/middleware/with-kya-os.session.js +28 -6
  30. package/dist/middleware/with-kya-os.session.js.map +1 -1
  31. package/dist/proof/generator.d.ts +38 -5
  32. package/dist/proof/generator.d.ts.map +1 -1
  33. package/dist/proof/generator.js +95 -25
  34. package/dist/proof/generator.js.map +1 -1
  35. package/dist/proof/index.d.ts +1 -1
  36. package/dist/proof/index.d.ts.map +1 -1
  37. package/dist/proof/index.js +1 -1
  38. package/dist/proof/index.js.map +1 -1
  39. package/dist/proof/verifier.d.ts +6 -1
  40. package/dist/proof/verifier.d.ts.map +1 -1
  41. package/dist/proof/verifier.js +20 -27
  42. package/dist/proof/verifier.js.map +1 -1
  43. package/dist/types/protocol.d.ts +28 -1
  44. package/dist/types/protocol.d.ts.map +1 -1
  45. package/dist/types/protocol.js +31 -0
  46. package/dist/types/protocol.js.map +1 -1
  47. package/package.json +1 -1
  48. package/schemas/delegation-credential.json +1 -1
  49. package/schemas/detached-proof.json +6 -1
  50. package/schemas/well-known-mcpi.json +3 -4
package/CHANGELOG.md CHANGED
@@ -7,6 +7,101 @@ Versioning: https://semver.org/spec/v2.0.0.html
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.16.0] - 2026-09-19
11
+
12
+ ### Added
13
+
14
+ - **`path-prefix` CRISP scope matcher.** `prefix` is character-level and right
15
+ for scope identifiers, but applied to a resource path it lets a grant for
16
+ `notes` authorize `notesx/secret.md`. `path-prefix` matches the path itself
17
+ or anything beneath it as a `/`-separated path, with an optional trailing `/`
18
+ or `/*`; an empty base grants nothing. Added to `matchScope`, the `CrispScope`
19
+ type, `schemas/delegation-credential.json`, and SPEC.md §6.3/§6.4.1.
20
+
21
+ ### Fixed
22
+
23
+ - **`DelegationVCVerificationResult.stage` names the failing check.** A failed
24
+ verdict from `verifyDelegationCredential` / `verifyDelegationJwt` reported
25
+ `stage: "complete"` once both the signature and status checks had run, leaving
26
+ only the `checks` flags to say which one failed. It now reports `"signature"` or
27
+ `"status"`; `"complete"` accompanies `valid: true` only.
28
+ - The revocation reason names the status method on the credential
29
+ (`Credential revoked via <credentialStatus.type> (…)`) instead of always
30
+ saying `StatusList2021`.
31
+ - `schemas/well-known-mcpi.json`: `version` accepts the three-part protocol
32
+ version the reference implementation advertises (`1.0.0`); `endpoints.handshake`
33
+ is no longer required — it belongs to the legacy 1.x session profile
34
+ (SPEC-MCP-EXTENSION.md §4).
35
+
36
+ ### Documentation
37
+
38
+ - SPEC.md §10: the discovery example now validates against the schema
39
+ (`serverDid`, not `did`; schema capability names; `statusListCredential`;
40
+ camelCase advisory fields) and says when `endpoints.handshake` applies.
41
+ - CONFORMANCE.md: names the three ladders (Core L1–L3, Card L1–L3, AAP-0–4) and
42
+ asks claims to say which; the Entity Card section uses the terminal `_meta`
43
+ key names (`org.kya-os/response-proof`, `org.kya-os/request-proof` carrying
44
+ `prf: "org.kya-os/proof.v1"`).
45
+
46
+ ## [1.15.0] - 2026-08-25
47
+
48
+ ### Added
49
+
50
+ - **Response-proof `envelope` profile** (`org.kya-os/response-proof.envelope`).
51
+ The `body` profile binds `responseHash` over the response body only, leaving
52
+ result members such as `structuredContent`, `isError` and `resultType`
53
+ unauthenticated - an in-path intermediary could rewrite them under a still
54
+ valid proof. The envelope profile covers the ENTIRE result object with the
55
+ top-level `_meta` member removed, mirroring the request side's
56
+ `{method, params minus _meta}` rule, so every present and future result
57
+ member is authenticated while `_meta` stays intermediary-mutable (which is
58
+ what keeps attach-after-sign sound). The profile is discriminated by a `prf`
59
+ claim COVERED by the JWS signature, so a proof cannot be silently downgraded
60
+ to body semantics, and `validateDetachedProof` rejects unknown `prf` values
61
+ fail-closed. Verification always derives the profile from the proof's own
62
+ claim, never from configuration, so one verifier accepts both.
63
+ - **Conformance vector-set immutability.** `conformance/SUITE-MANIFEST.json`
64
+ pins the vector set by hash, `scripts/suite-hash.mjs` computes and checks it,
65
+ and CI enforces it as its own line - a published vector set can no longer be
66
+ edited in place without the check failing. Suite is at 1.1.0.
67
+ - **`DEPRECATIONS.md`** - an internal ledger of deferred breaking changes.
68
+
69
+ ### Fixed
70
+
71
+ - **Sessionless request proofs now bind.** `generateRequestProof` defaulted
72
+ `meta.sessionId` to `''` when the caller omitted it, but `sessionId` is a
73
+ required non-empty string in the proof meta schema, so the proof failed
74
+ validation as `INVALID_PROOF_STRUCTURE` and `assertHolderBinding` returned
75
+ `unbound` for the LEGITIMATE holder - indistinguishable from the thief the
76
+ gate exists to catch. `sessionId` is documented as optional and a request
77
+ proof routinely precedes any handshake on a stateless core, so this was the
78
+ normal path rather than an edge case. It failed closed (nothing was wrongly
79
+ admitted), but phase-1 holder binding was unusable without a `sessionId` the
80
+ signature never said was mandatory. A per-proof id is now minted when none is
81
+ supplied; verification, the schema and the conformance vectors are unchanged.
82
+ - The `prf` claim is derived only from the profile option, and the proof schema
83
+ id is bumped accordingly.
84
+ - A challenge proof that the degraded-audit `isError` flip would have broken is
85
+ now stripped rather than emitted.
86
+ - `suite-hash` hashes the committed blob bytes rather than the working tree, so
87
+ a dirty checkout cannot produce a passing hash.
88
+
89
+ ### Changed
90
+
91
+ - **Response-proof profiles are named by mechanism, not sequence**:
92
+ `org.kya-os/response-proof.v2` -> `.envelope`, `.v1` -> `.body`, and
93
+ `RESPONSE_PROOF_PROFILE_V2`/`_V1` -> `_ENVELOPE`/`_BODY`. The sequence
94
+ framing was wrong on its own terms - original proofs carry no profile
95
+ identifier at all, so the first wire identifier would have debuted as `.v2`
96
+ naming a v1 that never existed. **No published API changes**: these
97
+ identifiers and constants are new in this release and never shipped in
98
+ 1.14.2, so nothing downstream can be pinned to the old names. The
99
+ request-proof profile keeps its shipped `org.kya-os/proof.v1` name.
100
+ - Conformance vectors use semantic names and a more robust signature-tamper
101
+ case.
102
+ - Dependency bumps across the minor-and-patch group.
103
+ - README restructures `did:cheqd` into a hierarchical Integrations section.
104
+
10
105
  ## [1.14.2] - 2026-08-19
11
106
 
12
107
  ### Fixed
package/CONFORMANCE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Compliance levels for KYA-OS implementations**
4
4
 
5
- Version: 1.0.0
5
+ Version: 1.1.0
6
6
  Status: Stable
7
7
 
8
8
  ---
@@ -11,6 +11,17 @@ Status: Stable
11
11
 
12
12
  This document defines three compliance levels for KYA-OS implementations. Each level builds on the previous, with increasing capability requirements. Implementations MUST pass all tests for a given level to claim conformance at that level.
13
13
 
14
+ Three independent ladders live in this document, and two of them reuse the
15
+ names L1–L3. A conformance claim MUST name its ladder:
16
+
17
+ | Ladder | Levels | Measures |
18
+ |--------|--------|----------|
19
+ | Core (this section onward) | Core L1 / L2 / L3 | Crypto primitives, the legacy session-bound proof, W3C delegation credentials |
20
+ | Entity Card (§ Entity Card Conformance) | Card L1 / L2 / L3 | The typed card and the stateless `org.kya-os/proof.v1` request proof |
21
+ | Audit Assurance Profile (§ Audit Assurance Profile conformance) | AAP-0 … AAP-4 | Auditability mechanics an implementation can truthfully advertise |
22
+
23
+ A bare "L2" is ambiguous; write "Core L2", "Card L2", or "AAP-2".
24
+
14
25
  ---
15
26
 
16
27
  ## Level 1 — Core Crypto
@@ -411,7 +422,7 @@ so it is reproducible against any implementation without re-signing.
411
422
 
412
423
  | File | Category | Positive | Negative |
413
424
  |------|----------|----------|----------|
414
- | `vectors/signed-proof.json` | Detached proof verification | valid signature + in-window ts | tampered signature, tampered meta, wrong key, timestamp skew exceeded |
425
+ | `vectors/signed-proof.json` | Detached proof verification | valid signature + in-window ts; envelope content binding (profile `org.kya-os/response-proof.envelope`) | tampered signature, tampered meta, wrong key, timestamp skew exceeded; tampered `structuredContent` under envelope coverage, `prf` stripped (downgrade), unknown profile |
415
426
  | `vectors/delegation-chain.json` | Delegation chain verification | single-hop, two-hop attenuated | broken issuer↔subject linkage, scope widening, tampered signature, audience mismatch |
416
427
  | `vectors/status-list.json` | StatusList2021 revocation | active (bit unset) | revoked (bit set) |
417
428
  | `vectors/did-key-resolution.json` | did:key resolution | valid Ed25519 | malformed multibase, wrong method |
@@ -430,6 +441,11 @@ protocol, and `negotiation` exercises the MCP extension admission gate
430
441
  A `fail` vector passes the suite only when the implementation correctly **rejects**
431
442
  it. The runner exits non-zero on any mismatch.
432
443
 
444
+ **On the envelope-profile vectors (`signed-proof/envelope-*`, `prf-stripped-downgrade`, `unknown-profile`; suite ≥ 1.1.0).**
445
+ These exercise the `org.kya-os/response-proof.envelope` profile (SPEC.md §7.3): `responseHash` covers the full result envelope with the top-level `_meta` member removed, discriminated by a signature-covered `prf` claim.
446
+ Their inputs carry an OPTIONAL `expected` member (`{ request, response }`) — the content the verifier "received" — which a conformant verifier re-hashes under the proof's own `prf` profile and reconciles against the bound hashes.
447
+ Vectors without `expected` remain pure signature/skew checks.
448
+
433
449
  **On `signed-proof/tampered-meta`.**
434
450
  The proof's `meta` block mirrors the claims signed inside the JWS, but it is a convenience for consumers that read the proof without decoding the token, not an authoritative copy.
435
451
  This vector leaves the JWS byte-identical to `valid-basic` and alters only the mirrored `meta.requestHash`, so the JWS signature still verifies.
@@ -457,6 +473,16 @@ pnpm run conformance:generate
457
473
 
458
474
  Re-running mints fresh keys but preserves every positive/negative relationship by
459
475
  construction.
476
+ Because fresh keys change the committed bytes, regenerated output MUST NOT be committed over an existing `suiteVersion` (see [Suite versioning and immutability](#suite-versioning-and-immutability)).
477
+
478
+ ### Suite versioning and immutability
479
+
480
+ The committed vector set is immutable at a given `suiteVersion`: the vector bytes may only change together with a `suiteVersion` bump.
481
+ `conformance/SUITE-MANIFEST.json` pins the current set (`suiteVersion`, `vectorSetHash`, `vectorCount`, per-file hashes) and is the anchor CI verifies against.
482
+ The hash recipe: SHA-256 each vector file's raw committed bytes, sort the `[filename, hex]` pairs by filename, canonicalize the array with RFC 8785 (JCS), SHA-256 that, prefix with `sha256:`.
483
+ CI enforces the invariant twice: a vitest guard (`conformance/__tests__/suite-immutability.test.ts`) and a dedicated step running `node conformance/suite-hash.mjs --check`.
484
+ To change vectors intentionally, bump `suiteVersion` (including the in-file `version` fields), regenerate the manifest with `node conformance/suite-hash.mjs --json`, and keep `pinnedAt` current.
485
+ The SIGNED public suite manifest published by the Conformance Attestation Program is a separate artifact built on this committed one.
460
486
 
461
487
  ### Running the harness against YOUR implementation
462
488
 
@@ -521,8 +547,9 @@ deployment satisfies its operational durability or independence claims.
521
547
  The Entity Card is a **distinct, newer layer** on top of the Level 1–3 ladder above:
522
548
  a typed, DID-anchored card plus a stateless, sender-constrained per-request proof.
523
549
  It is orthogonal to the legacy session-bound proof — the two coexist, each under
524
- its OWN distinct `_meta` key (`_meta['org.kya-os/proof']` for the legacy
525
- session-bound proof, `_meta['org.kya-os/proof.v1']` for the stateless card proof),
550
+ its OWN distinct `_meta` key (`_meta["org.kya-os/response-proof"]` for the legacy
551
+ session-bound proof, historically `org.kya-os/proof`; `_meta["org.kya-os/request-proof"]`
552
+ for the stateless card proof, whose object carries `prf: "org.kya-os/proof.v1"`),
526
553
  and each verifier reads its own key and ignores the other. Its
527
554
  conformance vectors live in the SAME harness under two categories, wired to the two
528
555
  adapter methods `verifyCardProof` and `verifyEntityCard`.
package/README.md CHANGED
@@ -265,182 +265,52 @@ flows, and sessions are shared across instances and survive restarts.
265
265
 
266
266
  ---
267
267
 
268
- ## Optional did:cheqd and DID-Linked Resources
268
+ ## Integrations
269
269
 
270
- `did:cheqd` support is additive and opt-in. Existing `did:key` and `did:web`
271
- flows remain unchanged, and operators can keep `did:web` as their canonical
272
- identifier while linking to a cheqd DID over time.
270
+ KYA-OS reaches the outside world two ways: shipped **modules** for specific
271
+ systems and standards, and typed **adapter seams** with in-memory defaults you
272
+ swap out. Adding one is "drop a folder under `src/integrations/`" or "implement
273
+ this interface" — contributions welcome.
273
274
 
274
- ### Resolver configuration
275
+ ### Modules
276
+
277
+ | Module | Adds | Docs |
278
+ | --- | --- | --- |
279
+ | **cheqd** (on-chain) | `did:cheqd` resolution, StatusList2021 revocation, DID-Linked Resources + registrar | [src/integrations/cheqd](./src/integrations/cheqd/README.md) |
280
+ | **OAuth / OIDC** | PKCE + protected-resource metadata; generic-OIDC reference adapter | [`@kya-os/mcp/authz`](./src/authz/) · [example](./examples/authz-inspector/) |
281
+ | **Verifiable audit** | RFC 9162 ledger; local or Checkpoint recorders, on-chain anchoring | [AUDITABILITY.md](./AUDITABILITY.md) |
282
+
283
+ Enabling a module is a small config change. cheqd, for example, is a resolver
284
+ you register under `didResolvers`:
275
285
 
276
286
  ```typescript
277
- import { RuntimeFetchProvider, withKyaOs, NodeCryptoProvider } from '@kya-os/mcp';
278
287
  import { cheqdResolver } from '@kya-os/mcp/cheqd';
279
288
 
280
- const crypto = new NodeCryptoProvider();
281
- const fetchProvider = new RuntimeFetchProvider();
282
- const didResolvers = {
283
- cheqd: cheqdResolver({ resolverUrl: 'https://resolver.cheqd.net' }),
284
- };
285
-
286
289
  await withKyaOs(server, {
287
290
  crypto,
288
291
  delegation: {
289
- fetchProvider,
290
- didResolvers,
291
- },
292
- });
293
- ```
294
-
295
- `did:cheqd` is resolved only when a resolver for the `cheqd` DID method is
296
- explicitly supplied. The core middleware and fetch provider use the generic
297
- `didResolvers` registry; cheqd-specific URL/cache/header options live in the
298
- `cheqdResolver()` factory from `@kya-os/mcp/cheqd`.
299
- `RuntimeFetchProvider` accepts the same registry when it is used directly by a
300
- standalone verifier or operator script.
301
- Unsupported methods, malformed DIDs, fetch failures, invalid JSON, malformed DID
302
- Documents, and DID id mismatches fail closed by returning `null`. The resolver
303
- accepts both raw DID Documents and Universal Resolver-style DID Resolution
304
- Results (`{ didDocument: ... }`).
305
-
306
- ### Registrar writes
307
-
308
- Registrar writes are explicit operator/admin actions. The package exposes
309
- `CheqdDidRegistrarClient` for cheqd DID Registrar `/create`, `/update`, and
310
- `/{did}/create-resource` flows, using cheqd's client-managed-secret pattern:
311
- the registrar returns a serialized payload, your signer signs it, and the
312
- signature is submitted back. Private keys are not sent to the registrar.
313
-
314
- ```typescript
315
- import {
316
- CheqdDidRegistrarClient,
317
- createLocalEd25519CheqdRegistrarSigner,
318
- } from '@kya-os/mcp/cheqd';
319
- import {
320
- NodeCryptoProvider,
321
- } from '@kya-os/mcp';
322
-
323
- const crypto = new NodeCryptoProvider();
324
- const registrar = new CheqdDidRegistrarClient({
325
- registrarUrl: 'https://did-registrar-staging.cheqd.net/1.0',
326
- fetchProvider,
327
- // Optional static or async auth headers for private registrar deployments.
328
- // headers: async () => ({ Authorization: `Bearer ${token}` }),
329
- });
330
-
331
- const signer = createLocalEd25519CheqdRegistrarSigner({
332
- cryptoProvider: crypto,
333
- privateKey: process.env.CHEQD_DID_PRIVATE_KEY_BASE64!,
334
- verificationMethodId: 'did:cheqd:testnet:...#key-1',
335
- signatureEncoding: 'base64url',
336
- });
337
- ```
338
-
339
- For mainnet, run or contract against your own fee-payer registrar deployment and
340
- provide the signer hook from your KMS/HSM boundary. The local Ed25519 helper is
341
- for simple controlled deployments and tests; it still signs locally and sends
342
- only signatures to the registrar.
343
-
344
- Runtime proof generation does not perform registrar writes. Create/update/DLR
345
- publishing should be triggered by an explicit operator workflow, deployment
346
- step, or admin tool.
347
-
348
- ### DID linkage
349
-
350
- For `did:web` <-> `did:cheqd` binding, publish reciprocal `alsoKnownAs` values
351
- and verify both DID Documents with `verifyDidLinkage()`. `buildDidWebDocument`
352
- can include the `did:cheqd` reference on the `did:web` side, while
353
- `updateCheqdAlsoKnownAs()` updates the cheqd side through the registrar.
354
-
355
- ```typescript
356
- import { verifyDidLinkage } from '@kya-os/mcp';
357
- import { updateCheqdAlsoKnownAs } from '@kya-os/mcp/cheqd';
358
-
359
- await updateCheqdAlsoKnownAs({
360
- didWeb: 'did:web:agent.example.com',
361
- didCheqd: 'did:cheqd:testnet:...',
362
- resolver: cheqdResolver,
363
- registrar,
364
- signer,
365
- verificationMethodId: 'did:cheqd:testnet:...#key-1',
366
- });
367
-
368
- const linkage = verifyDidLinkage({
369
- primaryDid: 'did:web:agent.example.com',
370
- secondaryDid: 'did:cheqd:testnet:...',
371
- primaryDidDocument: didWebDocument,
372
- secondaryDidDocument: didCheqdDocument,
373
- });
374
- ```
375
-
376
- ### DID-Linked Resource helpers
377
-
378
- DID-Linked Resource helpers are intended for durable manifests only. Supported
379
- artifact types are:
380
-
381
- - `CapabilityManifest`
382
- - `ConformanceManifest`
383
- - `AccessHashManifest`
384
- - `TrustConfigManifest`
385
-
386
- `prepareCheqdDlrResource()` validates the artifact, canonicalizes its `content`
387
- with JSON Canonicalization Scheme, computes or validates a `sha256:<64 hex>`
388
- content hash, and returns a registrar resource body. Updates are modeled as new
389
- resource versions under the same resource `name` and `type`; prior resources are
390
- not overwritten. Do not write high-volume tool calls, raw operational logs, or
391
- normal runtime proof events to cheqd; keep those in your normal audit/hash
392
- stores.
393
-
394
- ```typescript
395
- import { prepareCheqdDlrResource } from '@kya-os/mcp/cheqd';
396
-
397
- const prepared = await prepareCheqdDlrResource({
398
- type: 'TrustConfigManifest',
399
- subjectDid: 'did:cheqd:testnet:...',
400
- name: 'agent-trust-config',
401
- resourceType: 'TrustConfigManifest',
402
- version: '2026-06-01',
403
- content: {
404
- acceptedDidMethods: ['did:web', 'did:key', 'did:cheqd'],
405
- requiredLinkage: { type: 'alsoKnownAs', bidirectional: true },
292
+ didResolvers: { cheqd: cheqdResolver({ resolverUrl: 'https://resolver.cheqd.net' }) },
406
293
  },
407
- }, crypto);
408
-
409
- await registrar.createResource({
410
- did: 'did:cheqd:testnet:...',
411
- resource: prepared.resource,
412
- signer,
413
- verificationMethodId: 'did:cheqd:testnet:...#key-1',
414
294
  });
415
295
  ```
416
296
 
417
- See [examples/cheqd-dlr](./examples/cheqd-dlr/) for a complete operator flow.
418
-
419
- ### Live cheqd registrar E2E tests
420
-
421
- Live registrar coverage is opt-in because it performs real writes to cheqd
422
- testnet. The default endpoint is the cheqd-published testnet staging registrar;
423
- override it only with another testnet registrar. The live test creates a testnet
424
- DID, adds a realistic `did:web` alias via `alsoKnownAs`, then publishes one
425
- resource for each supported KYA DLR artifact type.
426
-
427
- ```powershell
428
- $env:KYA_OS_CHEQD_E2E = '1'
429
- npm run test:e2e:cheqd:testnet
430
- Remove-Item Env:\KYA_OS_CHEQD_E2E
431
- ```
297
+ Its full reference (registrar writes, `did:web` <-> `did:cheqd` linkage,
298
+ DID-Linked Resource helpers, live testnet E2E) lives in
299
+ [src/integrations/cheqd/README.md](./src/integrations/cheqd/README.md); see
300
+ [examples/cheqd-dlr](./examples/cheqd-dlr/) for a complete operator flow.
432
301
 
433
- Optional environment variables:
302
+ ### Adapter seams (bring your own)
434
303
 
435
- | Variable | Default |
436
- | --- | --- |
437
- | `KYA_OS_CHEQD_TESTNET_REGISTRAR_URL` | `https://did-registrar-staging.cheqd.net/1.0` |
438
- | `KYA_OS_CHEQD_TESTNET_RESOLVER_URL` | `https://resolver.cheqd.net` |
439
- | `KYA_OS_CHEQD_E2E_TIMEOUT_MS` | `180000` |
304
+ Every external dependency is a typed interface with an in-memory default, so you
305
+ can swap in your own backend without touching the middleware.
440
306
 
441
- Out of scope for this package: replacing `did:web` as the canonical identity,
442
- writing runtime proof events on-chain, KYC/KYB issuance, reputation VC snapshot
443
- publication, status-list backend hosting, and external registry changes.
307
+ | Seam | Default | Swap in |
308
+ | --- | --- | --- |
309
+ | DID resolution — `DIDResolver` | `did:key`, `did:web` | `did:cheqd`, custom |
310
+ | Revocation — `StatusListResolver` | none | cheqd StatusList2021 |
311
+ | State — `GrantStore`, `SessionStore`, `NonceCacheProvider`, `PendingFlowStore` | in-memory | Redis, DynamoDB, KV, Durable Objects, DB ([multi-instance](#multi-instance-deployments)) |
312
+ | Crypto — `CryptoProvider` | Node, WebCrypto | your KMS / HSM |
313
+ | Policy — `PolicyEngine` | default | custom |
444
314
 
445
315
  ## Links
446
316
 
package/SPEC.md CHANGED
@@ -548,13 +548,26 @@ interface DelegationConstraints {
548
548
  };
549
549
  scopes: Array<{
550
550
  resource: string;
551
- matcher: 'exact' | 'prefix' | 'regex';
551
+ matcher: 'exact' | 'prefix' | 'path-prefix' | 'regex';
552
552
  constraints?: Record<string, unknown>;
553
553
  }>;
554
554
  };
555
555
  }
556
556
  ```
557
557
 
558
+ #### CRISP scope matchers
559
+
560
+ | `matcher` | Semantics | Intended value |
561
+ |-----------|-----------|----------------|
562
+ | `exact` | Strict string equality. | Any. Preferred for sensitive resources (§11.4). |
563
+ | `prefix` | Character-level: the value starts with the pattern; one trailing `*` is optional sugar. An empty base grants nothing. | Scope **identifiers** (`repo:` matches `repo:write`). |
564
+ | `path-prefix` | Path-level: the value equals the pattern or lies beneath it as a `/`-separated path. `notes` matches `notes` and `notes/2026/plan.md`, never `notesx/secret.md`. A trailing `/` or `/*` is optional sugar; an empty base grants nothing. | Resource **paths** (files, folders, URL paths). |
565
+ | `regex` | Anchored full-string match; invalid patterns never match. Verifiers bound pattern and value length and reject nested-quantifier patterns. | Any; issuers accepting patterns from untrusted parties SHOULD prefer the other three. |
566
+
567
+ Verifiers MUST treat `prefix` and `path-prefix` as distinct: applying `prefix`
568
+ to a resource path lets a grant for `notes` authorize `notesx/secret.md`, which
569
+ is a boundary escape, not a match.
570
+
558
571
  ### 6.4 Delegation Graph
559
572
 
560
573
  Delegations form a directed acyclic graph (DAG):
@@ -582,7 +595,7 @@ Delegations form a directed acyclic graph (DAG):
582
595
 
583
596
  A `DelegationCredential` may authorize multiple resources, either via a list
584
597
  of explicit scope strings (`constraints.scopes[]`) or via pattern matchers
585
- (`constraints.crisp.scopes[].matcher`: `'exact' | 'prefix' | 'regex'`). When
598
+ (`constraints.crisp.scopes[].matcher`: `'exact' | 'prefix' | 'path-prefix' | 'regex'`). When
586
599
  the credential is used to invoke a specific tool or resource, the invocation
587
600
  MUST designate the specific scope being exercised, and verifiers MUST confirm
588
601
  that the designated scope is a member of (or matches a pattern in) the
@@ -878,6 +891,11 @@ interface ProofMeta {
878
891
  outcome?: 'allowed' | 'denied' | 'step_up_required' | 'needs_authorization';
879
892
  // Authorization outcome; ABSENT ⇒ implicitly 'allowed' (success)
880
893
  reason?: string; // Human-readable reason on a non-'allowed' outcome
894
+ prf?: 'org.kya-os/response-proof.envelope';
895
+ // Response-proof profile discriminator, COVERED by the
896
+ // JWS signature. ABSENT ⇒ the body profile (body-only
897
+ // responseHash, the original wire shape). Verifiers
898
+ // MUST reject any other value (fail-closed; §7.3).
881
899
  }
882
900
  ```
883
901
 
@@ -895,7 +913,16 @@ Request canonicalization includes:
895
913
  }
896
914
  ```
897
915
 
898
- Response canonicalization is the `data` field only (excludes `_meta`).
916
+ Response canonicalization is selected by the proof's response-proof profile, named by the signature-covered `prf` claim (§7.2):
917
+
918
+ - **body** (`prf` ABSENT — the original profile): the `data` field only (excludes `_meta`).
919
+ Under MCP carriage, `data` is the result's `content` array; result members outside it are NOT covered.
920
+ - **envelope** (`prf: "org.kya-os/response-proof.envelope"`): the ENTIRE result object with the top-level `_meta` member removed, mirroring the request side's `{method, params minus _meta}` rule.
921
+ This authenticates result members such as `structuredContent`, `isError`, and `resultType` that the body profile leaves uncovered, while `_meta` stays intermediary-mutable — which is what lets the proof itself be attached there after signing.
922
+
923
+ A verifier derives the profile from the received proof's own `prf` claim, never from configuration, and MUST reject an unrecognized `prf` outright.
924
+ Because `prf` is covered by the signature, stripping it from an envelope-profile proof breaks verification — a downgrade to body-only semantics is a hard failure, not a silent fallback.
925
+ Producers default to the body profile (wire compatibility with existing verifiers); the envelope profile is an explicit opt-in (`responseProofProfile` in the middleware configuration).
899
926
 
900
927
  ### 7.4 JWS Compact Serialization
901
928
 
@@ -927,6 +954,9 @@ BASE64URL(header) . BASE64URL(payload) . BASE64URL(signature)
927
954
  }
928
955
  ```
929
956
 
957
+ An envelope-profile proof (§7.3) additionally carries its `prf` claim in this payload — profile selection is signed, never advisory.
958
+ Every optional `ProofMeta` member present on the proof (`scopeId`, `delegationRef`, `clientDid`, `outcome`, `reason`, `prf`) appears in the payload; the mirrored `meta` block and the decoded payload MUST reconcile exactly.
959
+
930
960
  **Denial** and **step-up** proofs have no response body, so `responseHash` is
931
961
  omitted and `outcome` (plus an optional `reason`) is included instead. Keys
932
962
  remain RFC 8785-sorted:
@@ -1040,8 +1070,9 @@ verifier treats `_meta` keys that are *not* KYA-OS's proof key:
1040
1070
  `_meta` keys to the application layer rather than discarding them.
1041
1071
 
1042
1072
  Under no policy does a verifier include any non-proof `_meta` key in a hash or
1043
- signature computation. The signature covers the `data`/response body only; it
1044
- never covers `_meta` (§7.3).
1073
+ signature computation. The signature covers the profile-selected response
1074
+ material only — the `data` body under the body profile, the result envelope minus
1075
+ its top-level `_meta` under the envelope profile — and never covers `_meta` itself (§7.3).
1045
1076
 
1046
1077
  > **Note.** The proof object's shape is normatively defined by
1047
1078
  > `schemas/detached-proof.json` (`$schema: draft/2020-12`). The *placement* key
@@ -1275,27 +1306,35 @@ sufficient on its own: the resulting DelegationCredential is still verified per
1275
1306
 
1276
1307
  ## 10. Well-Known Endpoint
1277
1308
 
1278
- KYA-OS servers SHOULD expose `/.well-known/mcp`:
1309
+ KYA-OS servers SHOULD expose `/.well-known/mcp`. The normative shape is
1310
+ `schemas/well-known-mcpi.json`; the example below validates against it:
1279
1311
 
1280
1312
  ```json
1281
1313
  {
1282
- "did": "did:web:mcp-server.example.com",
1283
1314
  "version": "1.0.0",
1315
+ "serverDid": "did:web:mcp-server.example.com",
1284
1316
  "capabilities": {
1317
+ "proofs": true,
1285
1318
  "delegation": true,
1286
- "proof": true,
1287
- "revocation": true
1319
+ "statusList": true,
1320
+ "crisp": true
1288
1321
  },
1289
- "supported_did_methods": ["did:key", "did:web", "did:cheqd"],
1290
- "proof_algorithms": ["EdDSA"],
1291
1322
  "clockSkewSeconds": 120,
1292
1323
  "endpoints": {
1293
- "handshake": "/_kya-os/handshake",
1294
- "status_list": "/.well-known/status/1"
1295
- }
1324
+ "tools": "/mcp"
1325
+ },
1326
+ "statusListCredential": "https://mcp-server.example.com/.well-known/status/1",
1327
+ "supportedDidMethods": ["did:key", "did:web", "did:cheqd"],
1328
+ "proofAlgorithms": ["EdDSA"]
1296
1329
  }
1297
1330
  ```
1298
1331
 
1332
+ `serverDid` is the server's own DID, the audience a client binds proofs to.
1333
+ `supportedDidMethods` and `proofAlgorithms` are advisory additional properties
1334
+ (the schema permits them; it does not require them). Servers running the legacy
1335
+ 1.x session profile additionally list `endpoints.handshake`
1336
+ (SPEC-MCP-EXTENSION.md §4); servers on the stateless card-proof profile omit it.
1337
+
1299
1338
  Servers SHOULD list `did:cheqd` only when cheqd resolution has been explicitly
1300
1339
  configured for that deployment.
1301
1340
 
@@ -1 +1 @@
1
- {"version":3,"file":"holder-binding.d.ts","sourceRoot":"","sources":["../../src/delegation/holder-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,WAAW,CAMb;AAED,MAAM,WAAW,yBAAyB;IACxC,uEAAuE;IACvE,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sEAAsE;IACtE,MAAM,EAAE,cAAc,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAgBxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,KAAK,EAAE,aAAa,CAAC;IACrB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,OAAO,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACrC,uFAAuF;IACvF,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,oBAAoB,CAAC;IACxC,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CA4E9B"}
1
+ {"version":3,"file":"holder-binding.d.ts","sourceRoot":"","sources":["../../src/delegation/holder-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,WAAW,CAMb;AAED,MAAM,WAAW,yBAAyB;IACxC,uEAAuE;IACvE,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sEAAsE;IACtE,MAAM,EAAE,cAAc,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAuBxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,KAAK,EAAE,aAAa,CAAC;IACrB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,OAAO,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACrC,uFAAuF;IACvF,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,oBAAoB,CAAC;IACxC,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CA4E9B"}
@@ -69,8 +69,15 @@ export async function generateRequestProof(input) {
69
69
  const request = toHolderBindingRequest(toolName, args);
70
70
  const nonce = base64urlEncodeFromBytes(await crypto.randomBytes(16));
71
71
  const now = Math.floor(Date.now() / 1000);
72
+ // `sessionId` is optional on the way in (a request proof can precede any
73
+ // handshake), but `meta.sessionId` is a REQUIRED non-empty string in the
74
+ // proof schema. Defaulting to '' therefore minted a proof that could never
75
+ // verify: `assertHolderBinding` rejected the LEGITIMATE holder as
76
+ // INVALID_PROOF_STRUCTURE, indistinguishably from a thief. Mint a per-proof
77
+ // id instead, so the sessionless case (the stateless MCP core) works.
78
+ const boundSessionId = sessionId ?? `req-${base64urlEncodeFromBytes(await crypto.randomBytes(9))}`;
72
79
  return generator.generateProof(request, undefined, {
73
- sessionId: sessionId ?? '',
80
+ sessionId: boundSessionId,
74
81
  audience,
75
82
  nonce,
76
83
  timestamp: now,
@@ -1 +1 @@
1
- {"version":3,"file":"holder-binding.js","sourceRoot":"","sources":["../../src/delegation/holder-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"holder-binding.js","sourceRoot":"","sources":["../../src/delegation/holder-binding.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,17 @@
1
1
  import type { DelegationCredential } from '../types/protocol.js';
2
- export type ScopeMatcher = 'exact' | 'prefix' | 'regex';
2
+ export type ScopeMatcher = 'exact' | 'prefix' | 'path-prefix' | 'regex';
3
3
  /**
4
4
  * Match a single requested scope/resource value against a pattern by matcher kind.
5
5
  *
6
6
  * - `exact` : strict string equality.
7
7
  * - `prefix` : value starts with the pattern (a single trailing `*` is optional sugar).
8
8
  * An empty base ('' or lone '*') matches nothing — it will NOT grant
9
- * universal scope.
9
+ * universal scope. Character-level: `notes` matches `notesx/secret.md`.
10
+ * Use it for scope ids, where the value is an identifier, not a path.
11
+ * - `path-prefix` : the value IS the pattern, or lies under it as a `/`-separated
12
+ * path (`notes` matches `notes` and `notes/a/b.md`, never
13
+ * `notesx/secret.md`). Trailing `/` or `/*` on the pattern is optional
14
+ * sugar; an empty base matches nothing. Use it for resource paths.
10
15
  * - `regex` : anchored full-string match; never throws (invalid patterns → false).
11
16
  *
12
17
  * SECURITY: the regex pattern is supplied by the credential ISSUER. JS `RegExp`
@@ -20,7 +25,7 @@ export declare function matchScope(pattern: string, matcher: ScopeMatcher, value
20
25
  export interface ScopeSatisfaction {
21
26
  satisfied: boolean;
22
27
  /**
23
- * True when satisfaction came via a non-exact (prefix|regex) CrispScope matcher.
28
+ * True when satisfaction came via a non-exact (prefix|path-prefix|regex) CrispScope matcher.
24
29
  * Callers should surface a warning — non-exact matchers widen effective authority.
25
30
  */
26
31
  usedNonExactMatcher: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"scope-matcher.d.ts","sourceRoot":"","sources":["../../src/delegation/scope-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,sBAAsB,CAAC;AAY7E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAsBzF;AAkBD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,oBAAoB,GAC/B,iBAAiB,CAUnB"}
1
+ {"version":3,"file":"scope-matcher.d.ts","sourceRoot":"","sources":["../../src/delegation/scope-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,sBAAsB,CAAC;AAY7E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA6BzF;AAkBD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,oBAAoB,GAC/B,iBAAiB,CAUnB"}
@@ -12,7 +12,12 @@ const NESTED_QUANTIFIER = /\([^()]*[+*?{][^()]*\)\s*[+*{]/;
12
12
  * - `exact` : strict string equality.
13
13
  * - `prefix` : value starts with the pattern (a single trailing `*` is optional sugar).
14
14
  * An empty base ('' or lone '*') matches nothing — it will NOT grant
15
- * universal scope.
15
+ * universal scope. Character-level: `notes` matches `notesx/secret.md`.
16
+ * Use it for scope ids, where the value is an identifier, not a path.
17
+ * - `path-prefix` : the value IS the pattern, or lies under it as a `/`-separated
18
+ * path (`notes` matches `notes` and `notes/a/b.md`, never
19
+ * `notesx/secret.md`). Trailing `/` or `/*` on the pattern is optional
20
+ * sugar; an empty base matches nothing. Use it for resource paths.
16
21
  * - `regex` : anchored full-string match; never throws (invalid patterns → false).
17
22
  *
18
23
  * SECURITY: the regex pattern is supplied by the credential ISSUER. JS `RegExp`
@@ -33,6 +38,15 @@ export function matchScope(pattern, matcher, value) {
33
38
  return false;
34
39
  return value.startsWith(base);
35
40
  }
41
+ case 'path-prefix': {
42
+ let base = pattern.endsWith('*') ? pattern.slice(0, -1) : pattern;
43
+ while (base.endsWith('/'))
44
+ base = base.slice(0, -1);
45
+ // Refuse to grant universal scope via an empty/`*`-only prefix.
46
+ if (base.length === 0)
47
+ return false;
48
+ return value === base || value.startsWith(`${base}/`);
49
+ }
36
50
  case 'regex': {
37
51
  if (pattern.length > MAX_REGEX_LEN || value.length > MAX_VALUE_LEN)
38
52
  return false;