@didis/client 0.2.5 → 0.2.7
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/CHANGELOG.md +111 -79
- package/LICENSE +21 -21
- package/README.md +180 -160
- package/dist/guard.d.ts +16 -4
- package/dist/guard.js +54 -12
- package/dist/index.d.ts +32 -16
- package/dist/index.js +5 -1
- package/package.json +55 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,79 +1,111 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@didis/client` are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
4
|
-
|
|
5
|
-
## [Unreleased]
|
|
6
|
-
|
|
7
|
-
## [0.2.
|
|
8
|
-
|
|
9
|
-
###
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- `
|
|
35
|
-
|
|
36
|
-
## [0.2.
|
|
37
|
-
|
|
38
|
-
### Added
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
- `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
- `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- `
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
[0.2.
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@didis/client` are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and release numbering follows the project’s pre-1.0 policy. A third-component increment can contain breaking changes; read **Changed** and migration notes before upgrading. 0.2.6 is not a backward-compatible patch.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.2.7] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Correct first-party license attribution to DID.is; MIT permission and disclaimer terms are unchanged.
|
|
12
|
+
|
|
13
|
+
### Migration
|
|
14
|
+
|
|
15
|
+
- This release includes the execution-guard hardening and response types described under 0.2.6 below. **Upgrading from 0.2.5 is not a backward-compatible patch:** configure an application-owned trusted root for every guard; TypeScript requires `root` statically and Python enforces it at runtime with `GUARD_MISCONFIGURED`. Never derive the trusted root from submitted chains or model output. Expired approval, awaited-definition and immutable-deadline checks remain enforced; definition pins do not authorize callers or guarantee tool behavior.
|
|
16
|
+
- npm 0.2.6 was not published. 0.2.7 is the first npm release after 0.2.5 to deliver these changes; the 0.2.6 entry documents the earlier source release.
|
|
17
|
+
|
|
18
|
+
## [0.2.6] - 2026-11-03
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `createDidisGuard` / `withDidisGuard`: reject an ALLOW whose parseable `expires` is at or before execution time with the existing `TOOL_NOT_AUTHORIZED` code, including first use and disabled caching. Re-read pinned definitions after authorization and recheck each call's immutable deadline after async work and immediately before handler invocation; concurrent refreshes cannot extend another call's approval. Cached ALLOW is still bounded by both configured TTL and delegation expiry; failed/DENY/expired refreshes never fall back to stale approval (`GUARD_UNAVAILABLE` for transport/unexpected-answer errors). Missing/unparseable expiry remains optional and keeps the existing TTL-only behavior. Pin providers may now run twice per check. Each call retains the checked approval pin and synchronously compares it with the current pin immediately before execution; changes during asynchronous definition reads fail with `TOOL_DRIFT`. This is a pre-execution boundary, not per-stream reauthorization, resource binding, replay prevention, or an atomic remote side-effect guarantee.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Breaking in 0.2.6:** execution guards require a nonempty, trimmed application-owned trusted root DID. Missing/null/empty/whitespace roots raise `GUARD_MISCONFIGURED` at construction, before network or handler work. Migrate every guard and one-line wrapper by passing the root from trusted application configuration, not from a submitted chain, authorization response, or model output. Rootless advisory verification APIs remain available; signature-valid does not mean application-authorized. 0.2.5 accepts rootless guards; 0.2.6 rejects them. TypeScript requires `root` statically; Python retains its Optional annotation but enforces it at runtime.
|
|
27
|
+
|
|
28
|
+
- Correct delegation response fields to required nullable wire values; correct fast-verification explicit null fields. Wire-type corrections only; these corrections do not alter HTTP routing.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Add optional tools-only inventory completeness/evidence (missing means unknown); scoped requested-pin attestation preserves partial-result pin checks and rejects duplicate ambiguity. Missing completeness remains unknown, not complete.
|
|
33
|
+
|
|
34
|
+
- `diffObservations()` selects exact public history observations, including domain-only changes with identical document hashes. Semantic diff adds optional `fromObservation`/`toObservation` references. Legacy hash selectors remain available but ambiguous hashes return `INVALID_REQUEST` instead of silently selecting a different state. No change to private usage/billing routes.
|
|
35
|
+
|
|
36
|
+
## [0.2.5] - 2026-10-19
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Optional project API key: `new DidisClient({ apiKey })`. With a key, MCP inspection, A2A inspection, tool-call authorization and delegation-chain checks use the paid `/v1/customer/*` routes and count against your team's monthly allowance instead of the anonymous per-IP limit. Results are identical to the public routes. Each call sends one fresh `Idempotency-Key`; automatic `429`/`503` retries reuse it, so a request is never billed twice. The key is never sent to public routes, and `register: true` delegation checks always use the public route. Without a key nothing changes.
|
|
41
|
+
- `usage()` returns this month's team allowance (`CustomerUsage`: used, reserved, limit and remaining per pool). Requires the API key.
|
|
42
|
+
|
|
43
|
+
## [0.2.4] - 2026-10-19
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Automatic wait-and-retry for `429` (request budget used up) and `503` (temporarily overloaded) responses that carry `Retry-After`. By default the client retries up to 2 times and never waits longer than 10 seconds per attempt; a short random delay is added so clients refused together do not return together. Both statuses are refused before the request is processed, so retrying is safe for every call. Tune with `new DidisClient({ retry: { maxRetries, maxWaitMs } })` or turn off with `retry: false`. When retries run out, the last `DidisError` is thrown as before, with `problem.retryAfter`.
|
|
48
|
+
|
|
49
|
+
## [0.2.3] - 2026-10-18
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Requests now carry `user-agent: didis-client/<version>` (Node, Deno, Bun and other non-browser runtimes; browsers keep their own user agent). Only the package name and version are sent, so DID.is can count SDK use separately from other HTTP clients. New export `VERSION`.
|
|
54
|
+
|
|
55
|
+
- Type `LinkedAccountEvidence` and `Evidence.linkedAccounts`: each `alsoKnownAs` value with whether that website links back to the DID (`CONFIRMED`, `NOT_CONFIRMED`, `FAILED`, `UNSUPPORTED`, `UNREACHABLE`, `NOT_CHECKABLE`). A confirmation proves the same party controls the website origin and the DID; it does not prove who that party is.
|
|
56
|
+
|
|
57
|
+
- Policy rule `linkedAccounts` in `PolicyRules`: 1–4 https origins (for example `["https://shop.example"]`) that must be listed in the DID's `alsoKnownAs` and confirm the DID back. Use it to require "this DID belongs to our official website" in one policy call.
|
|
58
|
+
|
|
59
|
+
## [0.2.2] - 2026-10-18
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- `verifyCredential` option `requiredClaims` (`ClaimRequirement[]`): check conditions such as `{ path: "/age_over_18", equals: true }` without receiving claim values. The response field `requirements` (`RequirementsOutcome`) reports `met` and a per-path result; conditions are evaluated only for a `VALID` credential.
|
|
64
|
+
|
|
65
|
+
## [0.2.1] - 2026-10-14
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- Guard option `capability` (on `check`, `wrap` and `withDidisGuard`): the DID.is capability a tool needs when it differs from the tool name. MCP and OpenAI tool names cannot contain `:`, so use `{ tool: "weather_forecast", capability: "weather:forecast" }`. Pins stay keyed by the tool name; DID.is is asked about the capability; the ALLOW cache is keyed by capability. Without `capability`, behaviour is unchanged (the tool name is the capability).
|
|
70
|
+
- `onDecision` events and `DidisGuardError.detail` now include `capability`.
|
|
71
|
+
- Examples for the MCP TypeScript SDK (`mcp-client.mjs`), LangChain (`langchain-tool.mjs`) and the OpenAI Agents SDK (`openai-agents-tool.mjs`).
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- A capability containing `*`, empty segments or whitespace is refused with `GUARD_MISCONFIGURED` before any network call. Wildcards belong in delegations, not in tool calls.
|
|
76
|
+
|
|
77
|
+
## [0.2.0] - 2026-10-13
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- Fail-closed tool guard: `createDidisGuard`, `withDidisGuard` and `DidisGuardError`. A guarded handler runs only when the tool definition still matches its pin and DID.is `verifyTool` answers `ALLOW` for the configured agent (and root, if set). Drift, `DENY`, network errors, `429` and unexpected answers all throw, and the handler does not run. See [Guard agent tools](https://docs.did.is/build/guard-tools/).
|
|
82
|
+
- Tool definition pinning: `canonicalJson` (RFC 8785 JCS), `toolDefinitionSha256`, `mcpInventoryHash` and `pinToolDefinitions`. Hashes are identical to the `definitionSha256` and `inventoryHash` values DID.is reports.
|
|
83
|
+
- `attestMcpPins(client, endpoint, pins)` compares a public MCP server, as observed by DID.is, with your pins.
|
|
84
|
+
- `verifyCredential` options `expectedNonce` and `trustedIssuerCertificates` (SD-JWT VC key binding and caller-chosen X.509 anchors), with the `CredentialVerifyOptions` type.
|
|
85
|
+
- Response types `IssuerTrust` and `SdJwtEvidence`, and the optional `issuerTrust` and `sdJwt` fields on `CredentialVerification`.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- Node 18: WebCrypto is loaded from `node:crypto` when there is no global `crypto`. Before this, `verifyWebhookSignature` and `verifyCustomerMonitorSignature` failed on Node 18 with `crypto is not defined`.
|
|
90
|
+
|
|
91
|
+
## [0.1.0] - 2026-10-05
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
- First public release as `@didis/client`. Zero dependencies, ESM, Node 18+, Deno, Bun, browsers and edge runtimes.
|
|
96
|
+
- `DidisClient` for the DID.is public API (default base URL `https://did.is/api`): `health`, `resolve`, `resolveW3c`, `dereference`, `stream` (server-sent events), `history`, `diff`, `graph`, `verifyCredential`, `evaluatePolicy`, `inspectMcp`, `inspectA2a`, `verifyDelegation`, `authorize`, `verifyTool`, `fastVerifyDid` and `fastVerifyJws`.
|
|
97
|
+
- Self-hosted monitoring methods that need an admin token: `watch`, `watches`, `unwatch` and `events`.
|
|
98
|
+
- `DidisError` carrying the RFC 9457 problem body and `Retry-After`.
|
|
99
|
+
- Webhook helpers `verifyWebhookSignature` and `verifyCustomerMonitorSignature`.
|
|
100
|
+
|
|
101
|
+
[Unreleased]: https://github.com/did-is/didis-js/compare/v0.2.7...HEAD
|
|
102
|
+
[0.2.6]: https://github.com/did-is/didis-js/compare/v0.2.5...v0.2.6
|
|
103
|
+
[0.2.5]: https://github.com/did-is/didis-js/compare/v0.2.4...v0.2.5
|
|
104
|
+
[0.2.4]: https://github.com/did-is/didis-js/compare/v0.2.3...v0.2.4
|
|
105
|
+
[0.2.3]: https://github.com/did-is/didis-js/compare/v0.2.2...v0.2.3
|
|
106
|
+
[0.2.2]: https://github.com/did-is/didis-js/compare/v0.2.1...v0.2.2
|
|
107
|
+
[0.2.1]: https://github.com/did-is/didis-js/compare/v0.2.0...v0.2.1
|
|
108
|
+
[0.2.0]: https://github.com/did-is/didis-js/compare/v0.1.0...v0.2.0
|
|
109
|
+
[0.1.0]: https://github.com/did-is/didis-js/releases/tag/v0.1.0
|
|
110
|
+
|
|
111
|
+
[0.2.7]: https://github.com/did-is/didis-js/compare/v0.2.6...v0.2.7
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DID.is
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,160 +1,180 @@
|
|
|
1
|
-
# @didis/client
|
|
2
|
-
|
|
3
|
-
Official TypeScript/JavaScript SDK for the [DID.is](https://did.is) public API: DID resolution with evidence, W3C DID Resolution, verifiable credentials, explicit policies, and MCP/A2A agent trust. Works anywhere `fetch` exists: Node 18+, Deno, Bun, browsers and edge runtimes. Zero dependencies, fully typed, ESM.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
# @didis/client
|
|
2
|
+
|
|
3
|
+
Official TypeScript/JavaScript SDK for the [DID.is](https://did.is) public API: DID resolution with evidence, W3C DID Resolution, verifiable credentials, explicit policies, and MCP/A2A agent trust. Works anywhere `fetch` exists: Node 18+, Deno, Bun, browsers and edge runtimes. Zero dependencies, fully typed, ESM.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Current release: **0.2.7**. See [CHANGELOG.md](CHANGELOG.md) for root-required guard migration and corrected first-party license attribution.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @didis/client
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Runnable MCP examples
|
|
15
|
+
|
|
16
|
+
Start with [DID.is MCP examples](https://github.com/did-is/mcp-examples) for three separate jobs:
|
|
17
|
+
|
|
18
|
+
- [Connect your MCP client to DID.is](https://github.com/did-is/mcp-examples/tree/main/examples/00-connect-didis) — an opt-in round trip to the existing read-only server; connecting it does not protect your other tools.
|
|
19
|
+
- [Inspect a public MCP server](https://github.com/did-is/mcp-examples/tree/main/examples/01-inspect-public-server) — offline evidence first, then an explicit live inspection of a server you own or have permission to inspect.
|
|
20
|
+
- [Block calls after approved definitions change](https://github.com/did-is/mcp-examples/tree/main/examples/02-block-definition-drift) — an offline TypeScript execution boundary using the public SDK fingerprint helpers, not delegation authorization or the full SDK guard.
|
|
21
|
+
|
|
22
|
+
Default demos need no DID.is account, API key, or LLM key. The inspection example also has a [Python mirror](https://github.com/did-is/mcp-examples/tree/main/python/01-inspect-public-server). Host subscriptions and optional live calls are separate. [See the actual recorded run and the code boundary](https://github.com/did-is/mcp-examples/blob/main/docs/demo.md).
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { DidisClient, DidisError } from "@didis/client";
|
|
28
|
+
|
|
29
|
+
// Defaults to the public API at https://did.is/api.
|
|
30
|
+
// Self-hosting? new DidisClient({ baseUrl: "https://your-host/api" })
|
|
31
|
+
const didis = new DidisClient();
|
|
32
|
+
|
|
33
|
+
// Enriched resolution: verdict, evidence dimensions, graph, telemetry.
|
|
34
|
+
const r = await didis.resolve("did:web:identity.foundation");
|
|
35
|
+
console.log(r.verdict.headline);
|
|
36
|
+
for (const d of r.dimensions) console.log(d.label, d.state);
|
|
37
|
+
|
|
38
|
+
// DID Resolution v1 (W3C CR Draft HTTPS binding, unmodified).
|
|
39
|
+
const w3c = await didis.resolveW3c("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
|
|
40
|
+
|
|
41
|
+
// Live resolution stream (server-sent events).
|
|
42
|
+
for await (const ev of didis.stream("did:web:identity.foundation")) {
|
|
43
|
+
if (ev.event === "stage") console.log(ev.data.label, ev.data.status);
|
|
44
|
+
if (ev.event === "result") console.log(ev.data.verdict.outcome);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Credentials: VC DM 2.0 / 1.1 with Data Integrity (eddsa-jcs-2022, ecdsa-jcs-2019) or VC-JOSE/VC-JWT.
|
|
48
|
+
// RDFC suites, ecdsa-sd-2023 and bbs-2023 are reported as UNSUPPORTED, never as valid.
|
|
49
|
+
const credential = {
|
|
50
|
+
"@context": ["https://www.w3.org/ns/credentials/v2"],
|
|
51
|
+
type: ["VerifiableCredential"],
|
|
52
|
+
issuer: "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
53
|
+
validFrom: "2026-01-01T00:00:00Z",
|
|
54
|
+
credentialSubject: { id: "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLdmQR63zLFKfvUwi3" },
|
|
55
|
+
};
|
|
56
|
+
const vc = await didis.verifyCredential(credential);
|
|
57
|
+
console.log(vc.status, vc.headline);
|
|
58
|
+
|
|
59
|
+
// Policies are explicit rules, never scores.
|
|
60
|
+
const { evaluation } = await didis.evaluatePolicy("did:web:example.com", {
|
|
61
|
+
rules: { allowedMethods: ["web", "webvh"], domainBinding: "required", tlsMinDaysRemaining: 14 },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Agents.
|
|
65
|
+
const mcp = await didis.inspectMcp("https://mcp.example.com/mcp");
|
|
66
|
+
const card = await didis.inspectA2a("https://agent.example.com");
|
|
67
|
+
const chain: string[] = []; // delegation receipt JWS strings, root first
|
|
68
|
+
const { authorization } = await didis.authorize(chain, "payments.refund", { trustedRoots: ["did:web:example.com"] });
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await didis.resolve("did:web:does-not-exist.invalid");
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (e instanceof DidisError) console.log(e.status, e.problem.code, e.problem.detail);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Identifiers are percent-encoded exactly once by the client. Non-2xx responses throw `DidisError` with the RFC 9457 problem body (including the resolution `trace` where available).
|
|
78
|
+
|
|
79
|
+
**Rate limits.** When the public budget is used up the API answers `429` with `Retry-After`. The client waits that long and retries (up to 2 times, at most 10 s per wait), so a loop of calls slows down instead of failing. Tune with `new DidisClient({ retry: { maxRetries: 3, maxWaitMs: 5000 } })` or turn it off with `retry: false`.
|
|
80
|
+
|
|
81
|
+
**Project API key (optional).** Every check works without a key. A team that needs more than the anonymous per-IP limit can create a key in the DID.is console (Free plan included) and pass it: `new DidisClient({ apiKey: process.env.DIDIS_API_KEY })`. Then `inspectMcp`, `inspectA2a`, `authorize` and `verifyDelegation` use the `/v1/customer/*` routes and count against the team's monthly allowance; results are identical. Each call sends one fresh `Idempotency-Key` (retries reuse it, so nothing is billed twice). `await didis.usage()` shows the allowance left. Keep the key server-side; it is never sent to public routes.
|
|
82
|
+
|
|
83
|
+
## Guard agent tools
|
|
84
|
+
|
|
85
|
+
**0.2.7 migration from 0.2.5 (breaking; introduced in the 0.2.6 source release):** every execution guard, including standalone `check` calls and one-line wrappers, requires a nonempty, trimmed trusted root DID from application-owned configuration. Missing/null/empty/whitespace roots fail with `GUARD_MISCONFIGURED` at construction, before any network call or handler. TypeScript requires `root` statically; Python retains its Optional annotation but validates it at runtime. Do not obtain the trusted root from model output, a submitted delegation, or an ALLOW response. The configured root is sent to verification and compared with the returned root. Definition pins do not establish issuer authority. Rootless chain/tool verification remains advisory: signature-valid alone is not application authorization. 0.2.5 accepts rootless guards; 0.2.7 rejects them, so this is not a backward-compatible patch.
|
|
86
|
+
|
|
87
|
+
Wrap a tool handler so it runs only when (1) the tool definition the agent received still matches the one you approved and (2) DID.is says the agent's registered delegation grants the tool. Every failure throws `DidisGuardError` and the handler does not run (fail-closed).
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { DidisClient, createDidisGuard, pinToolDefinitions } from "@didis/client";
|
|
91
|
+
|
|
92
|
+
const didis = new DidisClient();
|
|
93
|
+
// Once, after reviewing the server's tools/list: store these pins in your config.
|
|
94
|
+
const pins = await pinToolDefinitions(reviewedTools);
|
|
95
|
+
|
|
96
|
+
const guard = createDidisGuard({
|
|
97
|
+
client: didis,
|
|
98
|
+
agent: process.env.AGENT_DID!, // from configuration, never from model output
|
|
99
|
+
root: process.env.ROOT_DID!, // trusted application configuration; required in 0.2.7
|
|
100
|
+
pins,
|
|
101
|
+
requirePins: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const createIssue = guard.wrap(
|
|
105
|
+
{
|
|
106
|
+
tool: "github_create_issue", // the name in tools/list (pin key)
|
|
107
|
+
capability: "github:create_issue", // what the delegation must grant
|
|
108
|
+
definition: () => currentTools.get("github_create_issue"),
|
|
109
|
+
},
|
|
110
|
+
async (args) => github.createIssue(args),
|
|
111
|
+
);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Tool name vs capability.** MCP and OpenAI tool names may only contain letters, digits, `_`, `-` and `.`, so a tool cannot be called `github:create_issue`. DID.is capabilities use `:` namespaces so that `github:*` can grant a whole group. Pass both: `tool` is used for the pin and error messages, `capability` is what DID.is is asked about. Without `capability`, the tool name is used. A capability must be exact (no `*`, no empty segments, no spaces); an invalid one throws `GUARD_MISCONFIGURED` before any network call. Since 0.2.1.
|
|
115
|
+
|
|
116
|
+
Guarding MCP clients, LangChain and the OpenAI Agents SDK: see [`examples/`](examples/) and [Guard agent tools](https://docs.did.is/build/guard-tools/#use-with-mcp-and-agent-frameworks).
|
|
117
|
+
|
|
118
|
+
- The definition check is local (RFC 8785 JCS + SHA-256, the same `definitionSha256` DID.is reports) and makes no network call.
|
|
119
|
+
- The delegation check calls `verifyTool`. An ALLOW is reused for 30 s by default (`allowCacheMs`), never past a parseable delegation expiry. Expired approvals are refused even on first use or with caching disabled. Approval deadlines and pins are rechecked after awaited work and immediately before execution; DENY and errors never fall back to stale approvals. Missing/unparseable expiry retains TTL-only behavior. This is a pre-execution boundary, not caller authentication, replay prevention, resource binding or per-stream reauthorization.
|
|
120
|
+
- Error codes: `TOOL_DRIFT`, `TOOL_DEFINITION_MISSING`, `TOOL_NOT_AUTHORIZED`, `GUARD_UNAVAILABLE`, `GUARD_MISCONFIGURED`.
|
|
121
|
+
- One tool only: `withDidisGuard(handler, { client, agent, root, tool, capability, pin, definition })`.
|
|
122
|
+
- `onDecision` and `DidisGuardError.detail` include both `tool` and `capability`.
|
|
123
|
+
- `attestMcpPins(client, endpoint, pins)` asks DID.is to inspect a public MCP server and compares its hashes with your pins. Run it at startup or deploy time, not per call: it is an outbound, rate-limited request, and servers that need login or run on a private network cannot be inspected.
|
|
124
|
+
|
|
125
|
+
## Webhooks
|
|
126
|
+
|
|
127
|
+
Console project monitor events use `verifyCustomerMonitorSignature`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { verifyCustomerMonitorSignature } from "@didis/client";
|
|
131
|
+
|
|
132
|
+
const ok = await verifyCustomerMonitorSignature(
|
|
133
|
+
secret,
|
|
134
|
+
rawBody,
|
|
135
|
+
req.headers["x-didis-signature"],
|
|
136
|
+
{ tenantId, projectId, eventId: req.headers["x-didis-event-id"] },
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For self-hosted admin daemon monitoring deliveries, use `verifyWebhookSignature`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { verifyWebhookSignature } from "@didis/client";
|
|
144
|
+
|
|
145
|
+
const ok = await verifyWebhookSignature(
|
|
146
|
+
secret,
|
|
147
|
+
req.headers["x-didis-timestamp"],
|
|
148
|
+
rawBody,
|
|
149
|
+
req.headers["x-didis-signature-256"],
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Surface
|
|
154
|
+
|
|
155
|
+
| Method | Route |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `health()` | `GET /health` |
|
|
158
|
+
| `resolve(did, { noCache })` | `GET /v1/resolve/{did}` |
|
|
159
|
+
| `resolveW3c(did)` | `GET /1.0/identifiers/{did}` |
|
|
160
|
+
| `dereference(didUrl)` | `GET /v1/dereference/{didUrl}` |
|
|
161
|
+
| `stream(did)` | `GET /v1/stream/{did}` |
|
|
162
|
+
| `history(did)` / `diff(did, from?, to?)` / `graph(did)` | `GET /v1/history`, `/v1/diff`, `/v1/graph` |
|
|
163
|
+
| `verifyCredential(vc)` | `POST /v1/credentials/verify` |
|
|
164
|
+
| `evaluatePolicy(did, policy, vc?)` | `POST /v1/policies/evaluate` |
|
|
165
|
+
| `inspectMcp(endpoint)` / `inspectA2a(url)` | `GET /v1/mcp/inspect`, `/v1/a2a/inspect` |
|
|
166
|
+
| `verifyDelegation(chain, opts)` / `authorize(chain, tool, opts)` / `verifyTool(agent, tool, root?)` | `/v1/agents/*` |
|
|
167
|
+
| `fastVerifyDid(did)` / `fastVerifyJws(jws, relationship?)` | `/v1/fast-verify/*` |
|
|
168
|
+
| `usage()` (with `apiKey`) | `GET /v1/customer/usage` |
|
|
169
|
+
| `watch` / `watches` / `unwatch` / `events` | `/v1/monitoring/*` (admin) |
|
|
170
|
+
|
|
171
|
+
Public API reference: <https://did.is/developers>. Monitoring routes (`watch`, `watches`, `unwatch`, `events`) need an admin token and are only available on self-hosted deployments; they are not exposed by the public API.
|
|
172
|
+
|
|
173
|
+
### Tools-only inventory completeness
|
|
174
|
+
|
|
175
|
+
`inventoryHash` fingerprints the observed tools, not an assumed entire catalog. Require `inventoryComplete === true` for whole-inventory acceptance. Optional missing completeness is unknown, not true. `inventoryEvidence` reports tools-only page/entry/drop counts, remaining pages and reasons; prompts/resources are not inspected. Pin attestation `ok` checks requested reviewed pins only and can succeed on partial inventory; it does not attest unpinned tools or establish safety. Duplicate ambiguous names cannot satisfy pins.
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/dist/guard.d.ts
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
* `definitionSha256`). Catches MCP "rug-pull" drift, including servers DID.is cannot reach.
|
|
8
8
|
* 2. Delegation (DID.is `verify-tool`): the configured agent DID holds a registered, VALID
|
|
9
9
|
* delegation whose leaf capabilities grant the tool's capability. ALLOW is cached briefly;
|
|
10
|
-
* DENY and errors are never cached.
|
|
10
|
+
* DENY and errors are never cached. A parseable `expires` is an exclusive deadline:
|
|
11
|
+
* stale ALLOW is refused with TOOL_NOT_AUTHORIZED, even with caching disabled.
|
|
12
|
+
* Absent/unparseable expiry keeps the legacy optional-field behavior (TTL-only cache).
|
|
13
|
+
*
|
|
14
|
+
* Pins are re-read after authorization awaits, and the per-call deadline is checked again
|
|
15
|
+
* immediately before invoking a wrapped handler. Pinned providers may run twice per check.
|
|
16
|
+
* This is a pre-execution check, not ongoing
|
|
17
|
+
* stream authorization or an atomic transaction with a remote tool's side effects.
|
|
11
18
|
*
|
|
12
19
|
* Tool name vs capability: MCP tool names may only use `A-Z a-z 0-9 _ - .` (SEP-986), while
|
|
13
20
|
* DID.is capabilities are namespaced with `:` (e.g. `weather:forecast`). Pass both when they
|
|
@@ -45,8 +52,8 @@ export interface DidisGuardOptions {
|
|
|
45
52
|
client: DidisClient;
|
|
46
53
|
/** The agent DID from your runtime configuration. Never take it from model output. */
|
|
47
54
|
agent: string;
|
|
48
|
-
/**
|
|
49
|
-
root
|
|
55
|
+
/** Application-owned trusted root DID. Required; never derive it from a chain or model output. */
|
|
56
|
+
root: string;
|
|
50
57
|
/** Approved `{ toolName: definitionSha256 }`. A pinned tool must be given its definition. */
|
|
51
58
|
pins?: Record<string, string>;
|
|
52
59
|
/** Refuse tools that have no pin. Default `false`. */
|
|
@@ -71,7 +78,7 @@ export interface GuardCallOptions {
|
|
|
71
78
|
* Must be an exact capability (no `*`): wildcards belong in delegations, not in tool calls.
|
|
72
79
|
*/
|
|
73
80
|
capability?: string;
|
|
74
|
-
/** The definition the agent received, or a function returning the current one. */
|
|
81
|
+
/** The definition the agent received, or a function returning the current one (may be read twice). */
|
|
75
82
|
definition?: Definition;
|
|
76
83
|
}
|
|
77
84
|
export interface DidisGuard {
|
|
@@ -91,6 +98,10 @@ export declare function withDidisGuard<A extends unknown[], R>(handler: (...args
|
|
|
91
98
|
pin?: string;
|
|
92
99
|
}): (...args: A) => Promise<Awaited<R>>;
|
|
93
100
|
export interface McpAttestation {
|
|
101
|
+
/** Requested pins only, never an all-tools or safety assertion. */
|
|
102
|
+
inventoryComplete?: boolean;
|
|
103
|
+
inventoryEvidence?: McpInspection["inventoryEvidence"];
|
|
104
|
+
scope: "requested-pins";
|
|
94
105
|
ok: boolean;
|
|
95
106
|
inventoryHash: string;
|
|
96
107
|
/** Pinned tools whose DID.is-observed hash differs, or that DID.is did not see. */
|
|
@@ -106,6 +117,7 @@ export interface McpAttestation {
|
|
|
106
117
|
/**
|
|
107
118
|
* Optional startup check: asks DID.is to inspect a public MCP endpoint and compares the observed
|
|
108
119
|
* `definitionSha256` values with your pins. Use at deploy/startup, never per tool call.
|
|
120
|
+
* Matching requested pins can succeed on partial inventory; extras and tool safety are not accepted.
|
|
109
121
|
* `AUTH_REQUIRED` or any non-INSPECTED status returns `ok: false`.
|
|
110
122
|
*/
|
|
111
123
|
export declare function attestMcpPins(client: DidisClient, endpoint: string, pins: Record<string, string>): Promise<McpAttestation>;
|
package/dist/guard.js
CHANGED
|
@@ -79,8 +79,8 @@ export function createDidisGuard(options) {
|
|
|
79
79
|
throw new DidisGuardError("GUARD_MISCONFIGURED", "", "a DidisClient is required");
|
|
80
80
|
if (typeof agent !== "string" || !DID.test(agent))
|
|
81
81
|
throw new DidisGuardError("GUARD_MISCONFIGURED", "", "agent must be a DID");
|
|
82
|
-
if (root !==
|
|
83
|
-
throw new DidisGuardError("GUARD_MISCONFIGURED", "", "
|
|
82
|
+
if (typeof root !== "string" || root !== root.trim() || !DID.test(root))
|
|
83
|
+
throw new DidisGuardError("GUARD_MISCONFIGURED", "", "an application-owned trusted root DID is required");
|
|
84
84
|
if (!Number.isFinite(allowCacheMs) || allowCacheMs < 0)
|
|
85
85
|
throw new DidisGuardError("GUARD_MISCONFIGURED", "", "allowCacheMs must be >= 0");
|
|
86
86
|
for (const [name, pin] of Object.entries(pins)) {
|
|
@@ -128,13 +128,20 @@ export function createDidisGuard(options) {
|
|
|
128
128
|
fail("TOOL_DRIFT", tool, capability, `definition name does not match '${tool}'`, { expected: pin, actual });
|
|
129
129
|
if (actual !== pin)
|
|
130
130
|
fail("TOOL_DRIFT", tool, capability, `definition of '${tool}' changed since it was approved`, { expected: pin, actual });
|
|
131
|
+
assertPinCurrent(tool, capability, pin);
|
|
132
|
+
return pin;
|
|
133
|
+
}
|
|
134
|
+
function assertPinCurrent(tool, capability, checkedPin) {
|
|
135
|
+
if (pinFor(tool) !== checkedPin) {
|
|
136
|
+
fail("TOOL_DRIFT", tool, capability, `approved pin for '${tool}' changed before execution`);
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
async function checkDelegation(tool, capability) {
|
|
133
140
|
const now = Date.now();
|
|
134
141
|
const until = allowed.get(capability);
|
|
135
142
|
if (until !== undefined && until > now) {
|
|
136
143
|
report(tool, capability, true, "cached ALLOW", true);
|
|
137
|
-
return;
|
|
144
|
+
return until;
|
|
138
145
|
}
|
|
139
146
|
allowed.delete(capability);
|
|
140
147
|
let auth;
|
|
@@ -152,17 +159,27 @@ export function createDidisGuard(options) {
|
|
|
152
159
|
}
|
|
153
160
|
fail("GUARD_UNAVAILABLE", tool, capability, "DID.is returned an unexpected authorization answer");
|
|
154
161
|
}
|
|
155
|
-
if (
|
|
162
|
+
if (auth.root !== root)
|
|
156
163
|
fail("TOOL_NOT_AUTHORIZED", tool, capability, "delegation root does not match the configured root");
|
|
164
|
+
// Expiry is optional in the existing wire contract. Never turn malformed/missing expiry
|
|
165
|
+
// into a new mandatory field; when parseable, enforce it on first use as well as reuse.
|
|
166
|
+
const parsed = typeof auth.expires === "string" ? Date.parse(auth.expires) : NaN;
|
|
167
|
+
const deadline = Number.isFinite(parsed) ? parsed : Infinity;
|
|
168
|
+
assertFresh(tool, capability, deadline);
|
|
157
169
|
if (allowCacheMs > 0) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
if (until2 > now)
|
|
170
|
+
const until2 = Math.min(now + allowCacheMs, deadline);
|
|
171
|
+
if (until2 > Date.now())
|
|
161
172
|
allowed.set(capability, until2);
|
|
162
173
|
}
|
|
163
174
|
report(tool, capability, true, auth.reason, false);
|
|
175
|
+
return deadline;
|
|
176
|
+
}
|
|
177
|
+
function assertFresh(tool, capability, deadline) {
|
|
178
|
+
if (deadline <= Date.now()) {
|
|
179
|
+
fail("TOOL_NOT_AUTHORIZED", tool, capability, "authorization expired before execution");
|
|
180
|
+
}
|
|
164
181
|
}
|
|
165
|
-
async function
|
|
182
|
+
async function prepare({ tool, capability, definition }) {
|
|
166
183
|
if (typeof tool !== "string" || !tool.trim() || tool !== tool.trim())
|
|
167
184
|
fail("GUARD_MISCONFIGURED", String(tool), String(capability ?? tool), "tool must be a non-empty, trimmed name");
|
|
168
185
|
const cap = capability === undefined ? tool : capability;
|
|
@@ -170,15 +187,27 @@ export function createDidisGuard(options) {
|
|
|
170
187
|
fail("GUARD_MISCONFIGURED", tool, String(cap), "capability must be exact: ':'-separated non-empty segments, no spaces, no '*'");
|
|
171
188
|
}
|
|
172
189
|
await checkDefinition(tool, cap, definition);
|
|
173
|
-
|
|
190
|
+
// Keep this call's deadline, not a later cache entry written by a concurrent call.
|
|
191
|
+
const deadline = await checkDelegation(tool, cap);
|
|
192
|
+
const pin = await checkDefinition(tool, cap, definition);
|
|
193
|
+
assertFresh(tool, cap, deadline);
|
|
194
|
+
assertPinCurrent(tool, cap, pin);
|
|
195
|
+
return { tool, capability: cap, deadline, pin };
|
|
174
196
|
}
|
|
175
197
|
return {
|
|
176
|
-
check
|
|
198
|
+
async check(call) {
|
|
199
|
+
const ticket = await prepare(call);
|
|
200
|
+
assertFresh(ticket.tool, ticket.capability, ticket.deadline);
|
|
201
|
+
assertPinCurrent(ticket.tool, ticket.capability, ticket.pin);
|
|
202
|
+
},
|
|
177
203
|
wrap(call, handler) {
|
|
178
204
|
if (typeof handler !== "function")
|
|
179
205
|
throw new DidisGuardError("GUARD_MISCONFIGURED", call?.tool ?? "", "handler must be a function");
|
|
180
206
|
return async (...args) => {
|
|
181
|
-
await
|
|
207
|
+
const ticket = await prepare(call);
|
|
208
|
+
// No await between the final deadline/pin checks and calling the handler.
|
|
209
|
+
assertFresh(ticket.tool, ticket.capability, ticket.deadline);
|
|
210
|
+
assertPinCurrent(ticket.tool, ticket.capability, ticket.pin);
|
|
182
211
|
return await handler(...args);
|
|
183
212
|
};
|
|
184
213
|
},
|
|
@@ -199,11 +228,21 @@ export function withDidisGuard(handler, options) {
|
|
|
199
228
|
/**
|
|
200
229
|
* Optional startup check: asks DID.is to inspect a public MCP endpoint and compares the observed
|
|
201
230
|
* `definitionSha256` values with your pins. Use at deploy/startup, never per tool call.
|
|
231
|
+
* Matching requested pins can succeed on partial inventory; extras and tool safety are not accepted.
|
|
202
232
|
* `AUTH_REQUIRED` or any non-INSPECTED status returns `ok: false`.
|
|
203
233
|
*/
|
|
204
234
|
export async function attestMcpPins(client, endpoint, pins) {
|
|
205
235
|
const inspection = await client.inspectMcp(endpoint);
|
|
206
|
-
const seen = new Map(
|
|
236
|
+
const seen = new Map();
|
|
237
|
+
const ambiguous = new Set();
|
|
238
|
+
for (const t of inspection.tools) {
|
|
239
|
+
if (seen.has(t.name) || ambiguous.has(t.name)) {
|
|
240
|
+
seen.delete(t.name);
|
|
241
|
+
ambiguous.add(t.name);
|
|
242
|
+
}
|
|
243
|
+
else
|
|
244
|
+
seen.set(t.name, t.definitionSha256);
|
|
245
|
+
}
|
|
207
246
|
const mismatched = Object.entries(pins)
|
|
208
247
|
.filter(([tool, expected]) => seen.get(tool) !== expected)
|
|
209
248
|
.map(([tool, expected]) => ({ tool, expected, observed: seen.get(tool) }));
|
|
@@ -211,6 +250,9 @@ export async function attestMcpPins(client, endpoint, pins) {
|
|
|
211
250
|
return {
|
|
212
251
|
ok: inspection.status === "INSPECTED" && mismatched.length === 0,
|
|
213
252
|
inventoryHash: inspection.inventoryHash,
|
|
253
|
+
inventoryComplete: inspection.inventoryComplete,
|
|
254
|
+
inventoryEvidence: inspection.inventoryEvidence,
|
|
255
|
+
scope: "requested-pins",
|
|
214
256
|
mismatched,
|
|
215
257
|
unpinned,
|
|
216
258
|
status: inspection.status,
|
package/dist/index.d.ts
CHANGED
|
@@ -372,6 +372,15 @@ export interface CustomerUsage {
|
|
|
372
372
|
expensiveLimit: number;
|
|
373
373
|
expensiveRemaining: number;
|
|
374
374
|
}
|
|
375
|
+
/** Bounded tools/list evidence only; prompts and resources are not inspected. */
|
|
376
|
+
export interface InventoryEvidence {
|
|
377
|
+
scope: string;
|
|
378
|
+
pagesFetched: number;
|
|
379
|
+
entriesSeen: number;
|
|
380
|
+
entriesDropped: number;
|
|
381
|
+
hasMore: boolean;
|
|
382
|
+
reasons: string[];
|
|
383
|
+
}
|
|
375
384
|
export interface McpInspection {
|
|
376
385
|
endpoint: string;
|
|
377
386
|
status: "INSPECTED" | "AUTH_REQUIRED" | string;
|
|
@@ -383,6 +392,9 @@ export interface McpInspection {
|
|
|
383
392
|
instructions?: string;
|
|
384
393
|
tools: ToolEvidence[];
|
|
385
394
|
inventoryHash: string;
|
|
395
|
+
/** Missing means unknown, not complete. Hash covers observed tools only. */
|
|
396
|
+
inventoryComplete?: boolean;
|
|
397
|
+
inventoryEvidence?: InventoryEvidence;
|
|
386
398
|
jcsProfile?: string;
|
|
387
399
|
classCounts: Record<string, number>;
|
|
388
400
|
drift: {
|
|
@@ -463,11 +475,11 @@ export interface DelegationLink {
|
|
|
463
475
|
issuer: string;
|
|
464
476
|
audience: string;
|
|
465
477
|
capabilities: string[];
|
|
466
|
-
notBefore
|
|
467
|
-
expires
|
|
468
|
-
jti
|
|
469
|
-
kid
|
|
470
|
-
alg
|
|
478
|
+
notBefore: string | null;
|
|
479
|
+
expires: string | null;
|
|
480
|
+
jti: string | null;
|
|
481
|
+
kid: string | null;
|
|
482
|
+
alg: string | null;
|
|
471
483
|
digest: string;
|
|
472
484
|
signatureValid: boolean;
|
|
473
485
|
status: string;
|
|
@@ -478,11 +490,11 @@ export interface DelegationChainResult {
|
|
|
478
490
|
status: string;
|
|
479
491
|
headline: string;
|
|
480
492
|
format: string;
|
|
481
|
-
root
|
|
482
|
-
leaf
|
|
493
|
+
root: string | null;
|
|
494
|
+
leaf: string | null;
|
|
483
495
|
effectiveCapabilities: string[];
|
|
484
|
-
notAfter
|
|
485
|
-
trustedRoot
|
|
496
|
+
notAfter: string | null;
|
|
497
|
+
trustedRoot: boolean | null;
|
|
486
498
|
links: DelegationLink[];
|
|
487
499
|
checks: Check[];
|
|
488
500
|
limitations: string[];
|
|
@@ -531,6 +543,8 @@ export interface ObservationRecord {
|
|
|
531
543
|
}
|
|
532
544
|
export interface SemanticDiff {
|
|
533
545
|
did: string;
|
|
546
|
+
fromObservation?: string;
|
|
547
|
+
toObservation?: string;
|
|
534
548
|
fromHash: string;
|
|
535
549
|
toHash: string;
|
|
536
550
|
fromObservedAt: string;
|
|
@@ -629,13 +643,13 @@ export interface FastVerifyDidResult {
|
|
|
629
643
|
}[];
|
|
630
644
|
keys: {
|
|
631
645
|
id: string;
|
|
632
|
-
curve
|
|
646
|
+
curve: string | null;
|
|
633
647
|
status: KeyEvidence["status"];
|
|
634
648
|
relationships: string[];
|
|
635
|
-
jwkThumbprint
|
|
649
|
+
jwkThumbprint: string | null;
|
|
636
650
|
}[];
|
|
637
651
|
domainBinding: string | null;
|
|
638
|
-
documentSha256
|
|
652
|
+
documentSha256: string | null;
|
|
639
653
|
observedAt: string;
|
|
640
654
|
cached: boolean;
|
|
641
655
|
}
|
|
@@ -643,9 +657,9 @@ export interface FastVerifyJwsResult {
|
|
|
643
657
|
status: "VALID" | "INVALID" | "INDETERMINATE" | "UNSUPPORTED" | "MALFORMED";
|
|
644
658
|
valid: boolean;
|
|
645
659
|
headline: string;
|
|
646
|
-
alg?: string;
|
|
647
|
-
kid?: string;
|
|
648
|
-
typ?: string;
|
|
660
|
+
alg?: string | null;
|
|
661
|
+
kid?: string | null;
|
|
662
|
+
typ?: string | null;
|
|
649
663
|
/** Decoded JSON payload, or base64url text when the payload is not JSON. */
|
|
650
664
|
payload?: unknown;
|
|
651
665
|
signingInputSha256?: string;
|
|
@@ -707,7 +721,7 @@ export declare function retryAfterMs(value: string | null | undefined, now?: num
|
|
|
707
721
|
/** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
|
|
708
722
|
export declare const DEFAULT_BASE_URL = "https://did.is/api";
|
|
709
723
|
/** Package version, kept equal to package.json (checked by the test suite). */
|
|
710
|
-
export declare const VERSION = "0.2.
|
|
724
|
+
export declare const VERSION = "0.2.7";
|
|
711
725
|
export declare class DidisClient {
|
|
712
726
|
private readonly baseUrl;
|
|
713
727
|
private readonly adminToken?;
|
|
@@ -747,6 +761,8 @@ export declare class DidisClient {
|
|
|
747
761
|
history(did: string): Promise<HistoryResponse>;
|
|
748
762
|
/** Semantic diff; defaults to the latest two distinct observations. */
|
|
749
763
|
diff(did: string, from?: string, to?: string): Promise<SemanticDiff>;
|
|
764
|
+
/** Exact public history rows, including domain-only changes with identical document hashes. */
|
|
765
|
+
diffObservations(did: string, fromObservation: string, toObservation: string): Promise<SemanticDiff>;
|
|
750
766
|
graph(did: string): Promise<GraphResponse>;
|
|
751
767
|
/** Audience is checked only when an expected recipient is supplied. Pass raw JSON as a string to preserve member identity. */
|
|
752
768
|
verifyCredential(credential: unknown, options?: CredentialVerifyOptions): Promise<CredentialVerification>;
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
26
26
|
/** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
|
|
27
27
|
export const DEFAULT_BASE_URL = "https://did.is/api";
|
|
28
28
|
/** Package version, kept equal to package.json (checked by the test suite). */
|
|
29
|
-
export const VERSION = "0.2.
|
|
29
|
+
export const VERSION = "0.2.7";
|
|
30
30
|
/**
|
|
31
31
|
* Identifies the SDK to the API so DID.is can count SDK use apart from other HTTP clients.
|
|
32
32
|
* Only the product name and version are sent. Browsers are left alone: there the browser
|
|
@@ -190,6 +190,10 @@ export class DidisClient {
|
|
|
190
190
|
const q = from && to ? `?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}` : "";
|
|
191
191
|
return this.request("GET", `/v1/diff/${enc(did)}${q}`);
|
|
192
192
|
}
|
|
193
|
+
/** Exact public history rows, including domain-only changes with identical document hashes. */
|
|
194
|
+
diffObservations(did, fromObservation, toObservation) {
|
|
195
|
+
return this.request("GET", `/v1/diff/${enc(did)}?fromObservation=${encodeURIComponent(fromObservation)}&toObservation=${encodeURIComponent(toObservation)}`);
|
|
196
|
+
}
|
|
193
197
|
graph(did) {
|
|
194
198
|
return this.request("GET", `/v1/graph/${enc(did)}`);
|
|
195
199
|
}
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@didis/client",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Official TypeScript/JavaScript SDK for the DID.is public API: DID resolution with evidence, credentials, policies and agent trust",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"default": "./dist/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./package.json": "./package.json"
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"README.md",
|
|
18
|
-
"LICENSE",
|
|
19
|
-
"CHANGELOG.md"
|
|
20
|
-
],
|
|
21
|
-
"sideEffects": false,
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=18"
|
|
24
|
-
},
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsc",
|
|
27
|
-
"test": "npm run build && node --test test/*.test.js",
|
|
28
|
-
"prepublishOnly": "npm run build"
|
|
29
|
-
},
|
|
30
|
-
"keywords": [
|
|
31
|
-
"did",
|
|
32
|
-
"w3c",
|
|
33
|
-
"verifiable-credentials",
|
|
34
|
-
"identity",
|
|
35
|
-
"mcp",
|
|
36
|
-
"a2a",
|
|
37
|
-
"resolver"
|
|
38
|
-
],
|
|
39
|
-
"author": "DID.is",
|
|
40
|
-
"license": "MIT",
|
|
41
|
-
"homepage": "https://did.is/developers",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/did-is/didis-js.git"
|
|
45
|
-
},
|
|
46
|
-
"bugs": {
|
|
47
|
-
"url": "https://github.com/did-is/didis-js/issues"
|
|
48
|
-
},
|
|
49
|
-
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"typescript": "^5.7.0"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@didis/client",
|
|
3
|
+
"version": "0.2.7",
|
|
4
|
+
"description": "Official TypeScript/JavaScript SDK for the DID.is public API: DID resolution with evidence, credentials, policies and agent trust",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"did",
|
|
32
|
+
"w3c",
|
|
33
|
+
"verifiable-credentials",
|
|
34
|
+
"identity",
|
|
35
|
+
"mcp",
|
|
36
|
+
"a2a",
|
|
37
|
+
"resolver"
|
|
38
|
+
],
|
|
39
|
+
"author": "DID.is",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"homepage": "https://did.is/developers",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/did-is/didis-js.git"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/did-is/didis-js/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"typescript": "^5.7.0"
|
|
54
|
+
}
|
|
55
|
+
}
|