@dupecom/botcha-cloudflare 0.16.0 → 0.19.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/README.md +1 -1
- package/dist/auth.d.ts +48 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +89 -21
- package/dist/dashboard/docs.d.ts +15 -0
- package/dist/dashboard/docs.d.ts.map +1 -0
- package/dist/dashboard/docs.js +556 -0
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +12 -5
- 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 +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -13
- package/dist/static.d.ts +592 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +422 -9
- 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-jwks.d.ts +2 -1
- package/dist/tap-jwks.d.ts.map +1 -1
- package/dist/tap-jwks.js +31 -7
- 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,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Delegation Chains
|
|
3
|
+
*
|
|
4
|
+
* "User X authorized Agent Y to do Z until time T."
|
|
5
|
+
*
|
|
6
|
+
* Signed, auditable chains of trust between TAP agents. A delegation grants
|
|
7
|
+
* a subset of one agent's capabilities to another agent, with time bounds
|
|
8
|
+
* and depth limits. Delegations can be chained (A→B→C) with each link
|
|
9
|
+
* only narrowing capabilities, never expanding them.
|
|
10
|
+
*
|
|
11
|
+
* Key invariants:
|
|
12
|
+
* - Capabilities can only be narrowed (subset enforcement)
|
|
13
|
+
* - Chain depth is capped (default max: 3)
|
|
14
|
+
* - Revoking a delegation cascades to all sub-delegations
|
|
15
|
+
* - Expired delegations are automatically invalid
|
|
16
|
+
* - Both grantor and grantee must belong to the same app
|
|
17
|
+
*/
|
|
18
|
+
import type { KVNamespace } from './agents.js';
|
|
19
|
+
import { TAPCapability } from './tap-agents.js';
|
|
20
|
+
export interface Delegation {
|
|
21
|
+
delegation_id: string;
|
|
22
|
+
grantor_id: string;
|
|
23
|
+
grantee_id: string;
|
|
24
|
+
app_id: string;
|
|
25
|
+
capabilities: TAPCapability[];
|
|
26
|
+
parent_delegation_id?: string;
|
|
27
|
+
chain: string[];
|
|
28
|
+
depth: number;
|
|
29
|
+
max_depth: number;
|
|
30
|
+
created_at: number;
|
|
31
|
+
expires_at: number;
|
|
32
|
+
revoked: boolean;
|
|
33
|
+
revoked_at?: number;
|
|
34
|
+
revocation_reason?: string;
|
|
35
|
+
metadata?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export interface CreateDelegationOptions {
|
|
38
|
+
grantor_id: string;
|
|
39
|
+
grantee_id: string;
|
|
40
|
+
capabilities: TAPCapability[];
|
|
41
|
+
duration_seconds?: number;
|
|
42
|
+
max_depth?: number;
|
|
43
|
+
parent_delegation_id?: string;
|
|
44
|
+
metadata?: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
export interface DelegationResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
delegation?: Delegation;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface DelegationListResult {
|
|
52
|
+
success: boolean;
|
|
53
|
+
delegations?: Delegation[];
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface DelegationVerifyResult {
|
|
57
|
+
valid: boolean;
|
|
58
|
+
chain?: Delegation[];
|
|
59
|
+
effective_capabilities?: TAPCapability[];
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if capabilitiesB is a subset of capabilitiesA.
|
|
64
|
+
*
|
|
65
|
+
* A capability in B is valid if there exists a capability in A with the same
|
|
66
|
+
* action, and B's scope is a subset of A's scope (or A has wildcard scope).
|
|
67
|
+
* B's restrictions must be equal or stricter.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isCapabilitySubset(parent: TAPCapability[], child: TAPCapability[]): {
|
|
70
|
+
valid: boolean;
|
|
71
|
+
error?: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Create a delegation from one agent to another.
|
|
75
|
+
*
|
|
76
|
+
* Validates:
|
|
77
|
+
* - Both agents exist and belong to the same app
|
|
78
|
+
* - Grantor has the capabilities being delegated
|
|
79
|
+
* - Capabilities are a valid subset (never expanded)
|
|
80
|
+
* - Chain depth is within limits
|
|
81
|
+
* - Parent delegation (if sub-delegation) is valid and not revoked/expired
|
|
82
|
+
*/
|
|
83
|
+
export declare function createDelegation(agents: KVNamespace, sessions: KVNamespace, appId: string, options: CreateDelegationOptions): Promise<DelegationResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Get a delegation by ID
|
|
86
|
+
*/
|
|
87
|
+
export declare function getDelegation(sessions: KVNamespace, delegationId: string): Promise<DelegationResult>;
|
|
88
|
+
/**
|
|
89
|
+
* List delegations for an agent (inbound, outbound, or both)
|
|
90
|
+
*/
|
|
91
|
+
export declare function listDelegations(sessions: KVNamespace, options: {
|
|
92
|
+
agent_id?: string;
|
|
93
|
+
app_id?: string;
|
|
94
|
+
direction?: 'in' | 'out' | 'both';
|
|
95
|
+
include_revoked?: boolean;
|
|
96
|
+
include_expired?: boolean;
|
|
97
|
+
}): Promise<DelegationListResult>;
|
|
98
|
+
/**
|
|
99
|
+
* Revoke a delegation and cascade to all sub-delegations.
|
|
100
|
+
*
|
|
101
|
+
* When a delegation is revoked, all delegations that have it as a parent
|
|
102
|
+
* (directly or transitively) are also revoked. This is enforced by marking
|
|
103
|
+
* each delegation record as revoked.
|
|
104
|
+
*/
|
|
105
|
+
export declare function revokeDelegation(sessions: KVNamespace, delegationId: string, reason?: string): Promise<DelegationResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Verify an entire delegation chain is valid.
|
|
108
|
+
*
|
|
109
|
+
* Walks from the leaf delegation up through parent delegations to the root,
|
|
110
|
+
* verifying each link is:
|
|
111
|
+
* - Not revoked
|
|
112
|
+
* - Not expired
|
|
113
|
+
* - Capabilities are valid subsets
|
|
114
|
+
*
|
|
115
|
+
* Returns the full chain and effective (intersected) capabilities.
|
|
116
|
+
*/
|
|
117
|
+
export declare function verifyDelegationChain(agents: KVNamespace, sessions: KVNamespace, delegationId: string): Promise<DelegationVerifyResult>;
|
|
118
|
+
declare const _default: {
|
|
119
|
+
createDelegation: typeof createDelegation;
|
|
120
|
+
getDelegation: typeof getDelegation;
|
|
121
|
+
listDelegations: typeof listDelegations;
|
|
122
|
+
revokeDelegation: typeof revokeDelegation;
|
|
123
|
+
verifyDelegationChain: typeof verifyDelegationChain;
|
|
124
|
+
isCapabilitySubset: typeof isCapabilitySubset;
|
|
125
|
+
};
|
|
126
|
+
export default _default;
|
|
127
|
+
//# sourceMappingURL=tap-delegation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-delegation.d.ts","sourceRoot":"","sources":["../src/tap-delegation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Delegation Chains
|
|
3
|
+
*
|
|
4
|
+
* "User X authorized Agent Y to do Z until time T."
|
|
5
|
+
*
|
|
6
|
+
* Signed, auditable chains of trust between TAP agents. A delegation grants
|
|
7
|
+
* a subset of one agent's capabilities to another agent, with time bounds
|
|
8
|
+
* and depth limits. Delegations can be chained (A→B→C) with each link
|
|
9
|
+
* only narrowing capabilities, never expanding them.
|
|
10
|
+
*
|
|
11
|
+
* Key invariants:
|
|
12
|
+
* - Capabilities can only be narrowed (subset enforcement)
|
|
13
|
+
* - Chain depth is capped (default max: 3)
|
|
14
|
+
* - Revoking a delegation cascades to all sub-delegations
|
|
15
|
+
* - Expired delegations are automatically invalid
|
|
16
|
+
* - Both grantor and grantee must belong to the same app
|
|
17
|
+
*/
|
|
18
|
+
import { TAP_VALID_ACTIONS, getTAPAgent } from './tap-agents.js';
|
|
19
|
+
// ============ CONSTANTS ============
|
|
20
|
+
const MAX_DELEGATION_DEPTH = 10; // Absolute max depth
|
|
21
|
+
const DEFAULT_MAX_DEPTH = 3; // Default max depth
|
|
22
|
+
const DEFAULT_DURATION = 3600; // 1 hour in seconds
|
|
23
|
+
const MAX_DURATION = 86400 * 30; // 30 days max
|
|
24
|
+
const DELEGATION_PREFIX = 'del_';
|
|
25
|
+
// ============ CORE FUNCTIONS ============
|
|
26
|
+
/**
|
|
27
|
+
* Generate a unique delegation ID
|
|
28
|
+
*/
|
|
29
|
+
function generateDelegationId() {
|
|
30
|
+
const bytes = new Uint8Array(16);
|
|
31
|
+
crypto.getRandomValues(bytes);
|
|
32
|
+
return DELEGATION_PREFIX + Array.from(bytes)
|
|
33
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
34
|
+
.join('');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if capabilitiesB is a subset of capabilitiesA.
|
|
38
|
+
*
|
|
39
|
+
* A capability in B is valid if there exists a capability in A with the same
|
|
40
|
+
* action, and B's scope is a subset of A's scope (or A has wildcard scope).
|
|
41
|
+
* B's restrictions must be equal or stricter.
|
|
42
|
+
*/
|
|
43
|
+
export function isCapabilitySubset(parent, child) {
|
|
44
|
+
for (const childCap of child) {
|
|
45
|
+
// Find matching parent capability by action
|
|
46
|
+
const parentCap = parent.find(p => p.action === childCap.action);
|
|
47
|
+
if (!parentCap) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: `Cannot delegate capability '${childCap.action}': grantor does not have it`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Check scope subset
|
|
54
|
+
if (childCap.scope && childCap.scope.length > 0) {
|
|
55
|
+
// If parent has no scope or wildcard, child's scope is valid
|
|
56
|
+
if (parentCap.scope && !parentCap.scope.includes('*')) {
|
|
57
|
+
for (const s of childCap.scope) {
|
|
58
|
+
if (s !== '*' && !parentCap.scope.includes(s)) {
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
error: `Cannot delegate scope '${s}' for '${childCap.action}': grantor lacks it`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Child requesting wildcard but parent doesn't have it
|
|
65
|
+
if (s === '*') {
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
error: `Cannot delegate wildcard scope for '${childCap.action}': grantor has restricted scope`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Check restrictions — child must be equal or stricter
|
|
75
|
+
if (parentCap.restrictions) {
|
|
76
|
+
if (!childCap.restrictions) {
|
|
77
|
+
// Parent has restrictions but child doesn't — child is less restrictive
|
|
78
|
+
// This is NOT allowed: delegated capabilities must be at least as restrictive
|
|
79
|
+
return {
|
|
80
|
+
valid: false,
|
|
81
|
+
error: `Cannot delegate '${childCap.action}' without restrictions: grantor has restrictions`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// max_amount: child's must be <= parent's
|
|
85
|
+
if (parentCap.restrictions.max_amount !== undefined) {
|
|
86
|
+
if (childCap.restrictions.max_amount === undefined ||
|
|
87
|
+
childCap.restrictions.max_amount > parentCap.restrictions.max_amount) {
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
error: `Cannot delegate max_amount > ${parentCap.restrictions.max_amount} for '${childCap.action}'`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// rate_limit: child's must be <= parent's
|
|
95
|
+
if (parentCap.restrictions.rate_limit !== undefined) {
|
|
96
|
+
if (childCap.restrictions.rate_limit === undefined ||
|
|
97
|
+
childCap.restrictions.rate_limit > parentCap.restrictions.rate_limit) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
error: `Cannot delegate rate_limit > ${parentCap.restrictions.rate_limit} for '${childCap.action}'`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { valid: true };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create a delegation from one agent to another.
|
|
110
|
+
*
|
|
111
|
+
* Validates:
|
|
112
|
+
* - Both agents exist and belong to the same app
|
|
113
|
+
* - Grantor has the capabilities being delegated
|
|
114
|
+
* - Capabilities are a valid subset (never expanded)
|
|
115
|
+
* - Chain depth is within limits
|
|
116
|
+
* - Parent delegation (if sub-delegation) is valid and not revoked/expired
|
|
117
|
+
*/
|
|
118
|
+
export async function createDelegation(agents, sessions, appId, options) {
|
|
119
|
+
try {
|
|
120
|
+
// Validate basic inputs
|
|
121
|
+
if (!options.grantor_id || !options.grantee_id) {
|
|
122
|
+
return { success: false, error: 'grantor_id and grantee_id are required' };
|
|
123
|
+
}
|
|
124
|
+
if (options.grantor_id === options.grantee_id) {
|
|
125
|
+
return { success: false, error: 'Cannot delegate to self' };
|
|
126
|
+
}
|
|
127
|
+
if (!options.capabilities || options.capabilities.length === 0) {
|
|
128
|
+
return { success: false, error: 'At least one capability is required' };
|
|
129
|
+
}
|
|
130
|
+
// Validate capability actions
|
|
131
|
+
for (const cap of options.capabilities) {
|
|
132
|
+
if (!TAP_VALID_ACTIONS.includes(cap.action)) {
|
|
133
|
+
return { success: false, error: `Invalid capability action: ${cap.action}` };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Validate max_depth (will be overridden for sub-delegations below)
|
|
137
|
+
let maxDepth = Math.min(options.max_depth ?? DEFAULT_MAX_DEPTH, MAX_DELEGATION_DEPTH);
|
|
138
|
+
// Get grantor agent
|
|
139
|
+
const grantorResult = await getTAPAgent(agents, options.grantor_id);
|
|
140
|
+
if (!grantorResult.success || !grantorResult.agent) {
|
|
141
|
+
return { success: false, error: 'Grantor agent not found' };
|
|
142
|
+
}
|
|
143
|
+
const grantor = grantorResult.agent;
|
|
144
|
+
// Get grantee agent
|
|
145
|
+
const granteeResult = await getTAPAgent(agents, options.grantee_id);
|
|
146
|
+
if (!granteeResult.success || !granteeResult.agent) {
|
|
147
|
+
return { success: false, error: 'Grantee agent not found' };
|
|
148
|
+
}
|
|
149
|
+
const grantee = granteeResult.agent;
|
|
150
|
+
// Verify same app
|
|
151
|
+
if (grantor.app_id !== appId) {
|
|
152
|
+
return { success: false, error: 'Grantor does not belong to this app' };
|
|
153
|
+
}
|
|
154
|
+
if (grantee.app_id !== appId) {
|
|
155
|
+
return { success: false, error: 'Grantee does not belong to this app' };
|
|
156
|
+
}
|
|
157
|
+
// Determine effective capabilities of the grantor
|
|
158
|
+
let grantorCapabilities = grantor.capabilities || [];
|
|
159
|
+
let chain = [options.grantor_id, options.grantee_id];
|
|
160
|
+
let depth = 0;
|
|
161
|
+
// If this is a sub-delegation, validate the parent delegation
|
|
162
|
+
if (options.parent_delegation_id) {
|
|
163
|
+
const parentDel = await getDelegation(sessions, options.parent_delegation_id);
|
|
164
|
+
if (!parentDel.success || !parentDel.delegation) {
|
|
165
|
+
return { success: false, error: 'Parent delegation not found' };
|
|
166
|
+
}
|
|
167
|
+
const parent = parentDel.delegation;
|
|
168
|
+
// Parent must not be revoked
|
|
169
|
+
if (parent.revoked) {
|
|
170
|
+
return { success: false, error: 'Parent delegation has been revoked' };
|
|
171
|
+
}
|
|
172
|
+
// Parent must not be expired
|
|
173
|
+
if (Date.now() > parent.expires_at) {
|
|
174
|
+
return { success: false, error: 'Parent delegation has expired' };
|
|
175
|
+
}
|
|
176
|
+
// Grantor must be the grantee of the parent delegation
|
|
177
|
+
if (parent.grantee_id !== options.grantor_id) {
|
|
178
|
+
return { success: false, error: 'Grantor is not the grantee of the parent delegation' };
|
|
179
|
+
}
|
|
180
|
+
// Must be same app
|
|
181
|
+
if (parent.app_id !== appId) {
|
|
182
|
+
return { success: false, error: 'Parent delegation belongs to a different app' };
|
|
183
|
+
}
|
|
184
|
+
// Check depth limits — inherit max_depth from parent chain
|
|
185
|
+
depth = parent.depth + 1;
|
|
186
|
+
maxDepth = parent.max_depth; // Always inherit from parent
|
|
187
|
+
if (depth >= maxDepth) {
|
|
188
|
+
return { success: false, error: `Delegation depth limit reached (max: ${maxDepth})` };
|
|
189
|
+
}
|
|
190
|
+
// For sub-delegations, the effective capabilities come from the parent
|
|
191
|
+
grantorCapabilities = parent.capabilities;
|
|
192
|
+
chain = [...parent.chain, options.grantee_id];
|
|
193
|
+
// Prevent cycles
|
|
194
|
+
if (parent.chain.includes(options.grantee_id)) {
|
|
195
|
+
return { success: false, error: 'Delegation would create a cycle' };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Validate capability subset
|
|
199
|
+
const subsetCheck = isCapabilitySubset(grantorCapabilities, options.capabilities);
|
|
200
|
+
if (!subsetCheck.valid) {
|
|
201
|
+
return { success: false, error: subsetCheck.error };
|
|
202
|
+
}
|
|
203
|
+
// Calculate expiration
|
|
204
|
+
const durationSeconds = Math.min(options.duration_seconds ?? DEFAULT_DURATION, MAX_DURATION);
|
|
205
|
+
// If sub-delegation, cannot outlive parent
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
let expiresAt = now + durationSeconds * 1000;
|
|
208
|
+
if (options.parent_delegation_id) {
|
|
209
|
+
const parentDel = await getDelegation(sessions, options.parent_delegation_id);
|
|
210
|
+
if (parentDel.delegation) {
|
|
211
|
+
expiresAt = Math.min(expiresAt, parentDel.delegation.expires_at);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Create the delegation
|
|
215
|
+
const delegationId = generateDelegationId();
|
|
216
|
+
const delegation = {
|
|
217
|
+
delegation_id: delegationId,
|
|
218
|
+
grantor_id: options.grantor_id,
|
|
219
|
+
grantee_id: options.grantee_id,
|
|
220
|
+
app_id: appId,
|
|
221
|
+
capabilities: options.capabilities,
|
|
222
|
+
parent_delegation_id: options.parent_delegation_id,
|
|
223
|
+
chain,
|
|
224
|
+
depth,
|
|
225
|
+
max_depth: maxDepth,
|
|
226
|
+
created_at: now,
|
|
227
|
+
expires_at: expiresAt,
|
|
228
|
+
revoked: false,
|
|
229
|
+
metadata: options.metadata,
|
|
230
|
+
};
|
|
231
|
+
// Store delegation with TTL
|
|
232
|
+
const ttlSeconds = Math.max(1, Math.floor((expiresAt - now) / 1000));
|
|
233
|
+
await sessions.put(`delegation:${delegationId}`, JSON.stringify(delegation), { expirationTtl: ttlSeconds });
|
|
234
|
+
// Update grantor's outbound index
|
|
235
|
+
await updateDelegationIndex(sessions, `agent_delegations_out:${options.grantor_id}`, delegationId, 'add');
|
|
236
|
+
// Update grantee's inbound index
|
|
237
|
+
await updateDelegationIndex(sessions, `agent_delegations_in:${options.grantee_id}`, delegationId, 'add');
|
|
238
|
+
return { success: true, delegation };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
console.error('Failed to create delegation:', error);
|
|
242
|
+
return { success: false, error: 'Internal server error' };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get a delegation by ID
|
|
247
|
+
*/
|
|
248
|
+
export async function getDelegation(sessions, delegationId) {
|
|
249
|
+
try {
|
|
250
|
+
const data = await sessions.get(`delegation:${delegationId}`, 'text');
|
|
251
|
+
if (!data) {
|
|
252
|
+
return { success: false, error: 'Delegation not found or expired' };
|
|
253
|
+
}
|
|
254
|
+
const delegation = JSON.parse(data);
|
|
255
|
+
return { success: true, delegation };
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error('Failed to get delegation:', error);
|
|
259
|
+
return { success: false, error: 'Internal server error' };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* List delegations for an agent (inbound, outbound, or both)
|
|
264
|
+
*/
|
|
265
|
+
export async function listDelegations(sessions, options) {
|
|
266
|
+
try {
|
|
267
|
+
const delegationIds = new Set();
|
|
268
|
+
const direction = options.direction || 'both';
|
|
269
|
+
if (options.agent_id) {
|
|
270
|
+
// Get delegations by agent
|
|
271
|
+
if (direction === 'out' || direction === 'both') {
|
|
272
|
+
const outData = await sessions.get(`agent_delegations_out:${options.agent_id}`, 'text');
|
|
273
|
+
if (outData) {
|
|
274
|
+
for (const id of JSON.parse(outData)) {
|
|
275
|
+
delegationIds.add(id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (direction === 'in' || direction === 'both') {
|
|
280
|
+
const inData = await sessions.get(`agent_delegations_in:${options.agent_id}`, 'text');
|
|
281
|
+
if (inData) {
|
|
282
|
+
for (const id of JSON.parse(inData)) {
|
|
283
|
+
delegationIds.add(id);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (delegationIds.size === 0) {
|
|
289
|
+
return { success: true, delegations: [] };
|
|
290
|
+
}
|
|
291
|
+
// Fetch all delegations
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
const delegations = [];
|
|
294
|
+
for (const id of delegationIds) {
|
|
295
|
+
const result = await getDelegation(sessions, id);
|
|
296
|
+
if (result.success && result.delegation) {
|
|
297
|
+
const d = result.delegation;
|
|
298
|
+
// Filter by app_id if specified
|
|
299
|
+
if (options.app_id && d.app_id !== options.app_id)
|
|
300
|
+
continue;
|
|
301
|
+
// Filter out revoked unless requested
|
|
302
|
+
if (d.revoked && !options.include_revoked)
|
|
303
|
+
continue;
|
|
304
|
+
// Filter out expired unless requested
|
|
305
|
+
if (now > d.expires_at && !options.include_expired)
|
|
306
|
+
continue;
|
|
307
|
+
delegations.push(d);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Sort by created_at descending (newest first)
|
|
311
|
+
delegations.sort((a, b) => b.created_at - a.created_at);
|
|
312
|
+
return { success: true, delegations };
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
console.error('Failed to list delegations:', error);
|
|
316
|
+
return { success: false, error: 'Internal server error' };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Revoke a delegation and cascade to all sub-delegations.
|
|
321
|
+
*
|
|
322
|
+
* When a delegation is revoked, all delegations that have it as a parent
|
|
323
|
+
* (directly or transitively) are also revoked. This is enforced by marking
|
|
324
|
+
* each delegation record as revoked.
|
|
325
|
+
*/
|
|
326
|
+
export async function revokeDelegation(sessions, delegationId, reason) {
|
|
327
|
+
try {
|
|
328
|
+
const result = await getDelegation(sessions, delegationId);
|
|
329
|
+
if (!result.success || !result.delegation) {
|
|
330
|
+
return { success: false, error: 'Delegation not found' };
|
|
331
|
+
}
|
|
332
|
+
const delegation = result.delegation;
|
|
333
|
+
if (delegation.revoked) {
|
|
334
|
+
return { success: true, delegation }; // Already revoked, idempotent
|
|
335
|
+
}
|
|
336
|
+
// Mark as revoked
|
|
337
|
+
delegation.revoked = true;
|
|
338
|
+
delegation.revoked_at = Date.now();
|
|
339
|
+
delegation.revocation_reason = reason;
|
|
340
|
+
// Re-store with remaining TTL (or short TTL if already expired)
|
|
341
|
+
const remainingTtl = Math.max(60, Math.floor((delegation.expires_at - Date.now()) / 1000));
|
|
342
|
+
await sessions.put(`delegation:${delegationId}`, JSON.stringify(delegation), { expirationTtl: remainingTtl });
|
|
343
|
+
// Cascade: find and revoke sub-delegations
|
|
344
|
+
// We search the grantee's outbound delegations for any that reference this as parent
|
|
345
|
+
await cascadeRevocation(sessions, delegationId, reason);
|
|
346
|
+
return { success: true, delegation };
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
console.error('Failed to revoke delegation:', error);
|
|
350
|
+
return { success: false, error: 'Internal server error' };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Recursively revoke all sub-delegations of a given delegation.
|
|
355
|
+
*/
|
|
356
|
+
async function cascadeRevocation(sessions, parentDelegationId, reason) {
|
|
357
|
+
// Get the parent delegation to find the grantee
|
|
358
|
+
const parentResult = await getDelegation(sessions, parentDelegationId);
|
|
359
|
+
if (!parentResult.success || !parentResult.delegation)
|
|
360
|
+
return;
|
|
361
|
+
const granteeId = parentResult.delegation.grantee_id;
|
|
362
|
+
// Get grantee's outbound delegations
|
|
363
|
+
const outData = await sessions.get(`agent_delegations_out:${granteeId}`, 'text');
|
|
364
|
+
if (!outData)
|
|
365
|
+
return;
|
|
366
|
+
const outIds = JSON.parse(outData);
|
|
367
|
+
for (const childId of outIds) {
|
|
368
|
+
const childResult = await getDelegation(sessions, childId);
|
|
369
|
+
if (!childResult.success || !childResult.delegation)
|
|
370
|
+
continue;
|
|
371
|
+
const child = childResult.delegation;
|
|
372
|
+
// Only revoke if this child's parent is our delegation
|
|
373
|
+
if (child.parent_delegation_id === parentDelegationId && !child.revoked) {
|
|
374
|
+
// Revoke this child (which will cascade further)
|
|
375
|
+
await revokeDelegation(sessions, childId, reason || `Parent delegation ${parentDelegationId} revoked`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Verify an entire delegation chain is valid.
|
|
381
|
+
*
|
|
382
|
+
* Walks from the leaf delegation up through parent delegations to the root,
|
|
383
|
+
* verifying each link is:
|
|
384
|
+
* - Not revoked
|
|
385
|
+
* - Not expired
|
|
386
|
+
* - Capabilities are valid subsets
|
|
387
|
+
*
|
|
388
|
+
* Returns the full chain and effective (intersected) capabilities.
|
|
389
|
+
*/
|
|
390
|
+
export async function verifyDelegationChain(agents, sessions, delegationId) {
|
|
391
|
+
try {
|
|
392
|
+
const chain = [];
|
|
393
|
+
let currentId = delegationId;
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
// Walk up the chain
|
|
396
|
+
while (currentId) {
|
|
397
|
+
const result = await getDelegation(sessions, currentId);
|
|
398
|
+
if (!result.success || !result.delegation) {
|
|
399
|
+
return { valid: false, error: `Delegation ${currentId} not found or expired` };
|
|
400
|
+
}
|
|
401
|
+
const del = result.delegation;
|
|
402
|
+
// Check revocation
|
|
403
|
+
if (del.revoked) {
|
|
404
|
+
return {
|
|
405
|
+
valid: false,
|
|
406
|
+
error: `Delegation ${currentId} has been revoked${del.revocation_reason ? ': ' + del.revocation_reason : ''}`
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
// Check expiration
|
|
410
|
+
if (now > del.expires_at) {
|
|
411
|
+
return { valid: false, error: `Delegation ${currentId} has expired` };
|
|
412
|
+
}
|
|
413
|
+
// Verify grantor agent exists
|
|
414
|
+
const grantorResult = await getTAPAgent(agents, del.grantor_id);
|
|
415
|
+
if (!grantorResult.success) {
|
|
416
|
+
return { valid: false, error: `Grantor agent ${del.grantor_id} not found` };
|
|
417
|
+
}
|
|
418
|
+
chain.unshift(del); // Add to front (building root→leaf order)
|
|
419
|
+
currentId = del.parent_delegation_id;
|
|
420
|
+
}
|
|
421
|
+
if (chain.length === 0) {
|
|
422
|
+
return { valid: false, error: 'Empty delegation chain' };
|
|
423
|
+
}
|
|
424
|
+
// Verify capability narrowing at each step
|
|
425
|
+
// The root delegation's capabilities must be a subset of the root grantor's capabilities
|
|
426
|
+
const rootDel = chain[0];
|
|
427
|
+
const rootGrantorResult = await getTAPAgent(agents, rootDel.grantor_id);
|
|
428
|
+
if (!rootGrantorResult.success || !rootGrantorResult.agent) {
|
|
429
|
+
return { valid: false, error: 'Root grantor agent not found' };
|
|
430
|
+
}
|
|
431
|
+
const rootCheck = isCapabilitySubset(rootGrantorResult.agent.capabilities || [], rootDel.capabilities);
|
|
432
|
+
if (!rootCheck.valid) {
|
|
433
|
+
return { valid: false, error: `Root delegation invalid: ${rootCheck.error}` };
|
|
434
|
+
}
|
|
435
|
+
// Verify each subsequent link narrows from its parent
|
|
436
|
+
for (let i = 1; i < chain.length; i++) {
|
|
437
|
+
const parentCaps = chain[i - 1].capabilities;
|
|
438
|
+
const childCaps = chain[i].capabilities;
|
|
439
|
+
const check = isCapabilitySubset(parentCaps, childCaps);
|
|
440
|
+
if (!check.valid) {
|
|
441
|
+
return {
|
|
442
|
+
valid: false,
|
|
443
|
+
error: `Chain link ${i} invalid: ${check.error}`
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Effective capabilities = leaf delegation's capabilities
|
|
448
|
+
// (since each step only narrows, the leaf is the most restricted)
|
|
449
|
+
const effectiveCapabilities = chain[chain.length - 1].capabilities;
|
|
450
|
+
return {
|
|
451
|
+
valid: true,
|
|
452
|
+
chain,
|
|
453
|
+
effective_capabilities: effectiveCapabilities,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
console.error('Failed to verify delegation chain:', error);
|
|
458
|
+
return { valid: false, error: 'Internal server error' };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// ============ UTILITY FUNCTIONS ============
|
|
462
|
+
/**
|
|
463
|
+
* Update an agent's delegation index (inbound or outbound)
|
|
464
|
+
*/
|
|
465
|
+
async function updateDelegationIndex(sessions, key, delegationId, operation) {
|
|
466
|
+
try {
|
|
467
|
+
const data = await sessions.get(key, 'text');
|
|
468
|
+
let ids = data ? JSON.parse(data) : [];
|
|
469
|
+
if (operation === 'add' && !ids.includes(delegationId)) {
|
|
470
|
+
ids.push(delegationId);
|
|
471
|
+
}
|
|
472
|
+
else if (operation === 'remove') {
|
|
473
|
+
ids = ids.filter(id => id !== delegationId);
|
|
474
|
+
}
|
|
475
|
+
// No TTL on indexes — they reference delegations which have their own TTL
|
|
476
|
+
await sessions.put(key, JSON.stringify(ids));
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
console.error('Failed to update delegation index:', error);
|
|
480
|
+
// Fail silently — index updates are not critical
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
export default {
|
|
484
|
+
createDelegation,
|
|
485
|
+
getDelegation,
|
|
486
|
+
listDelegations,
|
|
487
|
+
revokeDelegation,
|
|
488
|
+
verifyDelegationChain,
|
|
489
|
+
isCapabilitySubset,
|
|
490
|
+
};
|
package/dist/tap-jwks.d.ts
CHANGED
|
@@ -39,7 +39,8 @@ export declare function jwkToPem(jwk: JWK): Promise<string>;
|
|
|
39
39
|
export declare function algToJWKAlg(algorithm: string): string;
|
|
40
40
|
/**
|
|
41
41
|
* GET /.well-known/jwks
|
|
42
|
-
* Returns JWK Set for app's TAP-enabled agents
|
|
42
|
+
* Returns JWK Set for app's TAP-enabled agents.
|
|
43
|
+
* Also includes BOTCHA's own signing public key when JWT_SIGNING_KEY is configured.
|
|
43
44
|
*/
|
|
44
45
|
export declare function jwksRoute(c: Context): Promise<Response>;
|
|
45
46
|
/**
|
package/dist/tap-jwks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-jwks.d.ts","sourceRoot":"","sources":["../src/tap-jwks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"tap-jwks.d.ts","sourceRoot":"","sources":["../src/tap-jwks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAOpC,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IAEZ,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IAEX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IAEX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAID;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,GAAG,CAAC,CAed;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAKxD;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWrD;AAiFD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoG7D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2D/D;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuBjE;;;;;;;;;AAED,wBAOE"}
|