@proof-holdings/delegation-verifier 0.1.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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Verifying the delegation a card publishes.
3
+ *
4
+ * The carrier layout itself lives in `carriers.ts` (kept crypto-free so it can be read without
5
+ * pulling in the verification machinery); this module is the part that decides.
6
+ */
7
+ import type { VerificationResult, VerifyOptions } from './types.js';
8
+ export { DELEGATION_META_KEY, DELEGATION_EXTENSION_URI, REGISTRY_PUBLISHER_PROVIDED_KEY, extractDelegationPublications, parseDelegationPointer, pointerRecordName, POINTER_VERSION, POINTER_RECORD_PREFIX, } from './carriers.js';
9
+ export type { FoundPublication, PointerParseResult, PointerRejection } from './carriers.js';
10
+ /**
11
+ * Verify the delegation an MCP server card or A2A agent card publishes.
12
+ *
13
+ * `options.delegate` still means what it means everywhere else: the artifact identity YOU
14
+ * resolved — the npm package you are about to install, the endpoint you are about to call —
15
+ * NOT a field read out of this card. A card that describes itself is not evidence about
16
+ * itself.
17
+ */
18
+ export declare function verifyPublishedDelegation(card: unknown, options: VerifyOptions): Promise<VerificationResult>;
19
+ //# sourceMappingURL=publication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publication.d.ts","sourceRoot":"","sources":["../src/publication.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGpE,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,+BAA+B,EAC/B,6BAA6B,EAC7B,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE5F;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAmB7B"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Verifying the delegation a card publishes.
3
+ *
4
+ * The carrier layout itself lives in `carriers.ts` (kept crypto-free so it can be read without
5
+ * pulling in the verification machinery); this module is the part that decides.
6
+ */
7
+ import { collectPublishedTokens } from './carriers.js';
8
+ import { classifyFailureReason } from './types.js';
9
+ import { verifyDelegation } from './verify.js';
10
+ export { DELEGATION_META_KEY, DELEGATION_EXTENSION_URI, REGISTRY_PUBLISHER_PROVIDED_KEY, extractDelegationPublications, parseDelegationPointer, pointerRecordName, POINTER_VERSION, POINTER_RECORD_PREFIX, } from './carriers.js';
11
+ /**
12
+ * Verify the delegation an MCP server card or A2A agent card publishes.
13
+ *
14
+ * `options.delegate` still means what it means everywhere else: the artifact identity YOU
15
+ * resolved — the npm package you are about to install, the endpoint you are about to call —
16
+ * NOT a field read out of this card. A card that describes itself is not evidence about
17
+ * itself.
18
+ */
19
+ export async function verifyPublishedDelegation(card, options) {
20
+ const tokens = collectPublishedTokens(card);
21
+ if (tokens.length === 0) {
22
+ return {
23
+ valid: false,
24
+ reason: 'no_publication',
25
+ outcome: classifyFailureReason('no_publication'),
26
+ message: 'card carries no delegation publication in any sanctioned location',
27
+ };
28
+ }
29
+ if (tokens.length > 1) {
30
+ return {
31
+ valid: false,
32
+ reason: 'multiple_publications',
33
+ outcome: classifyFailureReason('multiple_publications'),
34
+ message: `card publishes ${tokens.length} different delegation tokens — refusing to guess which one it means`,
35
+ };
36
+ }
37
+ return verifyDelegation(tokens[0], options);
38
+ }
39
+ //# sourceMappingURL=publication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publication.js","sourceRoot":"","sources":["../src/publication.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,+BAA+B,EAC/B,6BAA6B,EAC7B,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAGvB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAAa,EACb,OAAsB;IAEtB,MAAM,MAAM,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,gBAAgB;YACxB,OAAO,EAAE,qBAAqB,CAAC,gBAAgB,CAAC;YAChD,OAAO,EAAE,mEAAmE;SAC7E,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,uBAAuB;YAC/B,OAAO,EAAE,qBAAqB,CAAC,uBAAuB,CAAC;YACvD,OAAO,EAAE,kBAAkB,MAAM,CAAC,MAAM,qEAAqE;SAC9G,CAAC;IACJ,CAAC;IACD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Revocation status of a delegation, over two channels that answer different questions.
3
+ *
4
+ * 1. **Token Status List** (IETF draft-ietf-oauth-status-list). The token carries its own
5
+ * slot — `status.status_list.{idx, uri}` — and the signed list at that uri says whether
6
+ * the slot is valid (0), revoked (1) or suspended (2). This is the standard, cacheable,
7
+ * herd-private channel, and — since `h-status-list-cascade-fold` — it is AUTHORITATIVE
8
+ * for the delegation, including the domain control proof cascade underneath it: a
9
+ * revoked or suspended control folds into this same bit, so `checkDelegationStatus`
10
+ * trusts a `valid`/`revoked`/`suspended` list read and does not also ask the endpoint
11
+ * (`h-delegation-verifier-status-fallthrough-gate`).
12
+ * 2. **The issuer's status endpoint.** Stays the only channel for three things the bit
13
+ * cannot express: `unknown_delegation` ("no record of this handle" is distinguishable
14
+ * from "we have a record and it is dead" only by a live call), a token carrying no
15
+ * status-list claim at all, and cascade-derived EXPIRY — the control's own `expires_at`
16
+ * is never packed into the bit (only revoked/suspended are), so a delegation whose
17
+ * effective lifetime is capped by an expired control, while its own list bit and its own
18
+ * `exp` claim both still read fine, is only visible through this endpoint.
19
+ *
20
+ * `h-fix-control-proof-reissue-shorter` closes the write side of that expiry gap going
21
+ * forward — a delegation can no longer be minted longer than its control, and reissuing a
22
+ * control shorter than an outstanding delegation now caps the reissue instead of leaving
23
+ * the delegation to outlive it — but two bounded residuals remain, both deliberately
24
+ * accepted there rather than fixed here: a control shortened before that task shipped
25
+ * (measured zero on staging, unmeasured in production), and that task's own fail-open
26
+ * when its registry-budget guard cannot run. A verifier package cannot special-case any
27
+ * one issuer's guarantees, so this residual is disclosed, not silently assumed away — see
28
+ * the package README's Failure reasons section.
29
+ *
30
+ * Both channels are authenticated: the status list is a signed JWT checked against the same
31
+ * key set as the delegation, and the uri inside the token is refused unless it belongs to the
32
+ * trusted issuer — it arrives INSIDE the artifact being verified, so treating it as an address
33
+ * to fetch would hand an attacker both the status authority and an SSRF primitive.
34
+ */
35
+ import type { FetchLike, TrustedIssuer } from './types.js';
36
+ export type StatusOutcome = {
37
+ state: 'valid';
38
+ } | {
39
+ state: 'revoked';
40
+ message: string;
41
+ } | {
42
+ state: 'suspended';
43
+ message: string;
44
+ }
45
+ /** The issuer's clock says this delegation is over — distinct from withdrawn. */
46
+ | {
47
+ state: 'expired';
48
+ message: string;
49
+ }
50
+ /** The issuer has no record of the delegation at all. */
51
+ | {
52
+ state: 'unknown';
53
+ message: string;
54
+ } | {
55
+ state: 'untrusted_uri';
56
+ message: string;
57
+ } | {
58
+ state: 'unavailable';
59
+ message: string;
60
+ };
61
+ export declare function allowedStatusOrigins(issuer: TrustedIssuer): string[];
62
+ /**
63
+ * Read one 2-bit slot out of a decoded status list. Lowest index in the least significant
64
+ * bits, `bits` statuses per byte — the draft's byte order.
65
+ */
66
+ export declare function readStatusSlot(bytes: Uint8Array, index: number, bits: number): number | null;
67
+ interface StatusListClaim {
68
+ idx: number;
69
+ uri: string;
70
+ }
71
+ export declare function readStatusListClaim(claims: Record<string, unknown>): StatusListClaim | null;
72
+ /**
73
+ * Combined status. A negative answer from EITHER channel is decisive; `unavailable` from
74
+ * either one is reported so the caller can fail closed.
75
+ */
76
+ export declare function checkDelegationStatus(params: {
77
+ token: string;
78
+ claims: Record<string, unknown>;
79
+ issuer: TrustedIssuer;
80
+ fetch: FetchLike;
81
+ timeoutMs: number;
82
+ now: Date;
83
+ clockToleranceSeconds: number;
84
+ }): Promise<StatusOutcome>;
85
+ export {};
86
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,MAAM,aAAa,GACrB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACzC,iFAAiF;GAC/E;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACvC,yDAAyD;GACvD;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAM9C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,EAAE,CAWpE;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5F;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,GAAG,IAAI,CAS3F;AA+ND;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,IAAI,CAAC;IACV,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GAAG,OAAO,CAAC,aAAa,CAAC,CA0BzB"}
package/dist/status.js ADDED
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Revocation status of a delegation, over two channels that answer different questions.
3
+ *
4
+ * 1. **Token Status List** (IETF draft-ietf-oauth-status-list). The token carries its own
5
+ * slot — `status.status_list.{idx, uri}` — and the signed list at that uri says whether
6
+ * the slot is valid (0), revoked (1) or suspended (2). This is the standard, cacheable,
7
+ * herd-private channel, and — since `h-status-list-cascade-fold` — it is AUTHORITATIVE
8
+ * for the delegation, including the domain control proof cascade underneath it: a
9
+ * revoked or suspended control folds into this same bit, so `checkDelegationStatus`
10
+ * trusts a `valid`/`revoked`/`suspended` list read and does not also ask the endpoint
11
+ * (`h-delegation-verifier-status-fallthrough-gate`).
12
+ * 2. **The issuer's status endpoint.** Stays the only channel for three things the bit
13
+ * cannot express: `unknown_delegation` ("no record of this handle" is distinguishable
14
+ * from "we have a record and it is dead" only by a live call), a token carrying no
15
+ * status-list claim at all, and cascade-derived EXPIRY — the control's own `expires_at`
16
+ * is never packed into the bit (only revoked/suspended are), so a delegation whose
17
+ * effective lifetime is capped by an expired control, while its own list bit and its own
18
+ * `exp` claim both still read fine, is only visible through this endpoint.
19
+ *
20
+ * `h-fix-control-proof-reissue-shorter` closes the write side of that expiry gap going
21
+ * forward — a delegation can no longer be minted longer than its control, and reissuing a
22
+ * control shorter than an outstanding delegation now caps the reissue instead of leaving
23
+ * the delegation to outlive it — but two bounded residuals remain, both deliberately
24
+ * accepted there rather than fixed here: a control shortened before that task shipped
25
+ * (measured zero on staging, unmeasured in production), and that task's own fail-open
26
+ * when its registry-budget guard cannot run. A verifier package cannot special-case any
27
+ * one issuer's guarantees, so this residual is disclosed, not silently assumed away — see
28
+ * the package README's Failure reasons section.
29
+ *
30
+ * Both channels are authenticated: the status list is a signed JWT checked against the same
31
+ * key set as the delegation, and the uri inside the token is refused unless it belongs to the
32
+ * trusted issuer — it arrives INSIDE the artifact being verified, so treating it as an address
33
+ * to fetch would hand an attacker both the status authority and an SSRF primitive.
34
+ */
35
+ import { base64UrlToBytes, isRecord, parseJwt } from './jwt.js';
36
+ import { getVerificationKeys, isSupportedAlgorithm, MAX_REMOTE_BODY_BYTES, verifySignature } from './jwks.js';
37
+ const STATUS_VALID = 0;
38
+ const STATUS_REVOKED = 1;
39
+ const STATUS_SUSPENDED = 2;
40
+ export function allowedStatusOrigins(issuer) {
41
+ const origins = new Set();
42
+ for (const candidate of [issuer.jwksUri, issuer.statusEndpoint, ...(issuer.statusListOrigins ?? [])]) {
43
+ if (!candidate)
44
+ continue;
45
+ try {
46
+ origins.add(new URL(candidate).origin);
47
+ }
48
+ catch {
49
+ // A malformed policy entry contributes no origin; the caller's own validation reports it.
50
+ }
51
+ }
52
+ return [...origins];
53
+ }
54
+ /** Inflate a zlib-wrapped DEFLATE payload without pulling in a compression dependency. */
55
+ async function inflate(bytes) {
56
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('deflate'));
57
+ const buffer = await new Response(stream).arrayBuffer();
58
+ return new Uint8Array(buffer);
59
+ }
60
+ /**
61
+ * Read one 2-bit slot out of a decoded status list. Lowest index in the least significant
62
+ * bits, `bits` statuses per byte — the draft's byte order.
63
+ */
64
+ export function readStatusSlot(bytes, index, bits) {
65
+ if (!Number.isInteger(index) || index < 0)
66
+ return null;
67
+ if (bits !== 1 && bits !== 2 && bits !== 4 && bits !== 8)
68
+ return null;
69
+ const perByte = 8 / bits;
70
+ const byteIndex = Math.floor(index / perByte);
71
+ if (byteIndex >= bytes.length)
72
+ return null;
73
+ const shift = (index % perByte) * bits;
74
+ const mask = (1 << bits) - 1;
75
+ return (bytes[byteIndex] >> shift) & mask;
76
+ }
77
+ export function readStatusListClaim(claims) {
78
+ const status = claims.status;
79
+ if (!isRecord(status))
80
+ return null;
81
+ const statusList = status.status_list;
82
+ if (!isRecord(statusList))
83
+ return null;
84
+ const { idx, uri } = statusList;
85
+ if (typeof idx !== 'number' || !Number.isInteger(idx) || idx < 0)
86
+ return null;
87
+ if (typeof uri !== 'string' || uri.length === 0)
88
+ return null;
89
+ return { idx, uri };
90
+ }
91
+ async function checkStatusList(params) {
92
+ const { claim, issuer } = params;
93
+ let uri;
94
+ try {
95
+ uri = new URL(claim.uri);
96
+ }
97
+ catch {
98
+ return { state: 'untrusted_uri', message: 'status list uri is not a valid URL' };
99
+ }
100
+ if (!allowedStatusOrigins(issuer).includes(uri.origin)) {
101
+ return {
102
+ state: 'untrusted_uri',
103
+ message: `status list uri origin ${uri.origin} is not an origin of trusted issuer "${issuer.issuer}"`,
104
+ };
105
+ }
106
+ let response;
107
+ try {
108
+ response = await params.fetch(uri.toString(), {
109
+ signal: AbortSignal.timeout(params.timeoutMs),
110
+ headers: { accept: 'application/statuslist+jwt, application/jwt, text/plain' },
111
+ // The origin guard above is only worth as much as the hop it guards: with the default
112
+ // `follow`, ANY open redirect on the issuer's origin would carry this fetch off it —
113
+ // the token controls the path — handing back both the status authority and an SSRF
114
+ // primitive against the verifying gateway's network.
115
+ redirect: 'error',
116
+ });
117
+ }
118
+ catch (error) {
119
+ return {
120
+ state: 'unavailable',
121
+ message: `status list fetch failed: ${error instanceof Error ? error.message : String(error)}`,
122
+ };
123
+ }
124
+ if (!response.ok) {
125
+ return { state: 'unavailable', message: `status list fetch failed with HTTP ${response.status}` };
126
+ }
127
+ const declaredLength = Number(response.headers.get('content-length'));
128
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_REMOTE_BODY_BYTES) {
129
+ return { state: 'unavailable', message: 'status list response is too large' };
130
+ }
131
+ const listToken = (await response.text()).trim();
132
+ if (listToken.length > MAX_REMOTE_BODY_BYTES) {
133
+ return { state: 'unavailable', message: 'status list response is too large' };
134
+ }
135
+ const parsed = parseJwt(listToken);
136
+ if (!parsed)
137
+ return { state: 'unavailable', message: 'status list is not a JWT' };
138
+ const alg = parsed.header.alg;
139
+ if (!isSupportedAlgorithm(alg)) {
140
+ return { state: 'unavailable', message: `status list uses unsupported algorithm ${String(alg)}` };
141
+ }
142
+ // The draft mandates this `typ`, and it is the one header that separates a status list from
143
+ // every other JWT the issuer signs with the SAME key — without it, any such token with a
144
+ // matching `sub` could stand in as a status authority.
145
+ if (parsed.header.typ !== 'statuslist+jwt') {
146
+ return {
147
+ state: 'unavailable',
148
+ message: `status list must carry typ "statuslist+jwt", got ${JSON.stringify(parsed.header.typ)}`,
149
+ };
150
+ }
151
+ let keys;
152
+ try {
153
+ keys = await getVerificationKeys({
154
+ jwksUri: issuer.jwksUri,
155
+ kid: typeof parsed.header.kid === 'string' ? parsed.header.kid : undefined,
156
+ alg,
157
+ fetch: params.fetch,
158
+ timeoutMs: params.timeoutMs,
159
+ });
160
+ }
161
+ catch (error) {
162
+ return {
163
+ state: 'unavailable',
164
+ message: `status list signing key unavailable: ${error instanceof Error ? error.message : String(error)}`,
165
+ };
166
+ }
167
+ if (keys.length === 0) {
168
+ return { state: 'unavailable', message: 'status list is signed by an unknown key' };
169
+ }
170
+ let signatureOk = false;
171
+ for (const key of keys) {
172
+ if (await verifySignature(key, alg, parsed.signingInput, parsed.signature)) {
173
+ signatureOk = true;
174
+ break;
175
+ }
176
+ }
177
+ if (!signatureOk) {
178
+ return { state: 'unavailable', message: 'status list signature is invalid' };
179
+ }
180
+ // The list must say it is the list we asked for, or a valid list from the same issuer could
181
+ // be replayed in place of another one.
182
+ if (parsed.payload.sub !== claim.uri) {
183
+ return { state: 'unavailable', message: 'status list `sub` does not match the uri it was fetched from' };
184
+ }
185
+ // A freshness channel with no expiry is not one: an `exp`-less list would be honoured
186
+ // forever, so its absence is treated as "cannot answer" rather than fail-open.
187
+ const nowSeconds = Math.floor(params.now.getTime() / 1000);
188
+ const exp = parsed.payload.exp;
189
+ if (typeof exp !== 'number') {
190
+ return { state: 'unavailable', message: 'status list carries no exp' };
191
+ }
192
+ if (nowSeconds > exp + params.clockToleranceSeconds) {
193
+ return { state: 'unavailable', message: 'status list has expired' };
194
+ }
195
+ const statusList = parsed.payload.status_list;
196
+ if (!isRecord(statusList) || typeof statusList.lst !== 'string') {
197
+ return { state: 'unavailable', message: 'status list payload has no `lst`' };
198
+ }
199
+ const bits = typeof statusList.bits === 'number' ? statusList.bits : 2;
200
+ let decoded;
201
+ try {
202
+ decoded = await inflate(base64UrlToBytes(statusList.lst));
203
+ }
204
+ catch (error) {
205
+ return {
206
+ state: 'unavailable',
207
+ message: `status list payload could not be decompressed: ${error instanceof Error ? error.message : String(error)}`,
208
+ };
209
+ }
210
+ const slot = readStatusSlot(decoded, claim.idx, bits);
211
+ if (slot === null) {
212
+ // A slot beyond the list is not "valid" — the issuer has no answer for this token.
213
+ return { state: 'unavailable', message: `status list has no slot ${claim.idx}` };
214
+ }
215
+ if (slot === STATUS_REVOKED)
216
+ return { state: 'revoked', message: 'the issuer status list marks this delegation revoked' };
217
+ if (slot === STATUS_SUSPENDED) {
218
+ return { state: 'suspended', message: 'the issuer status list marks this delegation suspended' };
219
+ }
220
+ if (slot !== STATUS_VALID) {
221
+ return { state: 'unavailable', message: `status list slot carries unknown status ${slot}` };
222
+ }
223
+ return { state: 'valid' };
224
+ }
225
+ /**
226
+ * Ask the issuer's status endpoint about this exact token. Contract:
227
+ * `POST` `{"proof_token": "<jwt>"}` → JSON with at least `{"valid": boolean}` and optionally
228
+ * `{"reason": "revoked" | "suspended" | "expired" | ...}`.
229
+ */
230
+ async function checkStatusEndpoint(params) {
231
+ const endpoint = params.issuer.statusEndpoint;
232
+ if (!endpoint) {
233
+ return {
234
+ state: 'unavailable',
235
+ message: `trusted issuer "${params.issuer.issuer}" declares no statusEndpoint, so the control-proof cascade cannot be checked`,
236
+ };
237
+ }
238
+ let response;
239
+ try {
240
+ response = await params.fetch(endpoint, {
241
+ method: 'POST',
242
+ signal: AbortSignal.timeout(params.timeoutMs),
243
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
244
+ body: JSON.stringify({ proof_token: params.token }),
245
+ redirect: 'error',
246
+ });
247
+ }
248
+ catch (error) {
249
+ return {
250
+ state: 'unavailable',
251
+ message: `status endpoint request failed: ${error instanceof Error ? error.message : String(error)}`,
252
+ };
253
+ }
254
+ if (!response.ok) {
255
+ return { state: 'unavailable', message: `status endpoint answered HTTP ${response.status}` };
256
+ }
257
+ let body;
258
+ try {
259
+ body = await response.json();
260
+ }
261
+ catch {
262
+ return { state: 'unavailable', message: 'status endpoint answered with a non-JSON body' };
263
+ }
264
+ if (!isRecord(body) || typeof body.valid !== 'boolean') {
265
+ return { state: 'unavailable', message: 'status endpoint answer carries no `valid` boolean' };
266
+ }
267
+ if (body.valid)
268
+ return { state: 'valid' };
269
+ // `valid: false` is not automatically "withdrawn". An issuer answers it for things that mean
270
+ // "I could not check" too — proof.holdings itself answers `status_unavailable` on a registry
271
+ // read failure. Reporting those as `revoked` would make a database blip at the issuer look
272
+ // like a permanent withdrawal to every gateway that asked, so only reasons that actually
273
+ // name a withdrawal are treated as one; anything unrecognised fails closed as unavailable.
274
+ const reason = typeof body.reason === 'string' ? body.reason : '';
275
+ switch (reason) {
276
+ case 'revoked':
277
+ return { state: 'revoked', message: 'the issuer reports this delegation revoked' };
278
+ case 'suspended':
279
+ return { state: 'suspended', message: 'the issuer reports this delegation suspended' };
280
+ case 'expired':
281
+ return { state: 'expired', message: 'the issuer reports this delegation expired' };
282
+ case 'unknown_delegation':
283
+ return { state: 'unknown', message: 'the issuer has no record of this delegation' };
284
+ default:
285
+ return {
286
+ state: 'unavailable',
287
+ message: `the issuer answered invalid without a recognised reason (${reason || 'none'})`,
288
+ };
289
+ }
290
+ }
291
+ /**
292
+ * Combined status. A negative answer from EITHER channel is decisive; `unavailable` from
293
+ * either one is reported so the caller can fail closed.
294
+ */
295
+ export async function checkDelegationStatus(params) {
296
+ const claim = readStatusListClaim(params.claims);
297
+ if (claim) {
298
+ const listOutcome = await checkStatusList({
299
+ claim,
300
+ issuer: params.issuer,
301
+ fetch: params.fetch,
302
+ timeoutMs: params.timeoutMs,
303
+ now: params.now,
304
+ clockToleranceSeconds: params.clockToleranceSeconds,
305
+ });
306
+ // Authoritative for `valid`/`revoked`/`suspended` — the fold (h-status-list-cascade-fold)
307
+ // makes those three the issuer's whole answer for this delegation's own bit.
308
+ // `unavailable`/`untrusted_uri` mean "could not read the list", not an answer from it, and
309
+ // still fall through to the endpoint below.
310
+ if (listOutcome.state !== 'unavailable' && listOutcome.state !== 'untrusted_uri') {
311
+ return listOutcome;
312
+ }
313
+ }
314
+ return checkStatusEndpoint({
315
+ token: params.token,
316
+ issuer: params.issuer,
317
+ fetch: params.fetch,
318
+ timeoutMs: params.timeoutMs,
319
+ });
320
+ }
321
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Public types of the Proof of Delegation reference verifier.
3
+ *
4
+ * The library verifies a delegation token — an attestation that the domain in `principal`
5
+ * authorized the artifact in `delegate` for a set of capability scopes. It attests NOTHING
6
+ * about the artifact's quality, safety or the business behind it, and it is not a statement
7
+ * that anyone "verified" the server.
8
+ *
9
+ * Two design rules separate this from an SDK, and both are load-bearing:
10
+ *
11
+ * 1. There is NO default trust list. `trustedIssuers` is supplied by the caller on every
12
+ * call — a verifier that trusts an issuer because the library shipped with its name
13
+ * hard-coded is a client for that issuer, not a verifier.
14
+ * 2. `delegate` is REQUIRED. The token is a bearer artifact: anyone can copy a genuine
15
+ * token into their own package. The comparison that defeats that is against the artifact
16
+ * identity the CALLER resolved independently — never against what the card says about
17
+ * itself.
18
+ */
19
+ /** A typed artifact identifier, canonicalized before comparison. */
20
+ export type DelegateType = 'url' | 'purl';
21
+ export interface DelegateInput {
22
+ type: DelegateType;
23
+ /** e.g. `pkg:npm/postmark-mcp` or `https://api.example.com/mcp` */
24
+ value: string;
25
+ }
26
+ /**
27
+ * One entry of the caller's trust policy.
28
+ *
29
+ * `statusEndpoint` is what lets the verifier see a revocation that a status list cannot show.
30
+ * An issuer's status list carries one bit per proof, so it reports the delegation's OWN
31
+ * revocation — but a delegation is only worth the domain control proof underneath it, and
32
+ * that cascade is resolved by the issuer, not encoded in the delegation's own bit. The
33
+ * endpoint contract is deliberately minimal: `POST` with `{"proof_token": "<jwt>"}` and a
34
+ * JSON answer carrying at least `{"valid": boolean}` (optionally `reason`).
35
+ */
36
+ export interface TrustedIssuer {
37
+ /** Exact value the token's `iss` claim must equal. */
38
+ issuer: string;
39
+ /** Absolute https URL of the issuer's JWKS. */
40
+ jwksUri: string;
41
+ /** Absolute https URL of the issuer's public token-status endpoint. */
42
+ statusEndpoint?: string;
43
+ /**
44
+ * Extra origins this issuer may serve its status list from. The origins of `jwksUri` and
45
+ * `statusEndpoint` are always allowed; anything else must be listed here or the status-list
46
+ * uri inside the token is refused WITHOUT being fetched.
47
+ */
48
+ statusListOrigins?: string[];
49
+ }
50
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
51
+ export interface VerifyOptions {
52
+ /** REQUIRED, no default. An empty list fails closed with `no_trust_policy`. */
53
+ trustedIssuers: TrustedIssuer[];
54
+ /** REQUIRED. The artifact identity the caller resolved independently. */
55
+ delegate: DelegateInput;
56
+ /**
57
+ * The domain you expect to have authorized this artifact. Optional in the type, but omitting
58
+ * it downgrades the answer — see `VerificationSuccess.principalChecked`. Compared after
59
+ * trimming and lowercasing.
60
+ */
61
+ expectedPrincipal?: string;
62
+ /**
63
+ * Scopes the delegation must grant. Compared as EXACT strings against the token's own
64
+ * `scope` (trimmed only): scope grammar belongs to the issuer, and imposing one issuer's
65
+ * rules here would make another issuer's grants unaskable.
66
+ */
67
+ requiredScopes?: string[];
68
+ /**
69
+ * `required` (default) — the delegation's status list is checked, and the issuer's status
70
+ * endpoint is asked too whenever the list cannot answer on its own (see `status.ts` for
71
+ * exactly when); anything unreachable fails closed with `status_unavailable`. There is no
72
+ * option to tolerate a transient `status_unavailable` — a caller wanting a grace window
73
+ * around a flaky issuer must implement that itself (e.g. retry, or branch on `outcome:
74
+ * 'unconfirmed'`); this library does not offer a built-in one.
75
+ * `skip` — signature and claims only; the result carries `statusChecked: false`, which a
76
+ * caller must treat as "revocation not checked", never as "not revoked".
77
+ */
78
+ statusCheck?: 'required' | 'skip';
79
+ /** Seconds of leeway on `exp`/`iat`. Default 5. */
80
+ clockToleranceSeconds?: number;
81
+ /** Verification time; defaults to now. */
82
+ now?: Date;
83
+ /** Injected fetch (tests, proxies, non-Node runtimes). Defaults to global fetch. */
84
+ fetch?: FetchLike;
85
+ /** Per-request network timeout in ms. Default 5000. */
86
+ timeoutMs?: number;
87
+ }
88
+ export type FailureReason =
89
+ /** The call itself is wrong (missing `delegate`, malformed trust entry). */
90
+ 'invalid_options'
91
+ /** `trustedIssuers` was empty — the library has no default trust. */
92
+ | 'no_trust_policy' | 'malformed_token' | 'untrusted_issuer' | 'unsupported_algorithm' | 'unknown_key' | 'bad_signature' | 'jwks_unavailable' | 'not_a_delegation' | 'unsupported_schema' | 'expired' | 'not_yet_valid' | 'malformed_claims' | 'principal_mismatch'
93
+ /** The token attests some OTHER artifact than the one the caller resolved. */
94
+ | 'delegate_mismatch' | 'scope_not_granted'
95
+ /** The status-list uri in the token does not belong to the trusted issuer. */
96
+ | 'status_uri_untrusted' | 'status_unavailable' | 'revoked' | 'suspended'
97
+ /** The issuer has no record of this delegation — distinct from "it could not answer". */
98
+ | 'unknown_delegation'
99
+ /** Card carries no delegation publication. */
100
+ | 'no_publication'
101
+ /** Card carries several publications — ambiguous, refused rather than guessed. */
102
+ | 'multiple_publications';
103
+ export type FailureOutcome = 'invalid' | 'unconfirmed';
104
+ /**
105
+ * Whether a `FailureReason` means "checked, and it's bad" (`invalid`) or "could not check"
106
+ * (`unconfirmed`). The three status-channel reasons that mean the revocation check itself
107
+ * failed are `unconfirmed`; every other reason is a completed check with a negative verdict,
108
+ * including a call-shape error — `verifyDelegation` always receives a well-formed
109
+ * `VerifyOptions` in practice, so those are `invalid` for exhaustiveness, not because they
110
+ * occur.
111
+ */
112
+ export declare function classifyFailureReason(reason: FailureReason): FailureOutcome;
113
+ export interface VerifiedDelegation {
114
+ issuer: string;
115
+ /** The domain that authorized the artifact. */
116
+ principal: string;
117
+ /** Canonical artifact identifier, byte-equal to what the caller resolved. */
118
+ delegate: string;
119
+ scope: string[];
120
+ /** Public handle of the domain control proof underneath this delegation. */
121
+ controlProof: string;
122
+ /** Public handle of the delegation itself (`ph_dlg_*` for proof.holdings). */
123
+ delegationId: string;
124
+ issuedAt: Date;
125
+ expiresAt: Date;
126
+ }
127
+ export interface VerificationSuccess {
128
+ valid: true;
129
+ /** False only when `statusCheck: 'skip'` was requested. */
130
+ statusChecked: boolean;
131
+ /**
132
+ * False when no `expectedPrincipal` was supplied — and then `valid: true` means LESS than it
133
+ * looks like. An issuer binds `delegate` to nothing: the domain in `principal` is whatever
134
+ * the requester proved control of, and the artifact is free-text they chose. So anyone who
135
+ * can verify `evil.example` can mint a genuine, signature-valid delegation naming SOMEONE
136
+ * ELSE's package. The required `delegate` argument defeats a COPIED token; only pinning
137
+ * `principal` defeats a token minted by the wrong party.
138
+ *
139
+ * A caller that already knows whose artifact this is (an agent installing `postmark-mcp`
140
+ * knows it expects `postmarkapp.com`) must pass `expectedPrincipal`. A caller that is
141
+ * discovering who claims an artifact may legitimately leave it off — and must then present
142
+ * the answer as "X says it authorized this", never as "verified".
143
+ */
144
+ principalChecked: boolean;
145
+ delegation: VerifiedDelegation;
146
+ }
147
+ export interface VerificationFailure {
148
+ valid: false;
149
+ reason: FailureReason;
150
+ /** `classifyFailureReason(reason)` — set for every failure, never left for the caller to derive. */
151
+ outcome: FailureOutcome;
152
+ message: string;
153
+ }
154
+ export type VerificationResult = VerificationSuccess | VerificationFailure;
155
+ //# sourceMappingURL=types.d.ts.map