@vurb/core 3.7.13 → 3.8.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 +63 -0
- package/dist/cli/commands/deploy.js +1 -1
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/client/VurbClient.js +2 -2
- package/dist/client/VurbClient.js.map +1 -1
- package/dist/core/StandardSchema.js +1 -1
- package/dist/core/StandardSchema.js.map +1 -1
- package/dist/core/builder/BuildPipeline.js +3 -3
- package/dist/core/builder/BuildPipeline.js.map +1 -1
- package/dist/core/builder/FluentToolBuilder.d.ts +1 -1
- package/dist/core/builder/FluentToolBuilder.js +3 -3
- package/dist/core/builder/FluentToolBuilder.js.map +1 -1
- package/dist/core/builder/GroupedToolBuilder.js +2 -2
- package/dist/core/builder/GroupedToolBuilder.js.map +1 -1
- package/dist/core/execution/ExecutionPipeline.js +3 -3
- package/dist/core/execution/ExecutionPipeline.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/initVurb.js +2 -2
- package/dist/core/initVurb.js.map +1 -1
- package/dist/core/middleware/AuditTrail.js +1 -1
- package/dist/core/middleware/AuditTrail.js.map +1 -1
- package/dist/core/middleware/ContextDerivation.js +1 -1
- package/dist/core/middleware/ContextDerivation.js.map +1 -1
- package/dist/core/middleware/InputFirewall.js +1 -1
- package/dist/core/middleware/InputFirewall.js.map +1 -1
- package/dist/core/middleware/RateLimiter.js +1 -1
- package/dist/core/middleware/RateLimiter.js.map +1 -1
- package/dist/core/registry/ToolRegistry.js +1 -1
- package/dist/core/registry/ToolRegistry.js.map +1 -1
- package/dist/core/response.d.ts +53 -2
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +42 -1
- package/dist/core/response.js.map +1 -1
- package/dist/domain/Group.js +2 -2
- package/dist/domain/Group.js.map +1 -1
- package/dist/exposition/ExpositionCompiler.d.ts +1 -1
- package/dist/exposition/ExpositionCompiler.js +2 -2
- package/dist/exposition/ExpositionCompiler.js.map +1 -1
- package/dist/fsm/StateMachineGate.js +3 -3
- package/dist/fsm/StateMachineGate.js.map +1 -1
- package/dist/handoff/DelegationToken.d.ts +58 -0
- package/dist/handoff/DelegationToken.d.ts.map +1 -0
- package/dist/handoff/DelegationToken.js +186 -0
- package/dist/handoff/DelegationToken.js.map +1 -0
- package/dist/handoff/HandoffStateStore.d.ts +52 -0
- package/dist/handoff/HandoffStateStore.d.ts.map +1 -0
- package/dist/handoff/HandoffStateStore.js +73 -0
- package/dist/handoff/HandoffStateStore.js.map +1 -0
- package/dist/handoff/index.d.ts +32 -0
- package/dist/handoff/index.d.ts.map +1 -0
- package/dist/handoff/index.js +19 -0
- package/dist/handoff/index.js.map +1 -0
- package/dist/handoff/middleware.d.ts +36 -0
- package/dist/handoff/middleware.d.ts.map +1 -0
- package/dist/handoff/middleware.js +118 -0
- package/dist/handoff/middleware.js.map +1 -0
- package/dist/handoff/types.d.ts +51 -0
- package/dist/handoff/types.d.ts.map +1 -0
- package/dist/handoff/types.js +9 -0
- package/dist/handoff/types.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/introspection/CryptoAttestation.js +1 -1
- package/dist/introspection/CryptoAttestation.js.map +1 -1
- package/dist/introspection/GovernanceObserver.js +1 -1
- package/dist/introspection/GovernanceObserver.js.map +1 -1
- package/dist/introspection/TokenEconomics.js +1 -1
- package/dist/introspection/TokenEconomics.js.map +1 -1
- package/dist/introspection/ToolContract.js +2 -2
- package/dist/introspection/ToolContract.js.map +1 -1
- package/dist/observability/TelemetryBus.js +3 -3
- package/dist/observability/TelemetryBus.js.map +1 -1
- package/dist/presenter/JudgeChain.js +2 -2
- package/dist/presenter/JudgeChain.js.map +1 -1
- package/dist/presenter/PromptFirewall.js +2 -2
- package/dist/presenter/PromptFirewall.js.map +1 -1
- package/dist/presenter/RedactEngine.js +3 -3
- package/dist/presenter/RedactEngine.js.map +1 -1
- package/dist/prompt/FluentPromptBuilder.js +1 -1
- package/dist/prompt/FluentPromptBuilder.js.map +1 -1
- package/dist/resource/ResourceRegistry.d.ts +1 -1
- package/dist/resource/ResourceRegistry.js +1 -1
- package/dist/sandbox/SandboxEngine.d.ts +1 -1
- package/dist/sandbox/SandboxEngine.js +6 -6
- package/dist/sandbox/SandboxEngine.js.map +1 -1
- package/dist/sandbox/SandboxGuard.js +5 -5
- package/dist/sandbox/SandboxGuard.js.map +1 -1
- package/dist/server/DevServer.js +2 -2
- package/dist/server/DevServer.js.map +1 -1
- package/dist/server/ServerAttachment.d.ts +39 -1
- package/dist/server/ServerAttachment.d.ts.map +1 -1
- package/dist/server/ServerAttachment.js +78 -26
- package/dist/server/ServerAttachment.js.map +1 -1
- package/dist/server/ServerResolver.d.ts.map +1 -1
- package/dist/server/ServerResolver.js +15 -2
- package/dist/server/ServerResolver.js.map +1 -1
- package/dist/server/autoDiscover.js +2 -2
- package/dist/server/autoDiscover.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/startServer.js +2 -2
- package/dist/server/startServer.js.map +1 -1
- package/dist/state-sync/StateSyncBuilder.d.ts +1 -1
- package/dist/state-sync/StateSyncBuilder.js +2 -2
- package/dist/state-sync/StateSyncBuilder.js.map +1 -1
- package/package.json +151 -151
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HandoffStateStore } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* JWT-inspired claims embedded in the delegation token.
|
|
4
|
+
*
|
|
5
|
+
* @property iss - Issuer URL (usually the gateway origin)
|
|
6
|
+
* @property sub - Scope identifier (e.g. `'finance_scope'`)
|
|
7
|
+
* @property iat - Issued-at Unix timestamp (seconds)
|
|
8
|
+
* @property exp - Expiry Unix timestamp (seconds)
|
|
9
|
+
* @property tid - Unique transaction ID for distributed tracing
|
|
10
|
+
* @property state - Inline carry-over state (< 2 KB)
|
|
11
|
+
* @property state_id - Claim-Check reference when state was externalized
|
|
12
|
+
* @property traceparent - W3C Trace Context header for distributed tracing
|
|
13
|
+
*/
|
|
14
|
+
export interface DelegationClaims {
|
|
15
|
+
iss: string;
|
|
16
|
+
sub: string;
|
|
17
|
+
iat: number;
|
|
18
|
+
exp: number;
|
|
19
|
+
tid: string;
|
|
20
|
+
state?: Record<string, unknown>;
|
|
21
|
+
state_id?: string;
|
|
22
|
+
traceparent?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Thrown by {@link verifyDelegationToken} on invalid or expired tokens. */
|
|
25
|
+
export declare class HandoffAuthError extends Error {
|
|
26
|
+
readonly code: 'MISSING_DELEGATION_TOKEN' | 'INVALID_DELEGATION_TOKEN' | 'EXPIRED_DELEGATION_TOKEN' | 'INVALID_SIGNATURE';
|
|
27
|
+
constructor(code: 'MISSING_DELEGATION_TOKEN' | 'INVALID_DELEGATION_TOKEN' | 'EXPIRED_DELEGATION_TOKEN' | 'INVALID_SIGNATURE', message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Mint a signed HMAC-SHA256 delegation token.
|
|
31
|
+
*
|
|
32
|
+
* If `carryOverState` exceeds 2 KB, the state is persisted in `store`
|
|
33
|
+
* and only the resulting `state_id` UUID is embedded in the token.
|
|
34
|
+
*
|
|
35
|
+
* @param scope - Scope identifier (e.g. `'finance'`)
|
|
36
|
+
* @param ttlSeconds - Token lifetime in seconds
|
|
37
|
+
* @param secret - HMAC signing secret (minimum 32 chars recommended)
|
|
38
|
+
* @param issuer - Issuer identifier (gateway URL or name)
|
|
39
|
+
* @param carryOverState - Optional semantic context to carry to the upstream
|
|
40
|
+
* @param store - Required when `carryOverState` may exceed 2 KB
|
|
41
|
+
* @param traceparent - W3C traceparent for distributed tracing
|
|
42
|
+
* @returns Signed token string: `base64url(claims).base64url(sig)`
|
|
43
|
+
*/
|
|
44
|
+
export declare function mintDelegationToken(scope: string, ttlSeconds: number, secret: string, issuer?: string, carryOverState?: Record<string, unknown>, store?: HandoffStateStore, traceparent?: string): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Verify a delegation token and return its claims.
|
|
47
|
+
*
|
|
48
|
+
* - Validates HMAC signature (constant-time)
|
|
49
|
+
* - Validates expiry (`exp`)
|
|
50
|
+
* - Hydrates `carryOverState` from store when `state_id` is present (Claim-Check retrieval)
|
|
51
|
+
*
|
|
52
|
+
* @param raw - Raw token produced by {@link mintDelegationToken}
|
|
53
|
+
* @param secret - HMAC signing secret (must match the mint secret)
|
|
54
|
+
* @param store - Required if the token may contain a `state_id`
|
|
55
|
+
* @throws {@link HandoffAuthError} on any validation failure
|
|
56
|
+
*/
|
|
57
|
+
export declare function verifyDelegationToken(raw: string, secret: string, store?: HandoffStateStore): Promise<DelegationClaims>;
|
|
58
|
+
//# sourceMappingURL=DelegationToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DelegationToken.d.ts","sourceRoot":"","sources":["../../src/handoff/DelegationToken.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgBpD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD,4EAA4E;AAC5E,qBAAa,gBAAiB,SAAQ,KAAK;aAEnB,IAAI,EACd,0BAA0B,GAC1B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB;gBAJT,IAAI,EACd,0BAA0B,GAC1B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB,EACzB,OAAO,EAAE,MAAM;CAKtB;AA4CD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACrC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,SAAiB,EACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,CAAC,EAAE,iBAAiB,EACzB,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,CAiCjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACvC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CA0E3B"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Handoff Protocol — Delegation Token
|
|
3
|
+
*
|
|
4
|
+
* HMAC-SHA256 token for zero-trust delegation between gateway and
|
|
5
|
+
* upstream micro-servers. Uses Node.js native `crypto` — no external deps.
|
|
6
|
+
*
|
|
7
|
+
* Claim-Check Pattern: if `carryOverState` exceeds 2 KB, the state is
|
|
8
|
+
* persisted in a {@link HandoffStateStore} and only the `state_id` UUID
|
|
9
|
+
* is embedded in the token, keeping HTTP headers within safe limits.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { createHmac, randomUUID, timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Constants
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/** Maximum bytes allowed inline in the token before Claim-Check kicks in. */
|
|
18
|
+
const CLAIM_CHECK_THRESHOLD_BYTES = 2048;
|
|
19
|
+
/** Separator between the base64url token body and the HMAC signature. */
|
|
20
|
+
const TOKEN_SEP = '.';
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Errors
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/** Thrown by {@link verifyDelegationToken} on invalid or expired tokens. */
|
|
25
|
+
export class HandoffAuthError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(code, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = 'HandoffAuthError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Internal helpers
|
|
35
|
+
// ============================================================================
|
|
36
|
+
function encodePayload(claims) {
|
|
37
|
+
return Buffer.from(JSON.stringify(claims), 'utf8').toString('base64url');
|
|
38
|
+
}
|
|
39
|
+
function decodePayload(encoded) {
|
|
40
|
+
return JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
41
|
+
}
|
|
42
|
+
function sign(payload, secret) {
|
|
43
|
+
return createHmac('sha256', secret).update(payload).digest('base64url');
|
|
44
|
+
}
|
|
45
|
+
function byteLength(obj) {
|
|
46
|
+
return Buffer.byteLength(JSON.stringify(obj), 'utf8');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Constant-time string comparison to prevent timing side-channel attacks.
|
|
50
|
+
*
|
|
51
|
+
* Uses Node.js native `crypto.timingSafeEqual` instead of a manual loop.
|
|
52
|
+
* V8's JIT can optimise a manual loop and leak the signature length via timing
|
|
53
|
+
* when the two strings have different lengths.
|
|
54
|
+
*
|
|
55
|
+
* `crypto.timingSafeEqual` requires equal-length Buffers, so we check lengths
|
|
56
|
+
* first (leaking that the lengths differ, but NOT the expected length).
|
|
57
|
+
*/
|
|
58
|
+
function timingSafeEqual(a, b) {
|
|
59
|
+
const bufA = Buffer.from(a, 'utf8');
|
|
60
|
+
const bufB = Buffer.from(b, 'utf8');
|
|
61
|
+
// Length mismatch exits early — this only leaks "they differ", not which is longer.
|
|
62
|
+
if (bufA.length !== bufB.length)
|
|
63
|
+
return false;
|
|
64
|
+
return cryptoTimingSafeEqual(bufA, bufB);
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Public API
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Mint a signed HMAC-SHA256 delegation token.
|
|
71
|
+
*
|
|
72
|
+
* If `carryOverState` exceeds 2 KB, the state is persisted in `store`
|
|
73
|
+
* and only the resulting `state_id` UUID is embedded in the token.
|
|
74
|
+
*
|
|
75
|
+
* @param scope - Scope identifier (e.g. `'finance'`)
|
|
76
|
+
* @param ttlSeconds - Token lifetime in seconds
|
|
77
|
+
* @param secret - HMAC signing secret (minimum 32 chars recommended)
|
|
78
|
+
* @param issuer - Issuer identifier (gateway URL or name)
|
|
79
|
+
* @param carryOverState - Optional semantic context to carry to the upstream
|
|
80
|
+
* @param store - Required when `carryOverState` may exceed 2 KB
|
|
81
|
+
* @param traceparent - W3C traceparent for distributed tracing
|
|
82
|
+
* @returns Signed token string: `base64url(claims).base64url(sig)`
|
|
83
|
+
*/
|
|
84
|
+
export async function mintDelegationToken(scope, ttlSeconds, secret, issuer = 'vurb-gateway', carryOverState, store, traceparent) {
|
|
85
|
+
const now = Math.floor(Date.now() / 1000);
|
|
86
|
+
const claims = {
|
|
87
|
+
iss: issuer,
|
|
88
|
+
sub: scope,
|
|
89
|
+
iat: now,
|
|
90
|
+
exp: now + ttlSeconds,
|
|
91
|
+
tid: randomUUID(),
|
|
92
|
+
};
|
|
93
|
+
if (traceparent) {
|
|
94
|
+
claims.traceparent = traceparent;
|
|
95
|
+
}
|
|
96
|
+
if (carryOverState !== undefined) {
|
|
97
|
+
if (byteLength(carryOverState) > CLAIM_CHECK_THRESHOLD_BYTES) {
|
|
98
|
+
if (!store) {
|
|
99
|
+
throw new Error('[vurb/core] carryOverState exceeds 2 KB but no HandoffStateStore was provided. ' +
|
|
100
|
+
'Pass a store to SwarmGatewayConfig.stateStore.');
|
|
101
|
+
}
|
|
102
|
+
const stateId = randomUUID();
|
|
103
|
+
await store.set(stateId, carryOverState, ttlSeconds + 60);
|
|
104
|
+
claims.state_id = stateId;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
claims.state = carryOverState;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const payload = encodePayload(claims);
|
|
111
|
+
const sig = sign(payload, secret);
|
|
112
|
+
return `${payload}${TOKEN_SEP}${sig}`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Verify a delegation token and return its claims.
|
|
116
|
+
*
|
|
117
|
+
* - Validates HMAC signature (constant-time)
|
|
118
|
+
* - Validates expiry (`exp`)
|
|
119
|
+
* - Hydrates `carryOverState` from store when `state_id` is present (Claim-Check retrieval)
|
|
120
|
+
*
|
|
121
|
+
* @param raw - Raw token produced by {@link mintDelegationToken}
|
|
122
|
+
* @param secret - HMAC signing secret (must match the mint secret)
|
|
123
|
+
* @param store - Required if the token may contain a `state_id`
|
|
124
|
+
* @throws {@link HandoffAuthError} on any validation failure
|
|
125
|
+
*/
|
|
126
|
+
export async function verifyDelegationToken(raw, secret, store) {
|
|
127
|
+
const sep = raw.lastIndexOf(TOKEN_SEP);
|
|
128
|
+
if (sep === -1) {
|
|
129
|
+
throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Malformed token: missing signature separator.');
|
|
130
|
+
}
|
|
131
|
+
const payload = raw.slice(0, sep);
|
|
132
|
+
const providedSig = raw.slice(sep + 1);
|
|
133
|
+
const expectedSig = sign(payload, secret);
|
|
134
|
+
if (!timingSafeEqual(providedSig, expectedSig)) {
|
|
135
|
+
throw new HandoffAuthError('INVALID_SIGNATURE', 'Delegation token signature is invalid.');
|
|
136
|
+
}
|
|
137
|
+
let claims;
|
|
138
|
+
try {
|
|
139
|
+
claims = decodePayload(payload);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Delegation token payload could not be decoded.');
|
|
143
|
+
}
|
|
144
|
+
const now = Math.floor(Date.now() / 1000);
|
|
145
|
+
if (claims.exp < now) {
|
|
146
|
+
throw new HandoffAuthError('EXPIRED_DELEGATION_TOKEN', `Delegation token expired at ${new Date(claims.exp * 1000).toISOString()}.`);
|
|
147
|
+
}
|
|
148
|
+
// Claim-Check: hydrate state from store
|
|
149
|
+
if (claims.state_id) {
|
|
150
|
+
if (!store) {
|
|
151
|
+
throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Token contains state_id but no HandoffStateStore was provided.');
|
|
152
|
+
}
|
|
153
|
+
// Use the two-phase get+delete pattern only when both optional methods exist
|
|
154
|
+
// on the store. Fall back to the atomic `getAndDelete` for stores that implement
|
|
155
|
+
// the minimum interface (only `set` + `getAndDelete`).
|
|
156
|
+
// Without this check, calling `store.get()` on a minimal store would throw
|
|
157
|
+
// "store.get is not a function" at runtime — a silent crash invisible in TS.
|
|
158
|
+
let state;
|
|
159
|
+
if (typeof store.get === 'function' && typeof store.delete === 'function') {
|
|
160
|
+
// Two-phase: read first, delete only after data is safely in claims.state
|
|
161
|
+
state = await store.get(claims.state_id);
|
|
162
|
+
if (state !== undefined) {
|
|
163
|
+
claims.state = state;
|
|
164
|
+
await store.delete(claims.state_id);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Atomic fallback: getAndDelete cannot lose data between read and delete
|
|
169
|
+
state = await store.getAndDelete(claims.state_id);
|
|
170
|
+
if (state !== undefined) {
|
|
171
|
+
claims.state = state;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// If state_id was present but the store returned nothing, the state has
|
|
175
|
+
// either expired (TTL elapsed) or was already consumed (replay attempt).
|
|
176
|
+
// Silently continuing would give the upstream an empty context — the correct
|
|
177
|
+
// behaviour is to reject the token so the caller gets a clear signal.
|
|
178
|
+
if (state === undefined) {
|
|
179
|
+
throw new HandoffAuthError('EXPIRED_DELEGATION_TOKEN', 'Delegation token carry-over state has expired or was already consumed. ' +
|
|
180
|
+
'Initiate a new handoff.');
|
|
181
|
+
}
|
|
182
|
+
delete claims.state_id;
|
|
183
|
+
}
|
|
184
|
+
return claims;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=DelegationToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DelegationToken.js","sourceRoot":"","sources":["../../src/handoff/DelegationToken.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Handoff Protocol (FHP) — HandoffStateStore
|
|
3
|
+
*
|
|
4
|
+
* Interface e implementação padrão para o padrão Claim-Check.
|
|
5
|
+
* Extender com adaptadores de plataforma (ex: `@vurb/cloudflare`).
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { HandoffStateStore } from './index.js';
|
|
10
|
+
/**
|
|
11
|
+
* In-memory implementation of {@link HandoffStateStore}.
|
|
12
|
+
*
|
|
13
|
+
* **Default for Node.js stateful processes.**
|
|
14
|
+
* Not suitable for edge/serverless where each request runs in a fresh isolate.
|
|
15
|
+
* For those environments, implement `HandoffStateStore` using your platform's
|
|
16
|
+
* KV store (e.g. Cloudflare KV via `@vurb/cloudflare`).
|
|
17
|
+
*
|
|
18
|
+
* TTL is enforced lazily on `getAndDelete` — no background timers or
|
|
19
|
+
* memory leaks from expired entries that are never retrieved.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { SwarmGateway } from '@vurb/swarm';
|
|
24
|
+
* import { InMemoryHandoffStateStore } from '@vurb/core';
|
|
25
|
+
*
|
|
26
|
+
* const gateway = new SwarmGateway({
|
|
27
|
+
* registry: { finance: 'http://finance-agent:8081' },
|
|
28
|
+
* delegationSecret: process.env.VURB_DELEGATION_SECRET!,
|
|
29
|
+
* stateStore: new InMemoryHandoffStateStore(),
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare class InMemoryHandoffStateStore implements HandoffStateStore {
|
|
34
|
+
private readonly _store;
|
|
35
|
+
set(stateId: string, state: Record<string, unknown>, ttlSeconds: number): Promise<void>;
|
|
36
|
+
getAndDelete(stateId: string): Promise<Record<string, unknown> | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Read without deleting (non-atomic, use for two-phase pattern).
|
|
39
|
+
* Falls back gracefully if the stateId has expired.
|
|
40
|
+
*
|
|
41
|
+
* Expired entries are pruned on access (lazy TTL cleanup),
|
|
42
|
+
* consistent with `getAndDelete`. Without this, expired entries would accumulate
|
|
43
|
+
* in the Map indefinitely if `get()` was called but the data was already expired
|
|
44
|
+
* (e.g. a slow upstream that delayed token verification past the TTL window).
|
|
45
|
+
*/
|
|
46
|
+
get(stateId: string): Promise<Record<string, unknown> | undefined>;
|
|
47
|
+
/** delete without reading. No-op if the key does not exist. */
|
|
48
|
+
delete(stateId: string): Promise<void>;
|
|
49
|
+
/** Current number of entries in the store (useful for testing). */
|
|
50
|
+
get size(): number;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=HandoffStateStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HandoffStateStore.d.ts","sourceRoot":"","sources":["../../src/handoff/HandoffStateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAElD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvF,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAejF;;;;;;;;OAQG;IACG,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAUxE,+DAA+D;IACzD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C,mEAAmE;IACnE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACJ"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of {@link HandoffStateStore}.
|
|
3
|
+
*
|
|
4
|
+
* **Default for Node.js stateful processes.**
|
|
5
|
+
* Not suitable for edge/serverless where each request runs in a fresh isolate.
|
|
6
|
+
* For those environments, implement `HandoffStateStore` using your platform's
|
|
7
|
+
* KV store (e.g. Cloudflare KV via `@vurb/cloudflare`).
|
|
8
|
+
*
|
|
9
|
+
* TTL is enforced lazily on `getAndDelete` — no background timers or
|
|
10
|
+
* memory leaks from expired entries that are never retrieved.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { SwarmGateway } from '@vurb/swarm';
|
|
15
|
+
* import { InMemoryHandoffStateStore } from '@vurb/core';
|
|
16
|
+
*
|
|
17
|
+
* const gateway = new SwarmGateway({
|
|
18
|
+
* registry: { finance: 'http://finance-agent:8081' },
|
|
19
|
+
* delegationSecret: process.env.VURB_DELEGATION_SECRET!,
|
|
20
|
+
* stateStore: new InMemoryHandoffStateStore(),
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class InMemoryHandoffStateStore {
|
|
25
|
+
_store = new Map();
|
|
26
|
+
async set(stateId, state, ttlSeconds) {
|
|
27
|
+
this._store.set(stateId, {
|
|
28
|
+
state,
|
|
29
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async getAndDelete(stateId) {
|
|
33
|
+
const entry = this._store.get(stateId);
|
|
34
|
+
if (!entry)
|
|
35
|
+
return undefined;
|
|
36
|
+
// Check TTL BEFORE deleting: if an entry is expired it was never valid for use,
|
|
37
|
+
// so we clean it up and return undefined. Lazy cleanup — no background timer needed.
|
|
38
|
+
if (Date.now() > entry.expiresAt) {
|
|
39
|
+
this._store.delete(stateId); // lazy TTL cleanup — no background timer
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
this._store.delete(stateId);
|
|
43
|
+
return entry.state;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read without deleting (non-atomic, use for two-phase pattern).
|
|
47
|
+
* Falls back gracefully if the stateId has expired.
|
|
48
|
+
*
|
|
49
|
+
* Expired entries are pruned on access (lazy TTL cleanup),
|
|
50
|
+
* consistent with `getAndDelete`. Without this, expired entries would accumulate
|
|
51
|
+
* in the Map indefinitely if `get()` was called but the data was already expired
|
|
52
|
+
* (e.g. a slow upstream that delayed token verification past the TTL window).
|
|
53
|
+
*/
|
|
54
|
+
async get(stateId) {
|
|
55
|
+
const entry = this._store.get(stateId);
|
|
56
|
+
if (!entry)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (Date.now() > entry.expiresAt) {
|
|
59
|
+
this._store.delete(stateId); // lazy TTL cleanup — consistent with getAndDelete
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return entry.state;
|
|
63
|
+
}
|
|
64
|
+
/** delete without reading. No-op if the key does not exist. */
|
|
65
|
+
async delete(stateId) {
|
|
66
|
+
this._store.delete(stateId);
|
|
67
|
+
}
|
|
68
|
+
/** Current number of entries in the store (useful for testing). */
|
|
69
|
+
get size() {
|
|
70
|
+
return this._store.size;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=HandoffStateStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HandoffStateStore.js","sourceRoot":"","sources":["../../src/handoff/HandoffStateStore.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,yBAAyB;IACjB,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAExD,KAAK,CAAC,GAAG,CAAC,OAAe,EAAE,KAA8B,EAAE,UAAkB;QACzE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE;YACrB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;SAC5C,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAAe;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,gFAAgF;QAChF,qFAAqF;QACrF,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,yCAAyC;YACtE,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,KAAK,CAAC;IACvB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,CAAC,OAAe;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,kDAAkD;YAC/E,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACvB,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,MAAM,CAAC,OAAe;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,mEAAmE;IACnE,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC5B,CAAC;CACJ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Handoff Protocol (FHP) — Core Barrel
|
|
3
|
+
*
|
|
4
|
+
* Exporta todas as primitivas FHP que pertencem ao `@vurb/core`:
|
|
5
|
+
*
|
|
6
|
+
* - **Tipos de dados**: `HandoffPayload`, `HandoffResponse`, `HandoffStateStore`
|
|
7
|
+
* - **Guards e factories**: `isHandoffResponse`, `handoff`
|
|
8
|
+
* - **Crypto**: `mintDelegationToken`, `verifyDelegationToken`, `DelegationClaims`, `HandoffAuthError`
|
|
9
|
+
* - **Store**: `InMemoryHandoffStateStore`
|
|
10
|
+
* - **Middleware zero-trust**: `requireGatewayClearance`, `GatewayClearanceContext`
|
|
11
|
+
*/
|
|
12
|
+
export type { HandoffPayload, HandoffResponse } from '../core/response.js';
|
|
13
|
+
export { isHandoffResponse, handoff } from '../core/response.js';
|
|
14
|
+
/** @see {@link HandoffStateStore} */
|
|
15
|
+
export type {
|
|
16
|
+
/**
|
|
17
|
+
* Pluggable persistence interface for the Claim-Check pattern.
|
|
18
|
+
*
|
|
19
|
+
* When `carryOverState` exceeds 2 KB, the SwarmGateway persists
|
|
20
|
+
* it under a UUID key and embeds only the key inside the HMAC token,
|
|
21
|
+
* keeping HTTP headers within safe limits (< 8 KB nginx/ALB limit).
|
|
22
|
+
*
|
|
23
|
+
* Default implementation: {@link InMemoryHandoffStateStore}.
|
|
24
|
+
* Platform adapters: implement this interface (e.g. `@vurb/cloudflare`).
|
|
25
|
+
*/
|
|
26
|
+
HandoffStateStore, } from './types.js';
|
|
27
|
+
export { InMemoryHandoffStateStore } from './HandoffStateStore.js';
|
|
28
|
+
export { mintDelegationToken, verifyDelegationToken, HandoffAuthError, } from './DelegationToken.js';
|
|
29
|
+
export type { DelegationClaims } from './DelegationToken.js';
|
|
30
|
+
export { requireGatewayClearance } from './middleware.js';
|
|
31
|
+
export type { GatewayClearanceContext } from './middleware.js';
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handoff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGjE,qCAAqC;AACrC,YAAY;AACR;;;;;;;;;GASG;AACH,iBAAiB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACnB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Handoff Protocol (FHP) — Core Barrel
|
|
3
|
+
*
|
|
4
|
+
* Exporta todas as primitivas FHP que pertencem ao `@vurb/core`:
|
|
5
|
+
*
|
|
6
|
+
* - **Tipos de dados**: `HandoffPayload`, `HandoffResponse`, `HandoffStateStore`
|
|
7
|
+
* - **Guards e factories**: `isHandoffResponse`, `handoff`
|
|
8
|
+
* - **Crypto**: `mintDelegationToken`, `verifyDelegationToken`, `DelegationClaims`, `HandoffAuthError`
|
|
9
|
+
* - **Store**: `InMemoryHandoffStateStore`
|
|
10
|
+
* - **Middleware zero-trust**: `requireGatewayClearance`, `GatewayClearanceContext`
|
|
11
|
+
*/
|
|
12
|
+
export { isHandoffResponse, handoff } from '../core/response.js';
|
|
13
|
+
// ── Store padrão ─────────────────────────────────────────────────────────────
|
|
14
|
+
export { InMemoryHandoffStateStore } from './HandoffStateStore.js';
|
|
15
|
+
// ── Delegation Token (mint/verify, HMAC-SHA256 puro) ─────────────────────────
|
|
16
|
+
export { mintDelegationToken, verifyDelegationToken, HandoffAuthError, } from './DelegationToken.js';
|
|
17
|
+
// ── Middleware zero-trust (para micro-servidores upstream) ───────────────────
|
|
18
|
+
export { requireGatewayClearance } from './middleware.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handoff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAkBjE,gFAAgF;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,gFAAgF;AAChF,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACnB,MAAM,sBAAsB,CAAC;AAG9B,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HandoffStateStore } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Properties injected into the handler context by `requireGatewayClearance`.
|
|
4
|
+
*
|
|
5
|
+
* Merge with your own context type via intersection:
|
|
6
|
+
* ```typescript
|
|
7
|
+
* type AppContext = BaseContext & GatewayClearanceContext;
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export interface GatewayClearanceContext {
|
|
11
|
+
/** Carry-over state from the gateway (inline or hydrated via Claim-Check). */
|
|
12
|
+
handoffState: Record<string, unknown>;
|
|
13
|
+
/** Delegation scope — the `sub` claim of the token (e.g. `'finance'`). */
|
|
14
|
+
handoffScope: string;
|
|
15
|
+
/** Transaction ID for distributed tracing (correlates with gateway spans). */
|
|
16
|
+
handoffTid: string;
|
|
17
|
+
/** W3C traceparent header, if present in the token. */
|
|
18
|
+
handoffTraceparent?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Zero-trust middleware que valida o token de delegação HMAC.
|
|
22
|
+
*
|
|
23
|
+
* Lê o header `x-vurb-delegation` do `extra` do pedido MCP,
|
|
24
|
+
* verifica a assinatura HMAC-SHA256 e o TTL, e injeta
|
|
25
|
+
* {@link GatewayClearanceContext} no contexto do handler.
|
|
26
|
+
*
|
|
27
|
+
* @param secret - Segredo HMAC partilhado entre o gateway e o micro-servidor.
|
|
28
|
+
* Deve corresponder a `SwarmGatewayConfig.delegationSecret`.
|
|
29
|
+
* @param store - Store opcional para hidratação do Claim-Check (necessário quando
|
|
30
|
+
* `carryOverState` pode exceder 2 KB).
|
|
31
|
+
*
|
|
32
|
+
* @throws {@link HandoffAuthError} — traduzido para erro `FORBIDDEN` pelo framework
|
|
33
|
+
* se não for capturado pelo handler.
|
|
34
|
+
*/
|
|
35
|
+
export declare function requireGatewayClearance(secret: string, store?: HandoffStateStore): (ctx: unknown) => Promise<GatewayClearanceContext>;
|
|
36
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/handoff/middleware.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMpD;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACpC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,iBAAiB,IAGX,KAAK,OAAO,KAAG,OAAO,CAAC,uBAAuB,CAAC,CA6BhE"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Handoff Protocol — Zero-Trust Middleware
|
|
3
|
+
*
|
|
4
|
+
* Middleware para micro-servidores upstream.
|
|
5
|
+
* Rejeita qualquer pedido sem um token de delegação HMAC válido
|
|
6
|
+
* emitido por um SwarmGateway autorizado.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { requireGatewayClearance } from '@vurb/core';
|
|
11
|
+
*
|
|
12
|
+
* export const refund = f.tool('finance.refund')
|
|
13
|
+
* .use(requireGatewayClearance(process.env.VURB_DELEGATION_SECRET!))
|
|
14
|
+
* .withString('invoiceId', 'Invoice ID')
|
|
15
|
+
* .handle(async (input, ctx) => {
|
|
16
|
+
* // ctx.handoffState — carry-over state from the gateway
|
|
17
|
+
* // ctx.handoffScope — delegation scope (e.g. 'finance')
|
|
18
|
+
* // ctx.handoffTid — transaction ID for tracing
|
|
19
|
+
* // ctx.handoffTraceparent — W3C traceparent, if present
|
|
20
|
+
* return success(await stripe.refund(input.invoiceId));
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { verifyDelegationToken, HandoffAuthError } from './DelegationToken.js';
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Middleware factory
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Zero-trust middleware que valida o token de delegação HMAC.
|
|
32
|
+
*
|
|
33
|
+
* Lê o header `x-vurb-delegation` do `extra` do pedido MCP,
|
|
34
|
+
* verifica a assinatura HMAC-SHA256 e o TTL, e injeta
|
|
35
|
+
* {@link GatewayClearanceContext} no contexto do handler.
|
|
36
|
+
*
|
|
37
|
+
* @param secret - Segredo HMAC partilhado entre o gateway e o micro-servidor.
|
|
38
|
+
* Deve corresponder a `SwarmGatewayConfig.delegationSecret`.
|
|
39
|
+
* @param store - Store opcional para hidratação do Claim-Check (necessário quando
|
|
40
|
+
* `carryOverState` pode exceder 2 KB).
|
|
41
|
+
*
|
|
42
|
+
* @throws {@link HandoffAuthError} — traduzido para erro `FORBIDDEN` pelo framework
|
|
43
|
+
* se não for capturado pelo handler.
|
|
44
|
+
*/
|
|
45
|
+
export function requireGatewayClearance(secret, store) {
|
|
46
|
+
return async (ctx) => {
|
|
47
|
+
const raw = extractDelegationHeader(ctx);
|
|
48
|
+
if (!raw) {
|
|
49
|
+
throw new HandoffAuthError('MISSING_DELEGATION_TOKEN', 'This tool requires a delegation token from the SwarmGateway. ' +
|
|
50
|
+
'Direct access without a gateway is not permitted.');
|
|
51
|
+
}
|
|
52
|
+
let claims;
|
|
53
|
+
try {
|
|
54
|
+
claims = await verifyDelegationToken(raw, secret, store);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err instanceof HandoffAuthError)
|
|
58
|
+
throw err;
|
|
59
|
+
throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', `Invalid token: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
handoffState: claims.state ?? {},
|
|
63
|
+
handoffScope: claims.sub,
|
|
64
|
+
handoffTid: claims.tid,
|
|
65
|
+
...(claims.traceparent ? { handoffTraceparent: claims.traceparent } : {}),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Header extraction — duck-typed, transport-agnostic
|
|
71
|
+
// ============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Extract the delegation header value from the MCP handler context.
|
|
74
|
+
* Header lookup is case-insensitive (HTTP/1.1 RFC 7230 §3.2).
|
|
75
|
+
*
|
|
76
|
+
* HTTP proxies (nginx, Cloudflare, AWS ALB) may normalize headers to
|
|
77
|
+
* Title-Case (`X-Vurb-Delegation`) or strip them. A case-insensitive scan
|
|
78
|
+
* with `toLowerCase()` ensures the token is found regardless of normalization.
|
|
79
|
+
*/
|
|
80
|
+
function extractDelegationHeader(ctx) {
|
|
81
|
+
if (!ctx || typeof ctx !== 'object')
|
|
82
|
+
return undefined;
|
|
83
|
+
const c = ctx;
|
|
84
|
+
// MCP SDK extra: extra.requestInfo.headers['x-vurb-delegation']
|
|
85
|
+
const requestInfo = c['requestInfo'];
|
|
86
|
+
if (requestInfo && typeof requestInfo === 'object') {
|
|
87
|
+
const h = requestInfo['headers'];
|
|
88
|
+
const val = findHeaderCaseInsensitive(h, 'x-vurb-delegation');
|
|
89
|
+
if (val !== undefined)
|
|
90
|
+
return val;
|
|
91
|
+
}
|
|
92
|
+
// Fallback: ctx.headers (plain HTTP / custom transports)
|
|
93
|
+
const val = findHeaderCaseInsensitive(c['headers'], 'x-vurb-delegation');
|
|
94
|
+
if (val !== undefined)
|
|
95
|
+
return val;
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Case-insensitive lookup of `headerName` in a headers map.
|
|
100
|
+
* Returns the value as a string if found, or `undefined`.
|
|
101
|
+
*/
|
|
102
|
+
function findHeaderCaseInsensitive(headers, headerName) {
|
|
103
|
+
if (!headers || typeof headers !== 'object')
|
|
104
|
+
return undefined;
|
|
105
|
+
const h = headers;
|
|
106
|
+
const lower = headerName.toLowerCase();
|
|
107
|
+
// Fast path: exact key (most common — MCP SDK always uses lowercase)
|
|
108
|
+
if (typeof h[lower] === 'string')
|
|
109
|
+
return h[lower];
|
|
110
|
+
// Slow path: scan all keys case-insensitively (proxy-normalized headers)
|
|
111
|
+
for (const key of Object.keys(h)) {
|
|
112
|
+
if (key.toLowerCase() === lower && typeof h[key] === 'string') {
|
|
113
|
+
return h[key];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/handoff/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA0B/E,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CACnC,MAAc,EACd,KAAyB;IAGzB,OAAO,KAAK,EAAE,GAAY,EAAoC,EAAE;QAC5D,MAAM,GAAG,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAEzC,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,MAAM,IAAI,gBAAgB,CACtB,0BAA0B,EAC1B,+DAA+D;gBAC/D,mDAAmD,CACtD,CAAC;QACN,CAAC;QAED,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACD,MAAM,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,GAAG,YAAY,gBAAgB;gBAAE,MAAM,GAAG,CAAC;YAC/C,MAAM,IAAI,gBAAgB,CACtB,0BAA0B,EAC1B,kBAAmB,GAAa,CAAC,OAAO,EAAE,CAC7C,CAAC;QACN,CAAC;QAED,OAAO;YACH,YAAY,EAAG,MAAM,CAAC,KAAK,IAAI,EAAE;YACjC,YAAY,EAAG,MAAM,CAAC,GAAG;YACzB,UAAU,EAAK,MAAM,CAAC,GAAG;YACzB,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjD,CAAC;IACjC,CAAC,CAAC;AACN,CAAC;AAED,+EAA+E;AAC/E,qDAAqD;AACrD,+EAA+E;AAE/E;;;;;;;GAOG;AACH,SAAS,uBAAuB,CAAC,GAAY;IACzC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,CAAC,GAAG,GAA8B,CAAC;IAEzC,gEAAgE;IAChE,MAAM,WAAW,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC;IACrC,IAAI,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACjD,MAAM,CAAC,GAAI,WAAuC,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,yBAAyB,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAC9D,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;IACtC,CAAC;IAED,yDAAyD;IACzD,MAAM,GAAG,GAAG,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC,CAAC;IACzE,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAElC,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,SAAS,yBAAyB,CAC9B,OAAgB,EAChB,UAAkB;IAElB,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,CAAC,GAAG,OAAkC,CAAC;IAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IACvC,qEAAqE;IACrE,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,KAAK,CAAW,CAAC;IAC5D,yEAAyE;IACzE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,KAAK,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC5D,OAAO,CAAC,CAAC,GAAG,CAAW,CAAC;QAC5B,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}
|