@clampd/sdk 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Generic interceptor for Clampd — wraps ANY tool or function call
3
+ * through the Clampd security proxy pipeline.
4
+ *
5
+ * Three interception patterns:
6
+ *
7
+ * A. `wrapFunction` — wrap any async function
8
+ * B. `wrapOpenAITools` — wrap OpenAI / Vercel AI SDK tool definitions
9
+ * C. `ClampdGuard` — middleware for any framework (check + execute)
10
+ *
11
+ * No external dependencies — pure TypeScript.
12
+ */
13
+ import { ClampdClient, type ProxyResponse } from "./client.js";
14
+ /**
15
+ * OpenAI-compatible tool definition.
16
+ *
17
+ * Matches the shape used by the OpenAI Node SDK, Vercel AI SDK, and
18
+ * most LLM tool-calling libraries:
19
+ *
20
+ * ```ts
21
+ * {
22
+ * type: "function",
23
+ * function: {
24
+ * name: "get_weather",
25
+ * description: "Looks up current weather for a city",
26
+ * parameters: { ... }, // JSON Schema
27
+ * execute: async (args) => { ... },
28
+ * },
29
+ * }
30
+ * ```
31
+ *
32
+ * The `execute` field is optional in the OpenAI spec but required for
33
+ * interception — if a tool has no `execute` it is passed through as-is.
34
+ */
35
+ export interface OpenAIToolFunction {
36
+ name: string;
37
+ description?: string;
38
+ parameters?: Record<string, unknown>;
39
+ execute?: (args: Record<string, unknown>) => Promise<unknown>;
40
+ [key: string]: unknown;
41
+ }
42
+ export interface OpenAITool {
43
+ type: "function";
44
+ function: OpenAIToolFunction;
45
+ [key: string]: unknown;
46
+ }
47
+ /**
48
+ * Thrown when the Clampd proxy denies a tool call and `blockOnDeny`
49
+ * is enabled (the default).
50
+ */
51
+ export declare class ClampdBlockedError extends Error {
52
+ readonly response: ProxyResponse;
53
+ readonly matchedRules: string[];
54
+ readonly sessionFlags: string[];
55
+ constructor(response: ProxyResponse);
56
+ }
57
+ export interface WrapFunctionOptions<TArgs extends unknown[]> {
58
+ /** ClampdClient instance for proxy calls. */
59
+ client: ClampdClient;
60
+ /** Tool name sent to the Clampd pipeline (e.g. "http.fetch"). */
61
+ toolName: string;
62
+ /**
63
+ * Upstream tool URL. If omitted the proxy call uses an empty string
64
+ * which tells the gateway to skip forwarding (verify-only mode).
65
+ */
66
+ targetUrl?: string;
67
+ /**
68
+ * Converts the original function arguments into a flat params dict
69
+ * for the Clampd proxy request.
70
+ *
71
+ * If not provided the interceptor applies a default strategy:
72
+ * - 0 args => `{}`
73
+ * - 1 arg that is a plain object => pass it through
74
+ * - otherwise => `{ args: [...] }`
75
+ */
76
+ paramExtractor?: (...args: TArgs) => Record<string, unknown>;
77
+ /**
78
+ * When `true` (default), a denied proxy response throws
79
+ * `ClampdBlockedError` instead of executing the wrapped function.
80
+ */
81
+ blockOnDeny?: boolean;
82
+ /** Optional prompt context forwarded to the gateway. */
83
+ promptContext?: string;
84
+ }
85
+ /**
86
+ * Wrap any async function so every invocation first passes through the
87
+ * Clampd security pipeline.
88
+ *
89
+ * ```ts
90
+ * const guardedFetch = wrapFunction(myFetchFn, {
91
+ * client,
92
+ * toolName: "http.fetch",
93
+ * targetUrl: "http://tool:5555",
94
+ * paramExtractor: (url, opts) => ({ url, method: opts?.method ?? "GET" }),
95
+ * });
96
+ * const result = await guardedFetch("https://api.example.com", { method: "GET" });
97
+ * ```
98
+ */
99
+ export declare function wrapFunction<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, opts: WrapFunctionOptions<TArgs>): (...args: TArgs) => Promise<TReturn>;
100
+ export interface WrapOpenAIToolsOptions {
101
+ /** ClampdClient instance for proxy calls. */
102
+ client: ClampdClient;
103
+ /**
104
+ * Upstream tool URL forwarded to the Clampd pipeline.
105
+ * Defaults to "" (verify-only).
106
+ */
107
+ targetUrl?: string;
108
+ /**
109
+ * When `true` (default), a denied proxy response throws
110
+ * `ClampdBlockedError` instead of executing the tool function.
111
+ */
112
+ blockOnDeny?: boolean;
113
+ }
114
+ /**
115
+ * Wrap an array of OpenAI-style tool definitions so that each tool's
116
+ * `execute` function is intercepted through the Clampd proxy.
117
+ *
118
+ * Tools without an `execute` function are returned unchanged.
119
+ *
120
+ * ```ts
121
+ * const guardedTools = wrapOpenAITools(tools, { client, targetUrl: "http://tool:5555" });
122
+ * ```
123
+ */
124
+ export declare function wrapOpenAITools(tools: OpenAITool[], opts: WrapOpenAIToolsOptions): OpenAITool[];
125
+ export interface ClampdGuardOptions {
126
+ /**
127
+ * Default target URL for proxy calls. Individual `execute()` calls
128
+ * can override this.
129
+ */
130
+ defaultTargetUrl?: string;
131
+ }
132
+ /**
133
+ * Framework-agnostic guard that can pre-check or fully execute any
134
+ * tool call through the Clampd pipeline.
135
+ *
136
+ * ```ts
137
+ * const guard = new ClampdGuard(client, { defaultTargetUrl: "http://tool:5555" });
138
+ *
139
+ * // Dry-run check only
140
+ * const res = await guard.check("http.fetch", { url: "https://evil.com" });
141
+ * if (!res.allowed) console.log("Denied:", res.denial_reason);
142
+ *
143
+ * // Full pipeline: check + execute
144
+ * const { result, proxyResponse } = await guard.execute(
145
+ * "http.fetch",
146
+ * { url: "https://api.example.com" },
147
+ * () => fetch("https://api.example.com").then(r => r.json()),
148
+ * );
149
+ * ```
150
+ */
151
+ export declare class ClampdGuard {
152
+ private readonly client;
153
+ private readonly defaultTargetUrl;
154
+ constructor(client: ClampdClient, opts?: ClampdGuardOptions);
155
+ /**
156
+ * Dry-run policy check via the gateway's `/v1/verify` endpoint.
157
+ * No token exchange or upstream forwarding occurs.
158
+ */
159
+ check(tool: string, params: Record<string, unknown>): Promise<ProxyResponse>;
160
+ /**
161
+ * Full security pipeline: send the call through `/v1/proxy` and, if
162
+ * allowed, execute the provided function.
163
+ *
164
+ * Throws `ClampdBlockedError` when the proxy denies the call.
165
+ *
166
+ * @returns An object containing both the function result and the raw
167
+ * `ProxyResponse` from the gateway.
168
+ */
169
+ execute<T>(tool: string, params: Record<string, unknown>, fn: () => Promise<T>, targetUrl?: string): Promise<{
170
+ result: T;
171
+ proxyResponse: ProxyResponse;
172
+ }>;
173
+ }
174
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Generic interceptor for Clampd — wraps ANY tool or function call
3
+ * through the Clampd security proxy pipeline.
4
+ *
5
+ * Three interception patterns:
6
+ *
7
+ * A. `wrapFunction` — wrap any async function
8
+ * B. `wrapOpenAITools` — wrap OpenAI / Vercel AI SDK tool definitions
9
+ * C. `ClampdGuard` — middleware for any framework (check + execute)
10
+ *
11
+ * No external dependencies — pure TypeScript.
12
+ */
13
+ // ── Error ─────────────────────────────────────────────────────────
14
+ /**
15
+ * Thrown when the Clampd proxy denies a tool call and `blockOnDeny`
16
+ * is enabled (the default).
17
+ */
18
+ export class ClampdBlockedError extends Error {
19
+ response;
20
+ matchedRules;
21
+ sessionFlags;
22
+ constructor(response) {
23
+ const parts = [
24
+ `Blocked: ${response.denial_reason ?? "unknown reason"} (risk=${response.risk_score.toFixed(2)})`,
25
+ ];
26
+ if (response.matched_rules?.length) {
27
+ parts.push(`rules: ${response.matched_rules.join(", ")}`);
28
+ }
29
+ if (response.session_flags?.length) {
30
+ parts.push(`session: ${response.session_flags.join(", ")}`);
31
+ }
32
+ super(parts.join(" | "));
33
+ this.name = "ClampdBlockedError";
34
+ this.response = response;
35
+ this.matchedRules = response.matched_rules ?? [];
36
+ this.sessionFlags = response.session_flags ?? [];
37
+ }
38
+ }
39
+ /**
40
+ * Extract params from arbitrary function arguments using the default
41
+ * heuristic when no `paramExtractor` is supplied.
42
+ */
43
+ function defaultExtractParams(args) {
44
+ if (args.length === 0) {
45
+ return {};
46
+ }
47
+ if (args.length === 1) {
48
+ const single = args[0];
49
+ if (single !== null &&
50
+ typeof single === "object" &&
51
+ !Array.isArray(single)) {
52
+ return single;
53
+ }
54
+ return { arg: single };
55
+ }
56
+ return { args };
57
+ }
58
+ /**
59
+ * Wrap any async function so every invocation first passes through the
60
+ * Clampd security pipeline.
61
+ *
62
+ * ```ts
63
+ * const guardedFetch = wrapFunction(myFetchFn, {
64
+ * client,
65
+ * toolName: "http.fetch",
66
+ * targetUrl: "http://tool:5555",
67
+ * paramExtractor: (url, opts) => ({ url, method: opts?.method ?? "GET" }),
68
+ * });
69
+ * const result = await guardedFetch("https://api.example.com", { method: "GET" });
70
+ * ```
71
+ */
72
+ export function wrapFunction(fn, opts) {
73
+ const { client, toolName, targetUrl = "", paramExtractor, blockOnDeny = true, promptContext, } = opts;
74
+ return async (...args) => {
75
+ const params = paramExtractor
76
+ ? paramExtractor(...args)
77
+ : defaultExtractParams(args);
78
+ const proxyRes = await client.proxy(toolName, params, targetUrl, promptContext);
79
+ if (!proxyRes.allowed && blockOnDeny) {
80
+ throw new ClampdBlockedError(proxyRes);
81
+ }
82
+ // Allowed (or blockOnDeny=false) — execute the original function.
83
+ return fn(...args);
84
+ };
85
+ }
86
+ /**
87
+ * Wrap an array of OpenAI-style tool definitions so that each tool's
88
+ * `execute` function is intercepted through the Clampd proxy.
89
+ *
90
+ * Tools without an `execute` function are returned unchanged.
91
+ *
92
+ * ```ts
93
+ * const guardedTools = wrapOpenAITools(tools, { client, targetUrl: "http://tool:5555" });
94
+ * ```
95
+ */
96
+ export function wrapOpenAITools(tools, opts) {
97
+ const { client, targetUrl = "", blockOnDeny = true } = opts;
98
+ return tools.map((tool) => {
99
+ const originalExecute = tool.function.execute;
100
+ // If the tool has no execute handler there is nothing to intercept.
101
+ if (typeof originalExecute !== "function") {
102
+ return tool;
103
+ }
104
+ const wrappedExecute = async (args) => {
105
+ const proxyRes = await client.proxy(tool.function.name, args, targetUrl);
106
+ if (!proxyRes.allowed && blockOnDeny) {
107
+ throw new ClampdBlockedError(proxyRes);
108
+ }
109
+ return originalExecute(args);
110
+ };
111
+ // Return a shallow copy so we do not mutate the caller's array.
112
+ return {
113
+ ...tool,
114
+ function: {
115
+ ...tool.function,
116
+ execute: wrappedExecute,
117
+ },
118
+ };
119
+ });
120
+ }
121
+ /**
122
+ * Framework-agnostic guard that can pre-check or fully execute any
123
+ * tool call through the Clampd pipeline.
124
+ *
125
+ * ```ts
126
+ * const guard = new ClampdGuard(client, { defaultTargetUrl: "http://tool:5555" });
127
+ *
128
+ * // Dry-run check only
129
+ * const res = await guard.check("http.fetch", { url: "https://evil.com" });
130
+ * if (!res.allowed) console.log("Denied:", res.denial_reason);
131
+ *
132
+ * // Full pipeline: check + execute
133
+ * const { result, proxyResponse } = await guard.execute(
134
+ * "http.fetch",
135
+ * { url: "https://api.example.com" },
136
+ * () => fetch("https://api.example.com").then(r => r.json()),
137
+ * );
138
+ * ```
139
+ */
140
+ export class ClampdGuard {
141
+ client;
142
+ defaultTargetUrl;
143
+ constructor(client, opts) {
144
+ this.client = client;
145
+ this.defaultTargetUrl = opts?.defaultTargetUrl ?? "";
146
+ }
147
+ /**
148
+ * Dry-run policy check via the gateway's `/v1/verify` endpoint.
149
+ * No token exchange or upstream forwarding occurs.
150
+ */
151
+ async check(tool, params) {
152
+ return this.client.verify(tool, params);
153
+ }
154
+ /**
155
+ * Full security pipeline: send the call through `/v1/proxy` and, if
156
+ * allowed, execute the provided function.
157
+ *
158
+ * Throws `ClampdBlockedError` when the proxy denies the call.
159
+ *
160
+ * @returns An object containing both the function result and the raw
161
+ * `ProxyResponse` from the gateway.
162
+ */
163
+ async execute(tool, params, fn, targetUrl) {
164
+ const url = targetUrl ?? this.defaultTargetUrl;
165
+ const proxyResponse = await this.client.proxy(tool, params, url);
166
+ if (!proxyResponse.allowed) {
167
+ throw new ClampdBlockedError(proxyResponse);
168
+ }
169
+ const result = await fn();
170
+ return { result, proxyResponse };
171
+ }
172
+ }
173
+ //# sourceMappingURL=interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Tool-side scope token verification (Ed25519).
3
+ *
4
+ * Lets tool developers verify that a Clampd-approved scope token is present
5
+ * before executing sensitive operations. The gateway mints these tokens on
6
+ * approved proxy() calls; this module validates them on the tool side.
7
+ *
8
+ * Scope tokens are signed with Ed25519 (asymmetric). The public key is fetched
9
+ * from the gateway's JWKS endpoint (GET /.well-known/jwks.json) and cached
10
+ * locally for 1 hour. No shared secret is needed.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { requireScope, verifyScopeToken, getCurrentScopeToken } from "@clampd/sdk";
15
+ *
16
+ * // Option A: Explicit verification
17
+ * const claims = await verifyScopeToken(getCurrentScopeToken());
18
+ * if (!claims.scope.split(" ").includes("data:pii:query")) {
19
+ * throw new Error("Insufficient scope");
20
+ * }
21
+ *
22
+ * // Option B: Declarative (throws if scope missing)
23
+ * await requireScope("data:pii:query");
24
+ * ```
25
+ */
26
+ import { KeyObject } from "node:crypto";
27
+ export interface ScopeTokenClaims {
28
+ /** Agent ID */
29
+ sub: string;
30
+ /** Space-separated granted scopes */
31
+ scope: string;
32
+ /** Tool name */
33
+ tool: string;
34
+ /** SHA-256(tool + params) binding */
35
+ binding: string;
36
+ /** Expiry unix timestamp */
37
+ exp: number;
38
+ /** Request ID */
39
+ rid: string;
40
+ }
41
+ export declare class ScopeVerificationError extends Error {
42
+ readonly reason: string;
43
+ readonly claims?: ScopeTokenClaims | undefined;
44
+ constructor(reason: string, claims?: ScopeTokenClaims | undefined);
45
+ }
46
+ /**
47
+ * Run a function with a scope token set in async context.
48
+ * Used internally by guard()/openai()/anthropic() wrappers.
49
+ */
50
+ export declare function withScopeToken<T>(token: string, fn: () => T): T;
51
+ export declare function setScopeToken(token: string): void;
52
+ interface JwksKey {
53
+ kty: string;
54
+ crv: string;
55
+ use?: string;
56
+ kid: string;
57
+ x: string;
58
+ }
59
+ interface JwksResponse {
60
+ keys: JwksKey[];
61
+ }
62
+ /**
63
+ * Fetch JWKS from the gateway. Results are cached for 1 hour.
64
+ */
65
+ export declare function fetchJwks(gatewayUrl?: string): Promise<JwksResponse>;
66
+ /**
67
+ * Invalidate the JWKS cache so the next verification re-fetches.
68
+ */
69
+ export declare function invalidateJwksCache(): void;
70
+ /**
71
+ * Verify a Clampd scope token and return its claims.
72
+ *
73
+ * @param token - The scope token (format: base64url_payload.base64url_signature)
74
+ * @param publicKey - Optional Ed25519 public key (Node.js KeyObject). If not provided, fetches JWKS.
75
+ * @param gatewayUrl - Gateway URL for JWKS fetch. Falls back to env vars.
76
+ * @throws {ScopeVerificationError} On invalid/expired/missing token.
77
+ */
78
+ export declare function verifyScopeToken(token: string, publicKey?: KeyObject, gatewayUrl?: string): Promise<ScopeTokenClaims>;
79
+ /**
80
+ * Read the current scope token from async context.
81
+ *
82
+ * Returns the token set by the guard/openai/anthropic wrappers during the
83
+ * current tool call, or empty string if no token is available.
84
+ */
85
+ export declare function getCurrentScopeToken(): string;
86
+ /**
87
+ * Verify scope token and check that a specific scope is granted.
88
+ *
89
+ * Combines getCurrentScopeToken(), verifyScopeToken(), and scope checking.
90
+ *
91
+ * @param requiredScope - The scope to check (e.g. "data:pii:query").
92
+ * @param token - Optional explicit token. If not provided, reads from context.
93
+ * @param publicKey - Optional Ed25519 public key.
94
+ * @param gatewayUrl - Gateway URL for JWKS fetch.
95
+ * @throws {ScopeVerificationError} If token is invalid or scope not granted.
96
+ */
97
+ export declare function requireScope(requiredScope: string, token?: string, publicKey?: KeyObject, gatewayUrl?: string): Promise<ScopeTokenClaims>;
98
+ export {};
99
+ //# sourceMappingURL=tool-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-verify.d.ts","sourceRoot":"","sources":["../src/tool-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAA2B,SAAS,EAAE,MAAM,aAAa,CAAC;AAKjE,MAAM,WAAW,gBAAgB;IAC/B,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAID,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,MAAM;aACd,MAAM,CAAC,EAAE,gBAAgB;gBADzB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,gBAAgB,YAAA;CAK5C;AAMD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE/D;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAID,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAcD;;GAEG;AACH,wBAAsB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAoB1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AA6BD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,SAAS,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAmE3B;AAID;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,SAAS,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAiB3B"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Tool-side scope token verification (Ed25519).
3
+ *
4
+ * Lets tool developers verify that a Clampd-approved scope token is present
5
+ * before executing sensitive operations. The gateway mints these tokens on
6
+ * approved proxy() calls; this module validates them on the tool side.
7
+ *
8
+ * Scope tokens are signed with Ed25519 (asymmetric). The public key is fetched
9
+ * from the gateway's JWKS endpoint (GET /.well-known/jwks.json) and cached
10
+ * locally for 1 hour. No shared secret is needed.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { requireScope, verifyScopeToken, getCurrentScopeToken } from "@clampd/sdk";
15
+ *
16
+ * // Option A: Explicit verification
17
+ * const claims = await verifyScopeToken(getCurrentScopeToken());
18
+ * if (!claims.scope.split(" ").includes("data:pii:query")) {
19
+ * throw new Error("Insufficient scope");
20
+ * }
21
+ *
22
+ * // Option B: Declarative (throws if scope missing)
23
+ * await requireScope("data:pii:query");
24
+ * ```
25
+ */
26
+ import { verify, createPublicKey } from "node:crypto";
27
+ import { AsyncLocalStorage } from "node:async_hooks";
28
+ // ── Error class ───────────────────────────────────────────────────────
29
+ export class ScopeVerificationError extends Error {
30
+ reason;
31
+ claims;
32
+ constructor(reason, claims) {
33
+ super(`Scope verification failed: ${reason}`);
34
+ this.reason = reason;
35
+ this.claims = claims;
36
+ this.name = "ScopeVerificationError";
37
+ }
38
+ }
39
+ // ── Scope token storage (AsyncLocalStorage) ───────────────────────────
40
+ const scopeTokenStore = new AsyncLocalStorage();
41
+ /**
42
+ * Run a function with a scope token set in async context.
43
+ * Used internally by guard()/openai()/anthropic() wrappers.
44
+ */
45
+ export function withScopeToken(token, fn) {
46
+ return scopeTokenStore.run(token, fn);
47
+ }
48
+ /**
49
+ * Set the scope token for the current async context.
50
+ * Falls back to module-level variable when not inside AsyncLocalStorage.
51
+ */
52
+ let _fallbackScopeToken = "";
53
+ export function setScopeToken(token) {
54
+ _fallbackScopeToken = token;
55
+ }
56
+ let _cachedJwks = null;
57
+ let _jwksFetchedAt = 0;
58
+ const _JWKS_CACHE_TTL = 3600 * 1000; // 1 hour in ms
59
+ function getGatewayUrl() {
60
+ return (process.env.CLAMPD_GATEWAY_URL ||
61
+ process.env.AG_GATEWAY_URL ||
62
+ "http://localhost:8080");
63
+ }
64
+ /**
65
+ * Fetch JWKS from the gateway. Results are cached for 1 hour.
66
+ */
67
+ export async function fetchJwks(gatewayUrl) {
68
+ const now = Date.now();
69
+ if (_cachedJwks && now - _jwksFetchedAt < _JWKS_CACHE_TTL) {
70
+ return _cachedJwks;
71
+ }
72
+ const base = (gatewayUrl || getGatewayUrl()).replace(/\/$/, "");
73
+ const url = `${base}/.well-known/jwks.json`;
74
+ const resp = await fetch(url, { signal: AbortSignal.timeout(10_000) });
75
+ if (!resp.ok) {
76
+ throw new ScopeVerificationError(`JWKS fetch failed: ${resp.status} ${resp.statusText}`);
77
+ }
78
+ const jwks = (await resp.json());
79
+ _cachedJwks = jwks;
80
+ _jwksFetchedAt = now;
81
+ return jwks;
82
+ }
83
+ /**
84
+ * Invalidate the JWKS cache so the next verification re-fetches.
85
+ */
86
+ export function invalidateJwksCache() {
87
+ _cachedJwks = null;
88
+ _jwksFetchedAt = 0;
89
+ }
90
+ /**
91
+ * Extract the Ed25519 public key from JWKS as a Node.js KeyObject.
92
+ */
93
+ async function getEd25519PublicKey(gatewayUrl) {
94
+ const jwks = await fetchJwks(gatewayUrl);
95
+ const key = jwks.keys.find((k) => k.crv === "Ed25519" && k.kid === "scope-v1");
96
+ if (!key) {
97
+ throw new ScopeVerificationError("No Ed25519 scope key found in JWKS");
98
+ }
99
+ // Convert JWK to a Node.js KeyObject
100
+ return createPublicKey({
101
+ key: {
102
+ kty: "OKP",
103
+ crv: "Ed25519",
104
+ x: key.x,
105
+ },
106
+ format: "jwk",
107
+ });
108
+ }
109
+ // ── Token verification ────────────────────────────────────────────────
110
+ /**
111
+ * Verify a Clampd scope token and return its claims.
112
+ *
113
+ * @param token - The scope token (format: base64url_payload.base64url_signature)
114
+ * @param publicKey - Optional Ed25519 public key (Node.js KeyObject). If not provided, fetches JWKS.
115
+ * @param gatewayUrl - Gateway URL for JWKS fetch. Falls back to env vars.
116
+ * @throws {ScopeVerificationError} On invalid/expired/missing token.
117
+ */
118
+ export async function verifyScopeToken(token, publicKey, gatewayUrl) {
119
+ if (!token) {
120
+ throw new ScopeVerificationError("No scope token provided");
121
+ }
122
+ // Split token: base64url_payload.base64url_signature
123
+ const parts = token.split(".");
124
+ if (parts.length !== 2) {
125
+ throw new ScopeVerificationError(`Malformed token: expected 2 parts, got ${parts.length}`);
126
+ }
127
+ const [payloadB64, sigB64] = parts;
128
+ // Get public key
129
+ const pubKey = publicKey || (await getEd25519PublicKey(gatewayUrl));
130
+ // Decode signature
131
+ let sigBytes;
132
+ try {
133
+ sigBytes = Buffer.from(sigB64, "base64url");
134
+ }
135
+ catch (e) {
136
+ throw new ScopeVerificationError(`Signature decode error: ${e}`);
137
+ }
138
+ // Verify Ed25519 signature
139
+ const isValid = verify("ed25519", Buffer.from(payloadB64), pubKey, sigBytes);
140
+ if (!isValid) {
141
+ invalidateJwksCache();
142
+ throw new ScopeVerificationError("Invalid signature");
143
+ }
144
+ // Decode payload
145
+ let payload;
146
+ try {
147
+ const payloadStr = Buffer.from(payloadB64, "base64url").toString("utf-8");
148
+ payload = JSON.parse(payloadStr);
149
+ }
150
+ catch (e) {
151
+ throw new ScopeVerificationError(`Invalid payload: ${e}`);
152
+ }
153
+ // Extract claims
154
+ if (typeof payload.sub !== "string" || typeof payload.exp !== "number") {
155
+ throw new ScopeVerificationError("Missing required claim: sub or exp");
156
+ }
157
+ const claims = {
158
+ sub: payload.sub,
159
+ scope: payload.scope ?? "",
160
+ tool: payload.tool ?? "",
161
+ binding: payload.binding ?? "",
162
+ exp: payload.exp,
163
+ rid: payload.rid ?? "",
164
+ };
165
+ // Check expiry
166
+ if (Date.now() / 1000 > claims.exp) {
167
+ throw new ScopeVerificationError("Token expired", claims);
168
+ }
169
+ return claims;
170
+ }
171
+ // ── Context helpers ───────────────────────────────────────────────────
172
+ /**
173
+ * Read the current scope token from async context.
174
+ *
175
+ * Returns the token set by the guard/openai/anthropic wrappers during the
176
+ * current tool call, or empty string if no token is available.
177
+ */
178
+ export function getCurrentScopeToken() {
179
+ return scopeTokenStore.getStore() ?? _fallbackScopeToken ?? "";
180
+ }
181
+ // ── Convenience function ──────────────────────────────────────────────
182
+ /**
183
+ * Verify scope token and check that a specific scope is granted.
184
+ *
185
+ * Combines getCurrentScopeToken(), verifyScopeToken(), and scope checking.
186
+ *
187
+ * @param requiredScope - The scope to check (e.g. "data:pii:query").
188
+ * @param token - Optional explicit token. If not provided, reads from context.
189
+ * @param publicKey - Optional Ed25519 public key.
190
+ * @param gatewayUrl - Gateway URL for JWKS fetch.
191
+ * @throws {ScopeVerificationError} If token is invalid or scope not granted.
192
+ */
193
+ export async function requireScope(requiredScope, token, publicKey, gatewayUrl) {
194
+ const resolvedToken = token || getCurrentScopeToken();
195
+ if (!resolvedToken) {
196
+ throw new ScopeVerificationError("No scope token available in context");
197
+ }
198
+ const claims = await verifyScopeToken(resolvedToken, publicKey, gatewayUrl);
199
+ const grantedScopes = claims.scope.split(" ");
200
+ if (!grantedScopes.includes(requiredScope)) {
201
+ throw new ScopeVerificationError(`Scope '${requiredScope}' not granted (have: ${claims.scope})`, claims);
202
+ }
203
+ return claims;
204
+ }
205
+ //# sourceMappingURL=tool-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-verify.js","sourceRoot":"","sources":["../src/tool-verify.ts"],"names":[],"mappings":"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"}