@dupecom/botcha-cloudflare 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +11 -4
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +2 -1
- package/dist/dashboard/whitepaper.d.ts.map +1 -1
- package/dist/dashboard/whitepaper.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -1
- package/dist/static.d.ts +541 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +353 -3
- package/dist/tap-attestation-routes.d.ts +204 -0
- package/dist/tap-attestation-routes.d.ts.map +1 -0
- package/dist/tap-attestation-routes.js +396 -0
- package/dist/tap-attestation.d.ts +178 -0
- package/dist/tap-attestation.d.ts.map +1 -0
- package/dist/tap-attestation.js +416 -0
- package/dist/tap-delegation-routes.d.ts +236 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -0
- package/dist/tap-delegation-routes.js +378 -0
- package/dist/tap-delegation.d.ts +127 -0
- package/dist/tap-delegation.d.ts.map +1 -0
- package/dist/tap-delegation.js +490 -0
- package/dist/tap-reputation-routes.d.ts +154 -0
- package/dist/tap-reputation-routes.d.ts.map +1 -0
- package/dist/tap-reputation-routes.js +341 -0
- package/dist/tap-reputation.d.ts +136 -0
- package/dist/tap-reputation.d.ts.map +1 -0
- package/dist/tap-reputation.js +346 -0
- package/package.json +1 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Capability Attestation API Routes
|
|
3
|
+
*
|
|
4
|
+
* Endpoints for issuing, retrieving, revoking, and verifying
|
|
5
|
+
* capability attestation tokens for TAP agents.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* POST /v1/attestations — Issue attestation token
|
|
9
|
+
* GET /v1/attestations/:id — Get attestation details
|
|
10
|
+
* GET /v1/attestations — List attestations for agent
|
|
11
|
+
* POST /v1/attestations/:id/revoke — Revoke attestation
|
|
12
|
+
* POST /v1/verify/attestation — Verify attestation + check capability
|
|
13
|
+
*/
|
|
14
|
+
import { extractBearerToken, verifyToken } from './auth.js';
|
|
15
|
+
import { issueAttestation, getAttestation, revokeAttestation, verifyAttestationToken, verifyAndCheckCapability, isValidCapabilityPattern, } from './tap-attestation.js';
|
|
16
|
+
// ============ VALIDATION HELPERS ============
|
|
17
|
+
async function validateAppAccess(c, requireAuth = true) {
|
|
18
|
+
const queryAppId = c.req.query('app_id');
|
|
19
|
+
let jwtAppId;
|
|
20
|
+
const authHeader = c.req.header('authorization');
|
|
21
|
+
const token = extractBearerToken(authHeader);
|
|
22
|
+
if (token) {
|
|
23
|
+
const result = await verifyToken(token, c.env.JWT_SECRET, c.env);
|
|
24
|
+
if (result.valid && result.payload) {
|
|
25
|
+
jwtAppId = result.payload.app_id;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const appId = queryAppId || jwtAppId;
|
|
29
|
+
if (requireAuth && !appId) {
|
|
30
|
+
return { valid: false, error: 'MISSING_APP_ID', status: 401 };
|
|
31
|
+
}
|
|
32
|
+
return { valid: true, appId };
|
|
33
|
+
}
|
|
34
|
+
// ============ ROUTE HANDLERS ============
|
|
35
|
+
/**
|
|
36
|
+
* POST /v1/attestations
|
|
37
|
+
* Issue a capability attestation token for an agent
|
|
38
|
+
*/
|
|
39
|
+
export async function issueAttestationRoute(c) {
|
|
40
|
+
try {
|
|
41
|
+
const appAccess = await validateAppAccess(c, true);
|
|
42
|
+
if (!appAccess.valid) {
|
|
43
|
+
return c.json({
|
|
44
|
+
success: false,
|
|
45
|
+
error: appAccess.error,
|
|
46
|
+
message: 'Authentication required'
|
|
47
|
+
}, (appAccess.status || 401));
|
|
48
|
+
}
|
|
49
|
+
const body = await c.req.json().catch(() => ({}));
|
|
50
|
+
// Validate required fields
|
|
51
|
+
if (!body.agent_id) {
|
|
52
|
+
return c.json({
|
|
53
|
+
success: false,
|
|
54
|
+
error: 'MISSING_AGENT_ID',
|
|
55
|
+
message: 'agent_id is required'
|
|
56
|
+
}, 400);
|
|
57
|
+
}
|
|
58
|
+
if (!body.can || !Array.isArray(body.can) || body.can.length === 0) {
|
|
59
|
+
return c.json({
|
|
60
|
+
success: false,
|
|
61
|
+
error: 'MISSING_CAPABILITIES',
|
|
62
|
+
message: 'At least one "can" rule is required (e.g. ["read:invoices", "browse:*"])'
|
|
63
|
+
}, 400);
|
|
64
|
+
}
|
|
65
|
+
// Validate capability patterns
|
|
66
|
+
for (const rule of body.can) {
|
|
67
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
68
|
+
return c.json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'INVALID_CAPABILITY_PATTERN',
|
|
71
|
+
message: `Invalid capability pattern in "can": ${rule}. Use "action:resource" format.`
|
|
72
|
+
}, 400);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (body.cannot && Array.isArray(body.cannot)) {
|
|
76
|
+
for (const rule of body.cannot) {
|
|
77
|
+
if (!isValidCapabilityPattern(rule)) {
|
|
78
|
+
return c.json({
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'INVALID_CAPABILITY_PATTERN',
|
|
81
|
+
message: `Invalid capability pattern in "cannot": ${rule}. Use "action:resource" format.`
|
|
82
|
+
}, 400);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const options = {
|
|
87
|
+
agent_id: body.agent_id,
|
|
88
|
+
can: body.can,
|
|
89
|
+
cannot: body.cannot,
|
|
90
|
+
restrictions: body.restrictions,
|
|
91
|
+
duration_seconds: body.duration_seconds,
|
|
92
|
+
delegation_id: body.delegation_id,
|
|
93
|
+
metadata: body.metadata,
|
|
94
|
+
};
|
|
95
|
+
const result = await issueAttestation(c.env.AGENTS, c.env.SESSIONS, appAccess.appId, c.env.JWT_SECRET, options);
|
|
96
|
+
if (!result.success) {
|
|
97
|
+
const status = result.error?.includes('not found') ? 404
|
|
98
|
+
: result.error?.includes('does not belong') ? 403
|
|
99
|
+
: result.error?.includes('Too many rules') ? 400
|
|
100
|
+
: 400;
|
|
101
|
+
return c.json({
|
|
102
|
+
success: false,
|
|
103
|
+
error: 'ATTESTATION_ISSUANCE_FAILED',
|
|
104
|
+
message: result.error
|
|
105
|
+
}, status);
|
|
106
|
+
}
|
|
107
|
+
const att = result.attestation;
|
|
108
|
+
return c.json({
|
|
109
|
+
success: true,
|
|
110
|
+
attestation_id: att.attestation_id,
|
|
111
|
+
agent_id: att.agent_id,
|
|
112
|
+
app_id: att.app_id,
|
|
113
|
+
token: att.token,
|
|
114
|
+
can: att.can,
|
|
115
|
+
cannot: att.cannot,
|
|
116
|
+
restrictions: att.restrictions || null,
|
|
117
|
+
delegation_id: att.delegation_id || null,
|
|
118
|
+
metadata: att.metadata || null,
|
|
119
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
120
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
121
|
+
}, 201);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error('Attestation issuance error:', error);
|
|
125
|
+
return c.json({
|
|
126
|
+
success: false,
|
|
127
|
+
error: 'INTERNAL_ERROR',
|
|
128
|
+
message: 'Internal server error'
|
|
129
|
+
}, 500);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* GET /v1/attestations/:id
|
|
134
|
+
* Get attestation details
|
|
135
|
+
*/
|
|
136
|
+
export async function getAttestationRoute(c) {
|
|
137
|
+
try {
|
|
138
|
+
const attestationId = c.req.param('id');
|
|
139
|
+
if (!attestationId) {
|
|
140
|
+
return c.json({
|
|
141
|
+
success: false,
|
|
142
|
+
error: 'MISSING_ATTESTATION_ID',
|
|
143
|
+
message: 'Attestation ID is required'
|
|
144
|
+
}, 400);
|
|
145
|
+
}
|
|
146
|
+
const result = await getAttestation(c.env.SESSIONS, attestationId);
|
|
147
|
+
if (!result.success || !result.attestation) {
|
|
148
|
+
return c.json({
|
|
149
|
+
success: false,
|
|
150
|
+
error: 'ATTESTATION_NOT_FOUND',
|
|
151
|
+
message: result.error || 'Attestation not found or expired'
|
|
152
|
+
}, 404);
|
|
153
|
+
}
|
|
154
|
+
const att = result.attestation;
|
|
155
|
+
return c.json({
|
|
156
|
+
success: true,
|
|
157
|
+
attestation_id: att.attestation_id,
|
|
158
|
+
agent_id: att.agent_id,
|
|
159
|
+
app_id: att.app_id,
|
|
160
|
+
can: att.can,
|
|
161
|
+
cannot: att.cannot,
|
|
162
|
+
restrictions: att.restrictions || null,
|
|
163
|
+
delegation_id: att.delegation_id || null,
|
|
164
|
+
metadata: att.metadata || null,
|
|
165
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
166
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
167
|
+
revoked: att.revoked,
|
|
168
|
+
revoked_at: att.revoked_at ? new Date(att.revoked_at).toISOString() : null,
|
|
169
|
+
revocation_reason: att.revocation_reason || null,
|
|
170
|
+
time_remaining: Math.max(0, att.expires_at - Date.now()),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error('Attestation retrieval error:', error);
|
|
175
|
+
return c.json({
|
|
176
|
+
success: false,
|
|
177
|
+
error: 'INTERNAL_ERROR',
|
|
178
|
+
message: 'Internal server error'
|
|
179
|
+
}, 500);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* GET /v1/attestations
|
|
184
|
+
* List attestations for an agent
|
|
185
|
+
*
|
|
186
|
+
* Query params:
|
|
187
|
+
* agent_id — required, the agent to list attestations for
|
|
188
|
+
*/
|
|
189
|
+
export async function listAttestationsRoute(c) {
|
|
190
|
+
try {
|
|
191
|
+
const appAccess = await validateAppAccess(c, true);
|
|
192
|
+
if (!appAccess.valid) {
|
|
193
|
+
return c.json({
|
|
194
|
+
success: false,
|
|
195
|
+
error: appAccess.error,
|
|
196
|
+
message: 'Authentication required'
|
|
197
|
+
}, (appAccess.status || 401));
|
|
198
|
+
}
|
|
199
|
+
const agentId = c.req.query('agent_id');
|
|
200
|
+
if (!agentId) {
|
|
201
|
+
return c.json({
|
|
202
|
+
success: false,
|
|
203
|
+
error: 'MISSING_AGENT_ID',
|
|
204
|
+
message: 'agent_id query parameter is required'
|
|
205
|
+
}, 400);
|
|
206
|
+
}
|
|
207
|
+
// Get the attestation index for this agent
|
|
208
|
+
const indexKey = `agent_attestations:${agentId}`;
|
|
209
|
+
const indexData = await c.env.SESSIONS.get(indexKey, 'text');
|
|
210
|
+
const attestationIds = indexData ? JSON.parse(indexData) : [];
|
|
211
|
+
// Fetch each attestation (filter out expired/missing)
|
|
212
|
+
const attestations = [];
|
|
213
|
+
for (const id of attestationIds) {
|
|
214
|
+
const result = await getAttestation(c.env.SESSIONS, id);
|
|
215
|
+
if (result.success && result.attestation) {
|
|
216
|
+
const att = result.attestation;
|
|
217
|
+
// Only include attestations for this app
|
|
218
|
+
if (att.app_id === appAccess.appId) {
|
|
219
|
+
attestations.push({
|
|
220
|
+
attestation_id: att.attestation_id,
|
|
221
|
+
agent_id: att.agent_id,
|
|
222
|
+
can: att.can,
|
|
223
|
+
cannot: att.cannot,
|
|
224
|
+
created_at: new Date(att.created_at).toISOString(),
|
|
225
|
+
expires_at: new Date(att.expires_at).toISOString(),
|
|
226
|
+
revoked: att.revoked,
|
|
227
|
+
delegation_id: att.delegation_id || null,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return c.json({
|
|
233
|
+
success: true,
|
|
234
|
+
attestations,
|
|
235
|
+
count: attestations.length,
|
|
236
|
+
agent_id: agentId,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error('Attestation listing error:', error);
|
|
241
|
+
return c.json({
|
|
242
|
+
success: false,
|
|
243
|
+
error: 'INTERNAL_ERROR',
|
|
244
|
+
message: 'Internal server error'
|
|
245
|
+
}, 500);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* POST /v1/attestations/:id/revoke
|
|
250
|
+
* Revoke an attestation
|
|
251
|
+
*/
|
|
252
|
+
export async function revokeAttestationRoute(c) {
|
|
253
|
+
try {
|
|
254
|
+
const attestationId = c.req.param('id');
|
|
255
|
+
if (!attestationId) {
|
|
256
|
+
return c.json({
|
|
257
|
+
success: false,
|
|
258
|
+
error: 'MISSING_ATTESTATION_ID',
|
|
259
|
+
message: 'Attestation ID is required'
|
|
260
|
+
}, 400);
|
|
261
|
+
}
|
|
262
|
+
const appAccess = await validateAppAccess(c, true);
|
|
263
|
+
if (!appAccess.valid) {
|
|
264
|
+
return c.json({
|
|
265
|
+
success: false,
|
|
266
|
+
error: appAccess.error,
|
|
267
|
+
message: 'Authentication required'
|
|
268
|
+
}, (appAccess.status || 401));
|
|
269
|
+
}
|
|
270
|
+
// Verify attestation exists and belongs to this app
|
|
271
|
+
const existing = await getAttestation(c.env.SESSIONS, attestationId);
|
|
272
|
+
if (!existing.success || !existing.attestation) {
|
|
273
|
+
return c.json({
|
|
274
|
+
success: false,
|
|
275
|
+
error: 'ATTESTATION_NOT_FOUND',
|
|
276
|
+
message: 'Attestation not found or expired'
|
|
277
|
+
}, 404);
|
|
278
|
+
}
|
|
279
|
+
if (existing.attestation.app_id !== appAccess.appId) {
|
|
280
|
+
return c.json({
|
|
281
|
+
success: false,
|
|
282
|
+
error: 'UNAUTHORIZED',
|
|
283
|
+
message: 'Attestation does not belong to this app'
|
|
284
|
+
}, 403);
|
|
285
|
+
}
|
|
286
|
+
const body = await c.req.json().catch(() => ({}));
|
|
287
|
+
const reason = body.reason || undefined;
|
|
288
|
+
const result = await revokeAttestation(c.env.SESSIONS, attestationId, reason);
|
|
289
|
+
if (!result.success) {
|
|
290
|
+
return c.json({
|
|
291
|
+
success: false,
|
|
292
|
+
error: 'REVOCATION_FAILED',
|
|
293
|
+
message: result.error
|
|
294
|
+
}, 500);
|
|
295
|
+
}
|
|
296
|
+
const att = result.attestation;
|
|
297
|
+
return c.json({
|
|
298
|
+
success: true,
|
|
299
|
+
attestation_id: att.attestation_id,
|
|
300
|
+
revoked: true,
|
|
301
|
+
revoked_at: att.revoked_at ? new Date(att.revoked_at).toISOString() : null,
|
|
302
|
+
revocation_reason: att.revocation_reason || null,
|
|
303
|
+
message: 'Attestation revoked. Token will be rejected on verification.',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error('Attestation revocation error:', error);
|
|
308
|
+
return c.json({
|
|
309
|
+
success: false,
|
|
310
|
+
error: 'INTERNAL_ERROR',
|
|
311
|
+
message: 'Internal server error'
|
|
312
|
+
}, 500);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* POST /v1/verify/attestation
|
|
317
|
+
* Verify an attestation token and optionally check a specific capability
|
|
318
|
+
*
|
|
319
|
+
* Body:
|
|
320
|
+
* token — required, the attestation JWT token
|
|
321
|
+
* action — optional, capability action to check (e.g. "read")
|
|
322
|
+
* resource — optional, capability resource to check (e.g. "invoices")
|
|
323
|
+
*/
|
|
324
|
+
export async function verifyAttestationRoute(c) {
|
|
325
|
+
try {
|
|
326
|
+
const body = await c.req.json().catch(() => ({}));
|
|
327
|
+
if (!body.token) {
|
|
328
|
+
return c.json({
|
|
329
|
+
success: false,
|
|
330
|
+
error: 'MISSING_TOKEN',
|
|
331
|
+
message: 'Attestation token is required'
|
|
332
|
+
}, 400);
|
|
333
|
+
}
|
|
334
|
+
// If action specified, do full verify+check
|
|
335
|
+
if (body.action) {
|
|
336
|
+
const result = await verifyAndCheckCapability(c.env.SESSIONS, body.token, c.env.JWT_SECRET, body.action, body.resource);
|
|
337
|
+
if (!result.allowed) {
|
|
338
|
+
return c.json({
|
|
339
|
+
success: false,
|
|
340
|
+
valid: false,
|
|
341
|
+
allowed: false,
|
|
342
|
+
agent_id: result.agent_id || null,
|
|
343
|
+
error: result.error || result.reason,
|
|
344
|
+
matched_rule: result.matched_rule || null,
|
|
345
|
+
checked_capability: body.resource ? `${body.action}:${body.resource}` : body.action,
|
|
346
|
+
}, result.error ? 401 : 403);
|
|
347
|
+
}
|
|
348
|
+
return c.json({
|
|
349
|
+
success: true,
|
|
350
|
+
valid: true,
|
|
351
|
+
allowed: true,
|
|
352
|
+
agent_id: result.agent_id,
|
|
353
|
+
reason: result.reason,
|
|
354
|
+
matched_rule: result.matched_rule,
|
|
355
|
+
checked_capability: body.resource ? `${body.action}:${body.resource}` : body.action,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// Otherwise just verify the token
|
|
359
|
+
const verification = await verifyAttestationToken(c.env.SESSIONS, body.token, c.env.JWT_SECRET);
|
|
360
|
+
if (!verification.valid || !verification.payload) {
|
|
361
|
+
return c.json({
|
|
362
|
+
success: false,
|
|
363
|
+
valid: false,
|
|
364
|
+
error: verification.error,
|
|
365
|
+
}, 401);
|
|
366
|
+
}
|
|
367
|
+
const payload = verification.payload;
|
|
368
|
+
return c.json({
|
|
369
|
+
success: true,
|
|
370
|
+
valid: true,
|
|
371
|
+
agent_id: payload.sub,
|
|
372
|
+
issuer: payload.iss,
|
|
373
|
+
can: payload.can,
|
|
374
|
+
cannot: payload.cannot,
|
|
375
|
+
restrictions: payload.restrictions || null,
|
|
376
|
+
delegation_id: payload.delegation_id || null,
|
|
377
|
+
issued_at: new Date(payload.iat * 1000).toISOString(),
|
|
378
|
+
expires_at: new Date(payload.exp * 1000).toISOString(),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error('Attestation verification error:', error);
|
|
383
|
+
return c.json({
|
|
384
|
+
success: false,
|
|
385
|
+
error: 'INTERNAL_ERROR',
|
|
386
|
+
message: 'Internal server error'
|
|
387
|
+
}, 500);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
export default {
|
|
391
|
+
issueAttestationRoute,
|
|
392
|
+
getAttestationRoute,
|
|
393
|
+
listAttestationsRoute,
|
|
394
|
+
revokeAttestationRoute,
|
|
395
|
+
verifyAttestationRoute,
|
|
396
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Capability Attestation
|
|
3
|
+
*
|
|
4
|
+
* Signed JWT tokens that cryptographically bind:
|
|
5
|
+
* WHO (agent_id) can do WHAT (can/cannot rules) on WHICH resources,
|
|
6
|
+
* attested by WHOM (app authority), until WHEN (expiration).
|
|
7
|
+
*
|
|
8
|
+
* Permission model: "action:resource" patterns with explicit deny.
|
|
9
|
+
* - Allow: { can: ["read:invoices", "write:orders", "browse:*"] }
|
|
10
|
+
* - Deny: { cannot: ["write:transfers", "purchase:*"] }
|
|
11
|
+
* - Deny takes precedence over allow
|
|
12
|
+
* - Wildcards: "*:*" (all), "read:*" (read anything), "*:invoices" (any action on invoices)
|
|
13
|
+
* - Backward compatible: bare actions like "browse" expand to "browse:*"
|
|
14
|
+
*
|
|
15
|
+
* Attestation tokens are signed JWTs (HS256) with type 'botcha-attestation'.
|
|
16
|
+
* They can be verified offline (signature check) or online (revocation check via KV).
|
|
17
|
+
*/
|
|
18
|
+
import type { KVNamespace } from './agents.js';
|
|
19
|
+
export interface AttestationPayload {
|
|
20
|
+
sub: string;
|
|
21
|
+
iss: string;
|
|
22
|
+
type: 'botcha-attestation';
|
|
23
|
+
jti: string;
|
|
24
|
+
iat: number;
|
|
25
|
+
exp: number;
|
|
26
|
+
can: string[];
|
|
27
|
+
cannot: string[];
|
|
28
|
+
restrictions?: {
|
|
29
|
+
max_amount?: number;
|
|
30
|
+
rate_limit?: number;
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
};
|
|
33
|
+
delegation_id?: string;
|
|
34
|
+
metadata?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
export interface Attestation {
|
|
37
|
+
attestation_id: string;
|
|
38
|
+
agent_id: string;
|
|
39
|
+
app_id: string;
|
|
40
|
+
can: string[];
|
|
41
|
+
cannot: string[];
|
|
42
|
+
restrictions?: {
|
|
43
|
+
max_amount?: number;
|
|
44
|
+
rate_limit?: number;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
};
|
|
47
|
+
delegation_id?: string;
|
|
48
|
+
metadata?: Record<string, string>;
|
|
49
|
+
token: string;
|
|
50
|
+
created_at: number;
|
|
51
|
+
expires_at: number;
|
|
52
|
+
revoked: boolean;
|
|
53
|
+
revoked_at?: number;
|
|
54
|
+
revocation_reason?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface IssueAttestationOptions {
|
|
57
|
+
agent_id: string;
|
|
58
|
+
can: string[];
|
|
59
|
+
cannot?: string[];
|
|
60
|
+
restrictions?: {
|
|
61
|
+
max_amount?: number;
|
|
62
|
+
rate_limit?: number;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
};
|
|
65
|
+
duration_seconds?: number;
|
|
66
|
+
delegation_id?: string;
|
|
67
|
+
metadata?: Record<string, string>;
|
|
68
|
+
}
|
|
69
|
+
export interface AttestationResult {
|
|
70
|
+
success: boolean;
|
|
71
|
+
attestation?: Attestation;
|
|
72
|
+
token?: string;
|
|
73
|
+
error?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface CapabilityCheckResult {
|
|
76
|
+
allowed: boolean;
|
|
77
|
+
reason?: string;
|
|
78
|
+
matched_rule?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Normalize a capability string to "action:resource" format.
|
|
82
|
+
* Bare actions like "browse" expand to "browse:*".
|
|
83
|
+
*/
|
|
84
|
+
export declare function normalizeCapability(cap: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Check if a pattern matches a target.
|
|
87
|
+
* Supports wildcards: "*:*", "read:*", "*:invoices", "read:invoices"
|
|
88
|
+
*/
|
|
89
|
+
export declare function matchesPattern(pattern: string, target: string): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Check if a specific action:resource is allowed by the can/cannot rules.
|
|
92
|
+
*
|
|
93
|
+
* Rules:
|
|
94
|
+
* 1. Check "cannot" list first — any match means DENIED (deny takes precedence)
|
|
95
|
+
* 2. Check "can" list — any match means ALLOWED
|
|
96
|
+
* 3. If no match in either list — DENIED (default deny)
|
|
97
|
+
*/
|
|
98
|
+
export declare function checkCapability(can: string[], cannot: string[], action: string, resource?: string): CapabilityCheckResult;
|
|
99
|
+
/**
|
|
100
|
+
* Validate capability pattern syntax.
|
|
101
|
+
* Valid: "action:resource", "action", "*:*", "read:*", "*:invoices"
|
|
102
|
+
*/
|
|
103
|
+
export declare function isValidCapabilityPattern(pattern: string): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Issue a capability attestation token for an agent.
|
|
106
|
+
*
|
|
107
|
+
* Validates:
|
|
108
|
+
* - Agent exists and belongs to the app
|
|
109
|
+
* - All capability patterns are syntactically valid
|
|
110
|
+
* - Total rules don't exceed limit
|
|
111
|
+
*
|
|
112
|
+
* Signs a JWT with the attestation payload.
|
|
113
|
+
*/
|
|
114
|
+
export declare function issueAttestation(agents: KVNamespace, sessions: KVNamespace, appId: string, secret: string, options: IssueAttestationOptions): Promise<AttestationResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Get an attestation by ID (from KV, not from JWT)
|
|
117
|
+
*/
|
|
118
|
+
export declare function getAttestation(sessions: KVNamespace, attestationId: string): Promise<AttestationResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Revoke an attestation.
|
|
121
|
+
*/
|
|
122
|
+
export declare function revokeAttestation(sessions: KVNamespace, attestationId: string, reason?: string): Promise<AttestationResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Verify an attestation JWT token.
|
|
125
|
+
*
|
|
126
|
+
* Checks:
|
|
127
|
+
* 1. JWT signature and expiration (cryptographic)
|
|
128
|
+
* 2. Token type is 'botcha-attestation'
|
|
129
|
+
* 3. Revocation status (via KV, fail-open)
|
|
130
|
+
*
|
|
131
|
+
* Returns the parsed attestation payload if valid.
|
|
132
|
+
*/
|
|
133
|
+
export declare function verifyAttestationToken(sessions: KVNamespace, token: string, secret: string): Promise<{
|
|
134
|
+
valid: boolean;
|
|
135
|
+
payload?: AttestationPayload;
|
|
136
|
+
error?: string;
|
|
137
|
+
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Full capability check: verify attestation token + check specific action:resource.
|
|
140
|
+
*
|
|
141
|
+
* Combines token verification with permission checking in one call.
|
|
142
|
+
*/
|
|
143
|
+
export declare function verifyAndCheckCapability(sessions: KVNamespace, token: string, secret: string, action: string, resource?: string): Promise<{
|
|
144
|
+
allowed: boolean;
|
|
145
|
+
agent_id?: string;
|
|
146
|
+
reason?: string;
|
|
147
|
+
matched_rule?: string;
|
|
148
|
+
error?: string;
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* Create a Hono middleware that enforces capability attestation.
|
|
152
|
+
*
|
|
153
|
+
* Usage:
|
|
154
|
+
* app.get('/api/invoices', requireCapability('read:invoices'), handler);
|
|
155
|
+
* app.post('/api/transfers', requireCapability('write:transfers'), handler);
|
|
156
|
+
*
|
|
157
|
+
* Extracts attestation token from:
|
|
158
|
+
* 1. X-Botcha-Attestation header
|
|
159
|
+
* 2. Authorization: Bearer header (if token type is attestation)
|
|
160
|
+
*
|
|
161
|
+
* On failure: returns 403 with capability denial details.
|
|
162
|
+
* On missing token: returns 401 requesting attestation.
|
|
163
|
+
*/
|
|
164
|
+
export declare function requireCapability(capability: string): (c: any, next: () => Promise<void>) => Promise<any>;
|
|
165
|
+
declare const _default: {
|
|
166
|
+
issueAttestation: typeof issueAttestation;
|
|
167
|
+
getAttestation: typeof getAttestation;
|
|
168
|
+
revokeAttestation: typeof revokeAttestation;
|
|
169
|
+
verifyAttestationToken: typeof verifyAttestationToken;
|
|
170
|
+
verifyAndCheckCapability: typeof verifyAndCheckCapability;
|
|
171
|
+
checkCapability: typeof checkCapability;
|
|
172
|
+
matchesPattern: typeof matchesPattern;
|
|
173
|
+
normalizeCapability: typeof normalizeCapability;
|
|
174
|
+
isValidCapabilityPattern: typeof isValidCapabilityPattern;
|
|
175
|
+
requireCapability: typeof requireCapability;
|
|
176
|
+
};
|
|
177
|
+
export default _default;
|
|
178
|
+
//# sourceMappingURL=tap-attestation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-attestation.d.ts","sourceRoot":"","sources":["../src/tap-attestation.ts"],"names":[],"mappings":"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"}
|