|
1 | 1 | import type { AuthorizeFn } from "@intx/agent"; |
2 | 2 | import type { ToolCall } from "@intx/types/runtime"; |
3 | 3 | import { getLogger } from "@intx/log"; |
4 | | -import { type } from "arktype"; |
5 | 4 | import { isAbsolute, resolve } from "node:path"; |
6 | 5 | import type { |
7 | 6 | Approval, |
@@ -62,6 +61,10 @@ import { canonicalToolName } from "../agent/canonical-tool-name.js"; |
62 | 61 | import { prepareDispatchedToolCall } from "../agent/tool-aliases.js"; |
63 | 62 | import { LOG_NAMESPACE_ROOT } from "../branding.js"; |
64 | 63 | import { scrubSecrets } from "../web/secret-scrub.js"; |
| 64 | +import { |
| 65 | + safePluginDiagnostic, |
| 66 | + validatePluginAuthorizeResult, |
| 67 | +} from "./plugin-authorize-result.js"; |
65 | 68 |
|
66 | 69 | import { |
67 | 70 | createMcpToolPermissionRegistry, |
@@ -354,36 +357,6 @@ const AUTO_ALLOWED_TOOLS = new Set([ |
354 | 357 |
|
355 | 358 | const logger = getLogger([LOG_NAMESPACE_ROOT, "permission", "plugin-authz"]); |
356 | 359 |
|
357 | | -function safePluginDiagnostic(err: unknown): string { |
358 | | - try { |
359 | | - const diagnostic: unknown = err instanceof Error ? err.message : err; |
360 | | - return typeof diagnostic === "string" ? diagnostic : String(diagnostic); |
361 | | - } catch { |
362 | | - return "[unprintable thrown value]"; |
363 | | - } |
364 | | -} |
365 | | - |
366 | | -const PluginAuthorizeResult = type({ |
367 | | - effect: "'allow' | 'ask' | 'deny' | null", |
368 | | - matchingGrants: "unknown[]", |
369 | | - resolvedBy: "unknown", |
370 | | - "reason?": "string", |
371 | | -}); |
372 | | - |
373 | | -function materializePluginAuthorizeResult(rawResult: unknown): unknown { |
374 | | - const source = rawResult as Record<string, unknown>; |
375 | | - const effect = source.effect; |
376 | | - const matchingGrants = source.matchingGrants; |
377 | | - const resolvedBy = source.resolvedBy; |
378 | | - const reason = source.reason; |
379 | | - return { |
380 | | - effect, |
381 | | - matchingGrants, |
382 | | - resolvedBy, |
383 | | - ...(reason !== undefined ? { reason } : {}), |
384 | | - }; |
385 | | -} |
386 | | - |
387 | 360 | export interface PermissionGateOptions { |
388 | 361 | // Seed approvals; the gate copies them and never mutates the caller's array. |
389 | 362 | approvals: Approval[]; |
@@ -1030,13 +1003,16 @@ export function createPermissionGate( |
1030 | 1003 | "invoke", |
1031 | 1004 | call, |
1032 | 1005 | ); |
1033 | | - const result = PluginAuthorizeResult( |
1034 | | - materializePluginAuthorizeResult(rawResult), |
1035 | | - ); |
1036 | | - if (result instanceof type.errors) { |
1037 | | - logger.warn`plugin authorization returned a malformed result: ${scrubSecrets(result.summary)}`; |
| 1006 | + const validated = validatePluginAuthorizeResult(rawResult); |
| 1007 | + if (!validated.ok) { |
| 1008 | + if (validated.kind === "malformed") { |
| 1009 | + logger.warn`plugin authorization returned a malformed result: ${scrubSecrets(validated.diagnostic)}`; |
| 1010 | + } else { |
| 1011 | + logger.warn`plugin authorization failed: ${scrubSecrets(validated.diagnostic)}`; |
| 1012 | + } |
1038 | 1013 | return { kind: "deny", reason: "Plugin authorization failed closed." }; |
1039 | 1014 | } |
| 1015 | + const { result } = validated; |
1040 | 1016 | if (result.effect === "deny" || result.effect === null) { |
1041 | 1017 | const publicReason = |
1042 | 1018 | result.effect === "deny" && |
|
0 commit comments