@astrasyncai/verification-gateway 5.18.1 → 5.20.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/CHANGELOG.md +232 -0
- package/README.md +217 -4
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +9 -1
- package/dist/adapters/express.d.ts +9 -1
- package/dist/adapters/express.js +160 -52
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +160 -52
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +7 -1
- package/dist/adapters/http-pdlss.d.ts +7 -1
- package/dist/adapters/http-pdlss.js +18 -2
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs +16 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/kit-express.d.mts +93 -1
- package/dist/adapters/kit-express.d.ts +93 -1
- package/dist/adapters/kit-express.js +181 -2
- package/dist/adapters/kit-express.js.map +1 -1
- package/dist/adapters/kit-express.mjs +180 -2
- package/dist/adapters/kit-express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +102 -9
- package/dist/adapters/mcp.d.ts +102 -9
- package/dist/adapters/mcp.js +242 -19
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +240 -19
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +97 -14
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +97 -14
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +101 -2
- package/dist/adapters/sdk.d.ts +101 -2
- package/dist/adapters/sdk.js +199 -12
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +199 -12
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +94 -16
- package/dist/bin/astrasync-codex-hook.js +94 -16
- package/dist/bin/astrasync-guard.js +94 -16
- package/dist/bin/astrasync.js +127 -16
- package/dist/browser/background.js +94 -16
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +94 -16
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +94 -16
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +94 -16
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +94 -16
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +94 -16
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +1 -1
- package/dist/edge-core/index.d.ts +1 -1
- package/dist/edge-core/index.js +69 -12
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +69 -12
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +94 -16
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +94 -16
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +706 -34
- package/dist/index.d.ts +706 -34
- package/dist/index.js +1391 -205
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1374 -195
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/registration/index.d.mts +11 -2
- package/dist/registration/index.d.ts +11 -2
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.d.mts +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +3 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +3 -2
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
- package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
- package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
- package/dist/{types-Cns4l8gX.d.mts → types-CxjqmPl-.d.mts} +115 -13
- package/dist/{types-Cns4l8gX.d.ts → types-CxjqmPl-.d.ts} +115 -13
- package/dist/{types-3ObBpsnf.d.mts → types-D37aSQ9c.d.mts} +106 -3
- package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -1
- package/dist/{types-BoxZEYPS.d.ts → types-Dn8tXnei.d.ts} +106 -3
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +50 -4
- package/dist/verify.d.ts +50 -4
- package/dist/verify.js +93 -14
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +91 -14
- package/dist/verify.mjs.map +1 -1
- package/dist/webhooks.d.mts +16 -3
- package/dist/webhooks.d.ts +16 -3
- package/dist/webhooks.js +24 -13
- package/dist/webhooks.js.map +1 -1
- package/dist/webhooks.mjs +24 -13
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/adapters/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler } from 'express';
|
|
2
|
-
import {
|
|
2
|
+
import { e as VerificationResult, G as GatewayConfig } from '../types-CxjqmPl-.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* X-Astra-Verified-Hop — cross-hop verify-access dedupe marker.
|
|
@@ -378,23 +378,116 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
378
378
|
recordDecisions?: boolean;
|
|
379
379
|
/** Forward runtime challenge (default `true`). */
|
|
380
380
|
enableRuntimeChallenge?: boolean;
|
|
381
|
+
/**
|
|
382
|
+
* 5.19.0: how a dashboard tool rule combines with a code `toolGates`
|
|
383
|
+
* entry for the same tool. With `apiKey` + `counterpartyId` set, the
|
|
384
|
+
* middleware fetches the endpoint's tool rules (`{tool, purpose, action,
|
|
385
|
+
* resource?, observe?}`, edited in the AstraSync dashboard; refreshed every
|
|
386
|
+
* `routesRefreshMs`) and turns each into a gate for that tool.
|
|
387
|
+
*
|
|
388
|
+
* - `'code'` (default): a dashboard rule can TIGHTEN a code gate but never
|
|
389
|
+
* loosen it. For a tool gated in code (object form), a dashboard
|
|
390
|
+
* `observe` rule, or one with no purpose, is ignored; a dashboard rule
|
|
391
|
+
* whose purpose or action differs is evaluated IN ADDITION — both must
|
|
392
|
+
* pass. Tools with no code gate (or a code `'observe'`) follow the
|
|
393
|
+
* dashboard rule.
|
|
394
|
+
* - `'dashboard'`: the dashboard rule wins outright for every tool it
|
|
395
|
+
* names (including `observe`).
|
|
396
|
+
*
|
|
397
|
+
* Rules flagged `legacy: true` (path rules stored on an MCP endpoint before
|
|
398
|
+
* tool rules existed) are ignored. The middleware sends its code-gated tool
|
|
399
|
+
* names on the policy fetch (`?codeGates=`) so the dashboard can mark them.
|
|
400
|
+
*/
|
|
401
|
+
toolGatePrecedence?: 'code' | 'dashboard';
|
|
402
|
+
/** 5.19.0: refresh interval (ms) for the dashboard tool rules. Default 5 minutes. */
|
|
403
|
+
routesRefreshMs?: number;
|
|
404
|
+
/**
|
|
405
|
+
* 5.20.0: tools and JSON-RPC methods that pass through with NO verify-access
|
|
406
|
+
* call at all — not even the audit evaluation `evaluateAlwaysIfCredentialed`
|
|
407
|
+
* adds for credentialed callers. Applies only while the call is not
|
|
408
|
+
* enforced (an `'observe'` gate, or a handshake/introspection method with
|
|
409
|
+
* no `methodGates` entry); a dashboard rule that enforces a listed tool
|
|
410
|
+
* still verifies.
|
|
411
|
+
*
|
|
412
|
+
* Use it for:
|
|
413
|
+
* - `methods`: handshake and introspection (`initialize`, `tools/list`,
|
|
414
|
+
* `resources/list`, `resources/read`) — nothing to attribute, and a
|
|
415
|
+
* backend round trip slows the host's first card fetch;
|
|
416
|
+
* - `tools`: observe tools whose handler calls verify-access itself, so
|
|
417
|
+
* the gate's evaluation would be a second backend verify for the same
|
|
418
|
+
* call (the MCP bridge measured ~3 backend calls per `verify_agent`).
|
|
419
|
+
*
|
|
420
|
+
* Listed calls leave `req.agentVerification` unset.
|
|
421
|
+
*/
|
|
422
|
+
skipEvaluation?: {
|
|
423
|
+
tools?: readonly string[];
|
|
424
|
+
methods?: readonly string[];
|
|
425
|
+
};
|
|
381
426
|
/**
|
|
382
427
|
* Posture when the MCP middleware itself throws an internal error.
|
|
383
|
-
* Default `'open'` for backward compatibility in SDK 2.4.13. Shadow logs
|
|
384
|
-
* record what WOULD be denied with `failOnError: 'closed'` so merchants
|
|
385
|
-
* can grep correlationId for impact analysis during the observation
|
|
386
|
-
* window. Default flips to `'closed'` in a follow-up release.
|
|
387
428
|
*
|
|
388
|
-
* 5.
|
|
389
|
-
* JSON-RPC error (HTTP 503 +
|
|
390
|
-
* identity-shaped 401
|
|
429
|
+
* 5.19.0 (behaviour change): default `'closed'` — an internal throw answers
|
|
430
|
+
* the retryable verification-unavailable JSON-RPC error (HTTP 503 +
|
|
431
|
+
* Retry-After, code -32003; never an identity-shaped 401) and the tool
|
|
432
|
+
* handler is not called. `'open'` (pass the call through unverified) is
|
|
433
|
+
* an explicit opt-in. The `[SHADOW] would-have-denied` log line is still
|
|
434
|
+
* written on every throw, with the correlationId.
|
|
391
435
|
*/
|
|
392
436
|
failOnError?: 'open' | 'closed';
|
|
393
437
|
}
|
|
438
|
+
/** 5.19.0: a dashboard tool rule as the MCP middleware evaluates it. */
|
|
439
|
+
interface DashboardToolRule {
|
|
440
|
+
tool: string;
|
|
441
|
+
purpose?: string;
|
|
442
|
+
action?: string;
|
|
443
|
+
resource?: string;
|
|
444
|
+
observe?: boolean;
|
|
445
|
+
}
|
|
446
|
+
/** The usable tool rules in a fetched policy (tool key set, not legacy), by tool name. */
|
|
447
|
+
declare function dashboardToolRules(routes: ReadonlyArray<{
|
|
448
|
+
tool?: string;
|
|
449
|
+
legacy?: boolean;
|
|
450
|
+
purpose?: string;
|
|
451
|
+
action?: string;
|
|
452
|
+
resource?: string;
|
|
453
|
+
observe?: boolean;
|
|
454
|
+
}>): Map<string, DashboardToolRule>;
|
|
455
|
+
type GateSource = 'toolGate' | 'methodGate' | 'dashboard' | 'tier' | 'discovery_default';
|
|
456
|
+
/**
|
|
457
|
+
* Resolve whether a parsed MCP request is enforced, and the gate config to
|
|
458
|
+
* apply. SDK 5.0.0 removed the access-level band — a gate's only decision is
|
|
459
|
+
* enforce vs observe.
|
|
460
|
+
*
|
|
461
|
+
* Precedence:
|
|
462
|
+
* 1. Non-tools/call without `methodGates` entry → pass through
|
|
463
|
+
* (`discovery_default`). Discovery is open — agents must list tools
|
|
464
|
+
* before invoking them.
|
|
465
|
+
* 2. `methodGates[method]` — per-method override (`'observe'` or a config)
|
|
466
|
+
* 3. tools/call: the code `toolGates[toolName]` and the dashboard tool rule
|
|
467
|
+
* (5.19.0) — see `toolGatePrecedence`. With the default `'code'`, a
|
|
468
|
+
* dashboard rule only tightens a code gate: it is ignored when it is
|
|
469
|
+
* `observe` or has no purpose, and evaluated as an `additional` gate
|
|
470
|
+
* (both must pass) when its purpose/action differ.
|
|
471
|
+
* 4. `mcpDefaultEnforced` — built-in default (tools/call → enforced)
|
|
472
|
+
*
|
|
473
|
+
* The `source` field is logged for forensics / activity-feed attribution.
|
|
474
|
+
*/
|
|
475
|
+
declare function resolveGate(parsed: ParsedMcpRequest, opts: {
|
|
476
|
+
toolGates?: Record<string, ToolGate>;
|
|
477
|
+
methodGates?: Record<string, ToolGate>;
|
|
478
|
+
dashboardRules?: Map<string, DashboardToolRule>;
|
|
479
|
+
toolGatePrecedence?: 'code' | 'dashboard';
|
|
480
|
+
}): {
|
|
481
|
+
enforce: boolean;
|
|
482
|
+
config?: ToolGateConfig;
|
|
483
|
+
/** 5.19.0: a dashboard gate evaluated in addition to `config` (both must pass). */
|
|
484
|
+
additional?: ToolGateConfig;
|
|
485
|
+
source: GateSource;
|
|
486
|
+
};
|
|
394
487
|
/**
|
|
395
488
|
* Create the MCP middleware. Attach AFTER `express.json()` — the body must
|
|
396
489
|
* already be a parsed object.
|
|
397
490
|
*/
|
|
398
491
|
declare function createMcpMiddleware(options: McpMiddlewareOptions): RequestHandler;
|
|
399
492
|
|
|
400
|
-
export { MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, type ToolGate, type ToolGateConfig, createMcpMiddleware, isVerifiedHopValidFor, mcpDefaultEnforced, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, serializeVerifiedHop };
|
|
493
|
+
export { type DashboardToolRule, MCP_VERIFIED_HOP_HEADER, type McpMiddlewareOptions, type ParsedMcpRequest, type ToolGate, type ToolGateConfig, createMcpMiddleware, dashboardToolRules, isVerifiedHopValidFor, mcpDefaultEnforced, mcpToPdlss, parseMcpJsonRpc, parseVerifiedHop, resolveGate, serializeVerifiedHop };
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -22,17 +22,19 @@ var mcp_exports = {};
|
|
|
22
22
|
__export(mcp_exports, {
|
|
23
23
|
MCP_VERIFIED_HOP_HEADER: () => MCP_VERIFIED_HOP_HEADER,
|
|
24
24
|
createMcpMiddleware: () => createMcpMiddleware,
|
|
25
|
+
dashboardToolRules: () => dashboardToolRules,
|
|
25
26
|
isVerifiedHopValidFor: () => isVerifiedHopValidFor,
|
|
26
27
|
mcpDefaultEnforced: () => mcpDefaultEnforced,
|
|
27
28
|
mcpToPdlss: () => mcpToPdlss,
|
|
28
29
|
parseMcpJsonRpc: () => parseMcpJsonRpc,
|
|
29
30
|
parseVerifiedHop: () => parseVerifiedHop,
|
|
31
|
+
resolveGate: () => resolveGate,
|
|
30
32
|
serializeVerifiedHop: () => serializeVerifiedHop
|
|
31
33
|
});
|
|
32
34
|
module.exports = __toCommonJS(mcp_exports);
|
|
33
35
|
|
|
34
36
|
// src/version.ts
|
|
35
|
-
var SDK_VERSION = "5.
|
|
37
|
+
var SDK_VERSION = "5.20.0";
|
|
36
38
|
|
|
37
39
|
// src/http.ts
|
|
38
40
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -274,7 +276,12 @@ function getCacheKey(request, counterpartyId) {
|
|
|
274
276
|
// its start_checkout quote was served the cached quote-leg grant (no
|
|
275
277
|
// settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
|
|
276
278
|
request.commercePhase || "",
|
|
277
|
-
request.checkoutSessionId || ""
|
|
279
|
+
request.checkoutSessionId || "",
|
|
280
|
+
// 5.19.0: a route_config purpose claim is checked server-side against
|
|
281
|
+
// the stored rule for this method + path, so it changes the verdict.
|
|
282
|
+
request.purposeSource || "",
|
|
283
|
+
request.requestMethod || "",
|
|
284
|
+
request.requestPath || ""
|
|
278
285
|
].join("|");
|
|
279
286
|
}
|
|
280
287
|
function getCachedResult(request, counterpartyId) {
|
|
@@ -466,6 +473,9 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
466
473
|
if (requestData.enableRuntimeChallenge)
|
|
467
474
|
body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
|
|
468
475
|
if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
|
|
476
|
+
if (config.actingProof?.connectorToken) {
|
|
477
|
+
body.actingProof = { connectorToken: config.actingProof.connectorToken };
|
|
478
|
+
}
|
|
469
479
|
if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
|
|
470
480
|
if (requestData.createSession) body.createSession = requestData.createSession;
|
|
471
481
|
if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
|
|
@@ -482,6 +492,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
482
492
|
if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
|
|
483
493
|
if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
|
|
484
494
|
if (requestData.attemptId) body.attemptId = requestData.attemptId;
|
|
495
|
+
if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
|
|
496
|
+
if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
|
|
497
|
+
if (requestData.metadata) body.metadata = requestData.metadata;
|
|
498
|
+
if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
|
|
499
|
+
if (requestData.requestPath) body.requestPath = requestData.requestPath;
|
|
485
500
|
if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
|
|
486
501
|
body.sdkVersion = SDK_VERSION;
|
|
487
502
|
if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
|
|
@@ -628,22 +643,46 @@ async function verify(config, request, options) {
|
|
|
628
643
|
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access decisions (verified ID + runtime challenge + PDLSS + trust score). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
|
|
629
644
|
);
|
|
630
645
|
}
|
|
631
|
-
const uncacheable = Boolean(request.commerceArtifacts);
|
|
646
|
+
const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
|
|
632
647
|
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
|
|
633
648
|
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
634
649
|
if (cached) {
|
|
635
650
|
if (mergedConfig.debug) {
|
|
636
651
|
console.log("[VerificationGateway] Returning cached result");
|
|
637
652
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
653
|
+
return sharedVerdict(cached, request);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
|
|
657
|
+
const key = getCacheKey(request, mergedConfig.counterpartyId);
|
|
658
|
+
const pending = inflightVerifies.get(key);
|
|
659
|
+
if (pending) return sharedVerdict(await pending, request);
|
|
660
|
+
const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
|
|
661
|
+
inflightVerifies.set(key, flight);
|
|
662
|
+
try {
|
|
663
|
+
return await flight;
|
|
664
|
+
} finally {
|
|
665
|
+
inflightVerifies.delete(key);
|
|
645
666
|
}
|
|
646
667
|
}
|
|
668
|
+
return verifyViaApi(mergedConfig, request, urls, uncacheable);
|
|
669
|
+
}
|
|
670
|
+
var inflightVerifies = /* @__PURE__ */ new Map();
|
|
671
|
+
var sharedVerdicts = /* @__PURE__ */ new WeakSet();
|
|
672
|
+
function isSharedVerdict(result) {
|
|
673
|
+
return sharedVerdicts.has(result);
|
|
674
|
+
}
|
|
675
|
+
function sharedVerdict(source, request) {
|
|
676
|
+
const hit = { ...source };
|
|
677
|
+
if (request.attemptId) {
|
|
678
|
+
hit.attemptId = request.attemptId;
|
|
679
|
+
} else {
|
|
680
|
+
delete hit.attemptId;
|
|
681
|
+
}
|
|
682
|
+
sharedVerdicts.add(hit);
|
|
683
|
+
return hit;
|
|
684
|
+
}
|
|
685
|
+
async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
|
|
647
686
|
const enrichedRequest = { ...request };
|
|
648
687
|
if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
|
|
649
688
|
enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
|
|
@@ -671,9 +710,18 @@ async function verify(config, request, options) {
|
|
|
671
710
|
if (!apiResponse.access?.allowed) {
|
|
672
711
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
673
712
|
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
713
|
+
const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
|
|
714
|
+
astraId: apiResponse.agent.astraId,
|
|
715
|
+
name: apiResponse.agent.name,
|
|
716
|
+
trustScore: apiResponse.agent.trustScore,
|
|
717
|
+
trustLevel: getTrustLevel(apiResponse.agent.trustScore),
|
|
718
|
+
blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
|
|
719
|
+
status: apiResponse.agent.agentStatus
|
|
720
|
+
} : void 0;
|
|
674
721
|
const result2 = {
|
|
675
722
|
identityVerified: idVerifiedFromBackend,
|
|
676
723
|
policyAllowed: false,
|
|
724
|
+
...denyAgent && { agent: denyAgent },
|
|
677
725
|
// 3.6.0: the server now emits top-level denialReasons on every deny
|
|
678
726
|
// path — prefer it verbatim; the failure-message reconstruction remains
|
|
679
727
|
// as the fallback for older backends.
|
|
@@ -713,7 +761,9 @@ async function verify(config, request, options) {
|
|
|
713
761
|
fulfilment: apiResponse.fulfilment,
|
|
714
762
|
mandates: apiResponse.mandates,
|
|
715
763
|
kyaToken: apiResponse.kyaToken,
|
|
716
|
-
aep: apiResponse.aep
|
|
764
|
+
aep: apiResponse.aep,
|
|
765
|
+
// 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
|
|
766
|
+
warnings: apiResponse.warnings
|
|
717
767
|
};
|
|
718
768
|
return result2;
|
|
719
769
|
}
|
|
@@ -781,7 +831,9 @@ async function verify(config, request, options) {
|
|
|
781
831
|
// 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
|
|
782
832
|
mandates: apiResponse.mandates,
|
|
783
833
|
kyaToken: apiResponse.kyaToken,
|
|
784
|
-
aep: apiResponse.aep
|
|
834
|
+
aep: apiResponse.aep,
|
|
835
|
+
// 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
|
|
836
|
+
warnings: apiResponse.warnings
|
|
785
837
|
};
|
|
786
838
|
if (result.recommendation === "deny") {
|
|
787
839
|
result.policyAllowed = false;
|
|
@@ -807,8 +859,18 @@ async function verify(config, request, options) {
|
|
|
807
859
|
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
808
860
|
}
|
|
809
861
|
}
|
|
862
|
+
notifyVerificationListeners(request, result);
|
|
810
863
|
return result;
|
|
811
864
|
}
|
|
865
|
+
var verificationListeners = /* @__PURE__ */ new Set();
|
|
866
|
+
function notifyVerificationListeners(request, result) {
|
|
867
|
+
for (const listener of verificationListeners) {
|
|
868
|
+
try {
|
|
869
|
+
listener(result, request);
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
812
874
|
async function recordDecision(config, sessionId, decision, reason) {
|
|
813
875
|
const headers = { "Content-Type": "application/json" };
|
|
814
876
|
if (config.apiKey) {
|
|
@@ -822,6 +884,42 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
822
884
|
}).catch(() => {
|
|
823
885
|
});
|
|
824
886
|
}
|
|
887
|
+
var refusedPolicyReads = /* @__PURE__ */ new Set();
|
|
888
|
+
async function fetchRoutes(config, counterpartyId, options = {}) {
|
|
889
|
+
if (!counterpartyId) return null;
|
|
890
|
+
const headers = { "Content-Type": "application/json" };
|
|
891
|
+
if (config.apiKey) {
|
|
892
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
893
|
+
headers["X-API-Key"] = config.apiKey;
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
|
|
897
|
+
const response = await sdkFetch(
|
|
898
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
|
|
899
|
+
{ method: "GET", headers }
|
|
900
|
+
);
|
|
901
|
+
if (!response.ok) {
|
|
902
|
+
if ((response.status === 401 || response.status === 403 || response.status === 404) && !refusedPolicyReads.has(counterpartyId)) {
|
|
903
|
+
refusedPolicyReads.add(counterpartyId);
|
|
904
|
+
console.error(
|
|
905
|
+
`[VerificationGateway] the configured API key cannot read the route policy for ${counterpartyId} (HTTP ${response.status}). The key belongs to an account that does not own this endpoint, or the counterpartyId is wrong. The gateway is in PASS-THROUGH mode: no agent is being verified.`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
const body = await response.json();
|
|
911
|
+
const type = body.data?.counterpartyType;
|
|
912
|
+
if (typeof type === "string" && options.onEndpointType) {
|
|
913
|
+
try {
|
|
914
|
+
options.onEndpointType(type);
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return body.data?.routes ?? [];
|
|
919
|
+
} catch {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
825
923
|
|
|
826
924
|
// src/metadata-capture.ts
|
|
827
925
|
var CAPTURE_SCHEMA_VERSION = 1;
|
|
@@ -1144,6 +1242,42 @@ async function awaitStepUpApproval(config, approval) {
|
|
|
1144
1242
|
return "timeout";
|
|
1145
1243
|
}
|
|
1146
1244
|
|
|
1245
|
+
// src/adapters/routes-cache.ts
|
|
1246
|
+
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1247
|
+
function createRoutesCache(config, opts = {}) {
|
|
1248
|
+
const refreshMs = opts.refreshMs ?? DEFAULT_ROUTES_REFRESH_MS;
|
|
1249
|
+
let cached = [];
|
|
1250
|
+
let lastFetchAt = 0;
|
|
1251
|
+
let refreshing = null;
|
|
1252
|
+
async function refresh() {
|
|
1253
|
+
if (!config.counterpartyId) {
|
|
1254
|
+
opts.onNoCounterparty?.();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const fetched = await fetchRoutes(config, config.counterpartyId, opts.fetchOptions);
|
|
1258
|
+
if (fetched) {
|
|
1259
|
+
cached = fetched;
|
|
1260
|
+
lastFetchAt = Date.now();
|
|
1261
|
+
opts.onFetched?.(fetched);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function start() {
|
|
1265
|
+
refreshing = refresh().catch(() => void 0).finally(() => {
|
|
1266
|
+
refreshing = null;
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
start();
|
|
1270
|
+
return {
|
|
1271
|
+
async current() {
|
|
1272
|
+
if (refreshing) await refreshing.catch(() => {
|
|
1273
|
+
});
|
|
1274
|
+
if (config.counterpartyId && Date.now() - lastFetchAt > refreshMs) start();
|
|
1275
|
+
return cached;
|
|
1276
|
+
},
|
|
1277
|
+
peek: () => cached
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1147
1281
|
// src/verified-hop.ts
|
|
1148
1282
|
var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
|
|
1149
1283
|
var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
|
|
@@ -1364,6 +1498,21 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1364
1498
|
}
|
|
1365
1499
|
});
|
|
1366
1500
|
}
|
|
1501
|
+
function dashboardToolRules(routes) {
|
|
1502
|
+
const out = /* @__PURE__ */ new Map();
|
|
1503
|
+
for (const r of routes) {
|
|
1504
|
+
if (typeof r.tool !== "string" || !r.tool || r.legacy === true) continue;
|
|
1505
|
+
if (out.has(r.tool)) continue;
|
|
1506
|
+
out.set(r.tool, {
|
|
1507
|
+
tool: r.tool,
|
|
1508
|
+
...r.purpose && { purpose: r.purpose },
|
|
1509
|
+
...r.action && { action: r.action },
|
|
1510
|
+
...r.resource && { resource: r.resource },
|
|
1511
|
+
...r.observe === true && { observe: true }
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
return out;
|
|
1515
|
+
}
|
|
1367
1516
|
function resolveGate(parsed, opts) {
|
|
1368
1517
|
if (!parsed.toolName) {
|
|
1369
1518
|
const methodGate = opts.methodGates?.[parsed.method];
|
|
@@ -1373,8 +1522,30 @@ function resolveGate(parsed, opts) {
|
|
|
1373
1522
|
return { enforce: false, source: "discovery_default" };
|
|
1374
1523
|
}
|
|
1375
1524
|
const toolGate = opts.toolGates?.[parsed.toolName];
|
|
1525
|
+
const rule = opts.dashboardRules?.get(parsed.toolName);
|
|
1526
|
+
const fromRule = (r) => r.observe ? { enforce: false, source: "dashboard" } : {
|
|
1527
|
+
enforce: true,
|
|
1528
|
+
config: {
|
|
1529
|
+
...r.purpose && { purpose: r.purpose },
|
|
1530
|
+
...r.action && { action: r.action },
|
|
1531
|
+
...r.resource && { resource: r.resource }
|
|
1532
|
+
},
|
|
1533
|
+
source: "dashboard"
|
|
1534
|
+
};
|
|
1535
|
+
const codeGated = toolGate !== void 0 && toolGate !== "observe";
|
|
1536
|
+
if (rule && (opts.toolGatePrecedence === "dashboard" || !codeGated)) return fromRule(rule);
|
|
1376
1537
|
if (toolGate !== void 0) {
|
|
1377
|
-
|
|
1538
|
+
if (toolGate === "observe") return { enforce: false, source: "toolGate" };
|
|
1539
|
+
if (rule && !rule.observe && rule.purpose && (rule.purpose !== toolGate.purpose || rule.action && rule.action !== toolGate.action)) {
|
|
1540
|
+
const extra = fromRule(rule).config;
|
|
1541
|
+
return {
|
|
1542
|
+
enforce: true,
|
|
1543
|
+
config: toolGate,
|
|
1544
|
+
additional: { ...extra, ...!extra.resource && toolGate.resource && { resource: toolGate.resource } },
|
|
1545
|
+
source: "toolGate"
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
return { enforce: true, config: toolGate, source: "toolGate" };
|
|
1378
1549
|
}
|
|
1379
1550
|
return { enforce: mcpDefaultEnforced(parsed), source: "tier" };
|
|
1380
1551
|
}
|
|
@@ -1389,13 +1560,34 @@ function createMcpMiddleware(options) {
|
|
|
1389
1560
|
verifiedHopMaxAgeMs,
|
|
1390
1561
|
recordDecisions,
|
|
1391
1562
|
enableRuntimeChallenge = true,
|
|
1392
|
-
failOnError = "
|
|
1563
|
+
failOnError = "closed",
|
|
1564
|
+
toolGatePrecedence = "code",
|
|
1565
|
+
routesRefreshMs,
|
|
1566
|
+
skipEvaluation,
|
|
1393
1567
|
...config
|
|
1394
1568
|
} = options;
|
|
1569
|
+
const skipTools = new Set(skipEvaluation?.tools ?? []);
|
|
1570
|
+
const skipMethods = new Set(skipEvaluation?.methods ?? []);
|
|
1395
1571
|
if (config.apiBaseUrl) {
|
|
1396
1572
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1397
1573
|
});
|
|
1398
1574
|
}
|
|
1575
|
+
const codeGatedTools = Object.entries(toolGates ?? {}).filter(([, g]) => g !== "observe").map(([name]) => name);
|
|
1576
|
+
let warnedEndpointType = false;
|
|
1577
|
+
const onEndpointType = (counterpartyType) => {
|
|
1578
|
+
if (counterpartyType === "mcp_server" || warnedEndpointType) return;
|
|
1579
|
+
warnedEndpointType = true;
|
|
1580
|
+
console.warn(
|
|
1581
|
+
`[VerificationGateway/MCP] counterpartyId ${config.counterpartyId} is a '${counterpartyType}' endpoint, not an 'mcp_server' endpoint. Dashboard tool rules are read from an mcp_server endpoint only: register a separate MCP endpoint for this server (same owner) and use its id here. See https://astrasync.ai/docs/mcp-integration#tool-rules.`
|
|
1582
|
+
);
|
|
1583
|
+
};
|
|
1584
|
+
const rulesCache = config.apiKey && config.counterpartyId ? createRoutesCache(config, {
|
|
1585
|
+
...routesRefreshMs !== void 0 && { refreshMs: routesRefreshMs },
|
|
1586
|
+
fetchOptions: {
|
|
1587
|
+
...codeGatedTools.length && { codeGates: codeGatedTools },
|
|
1588
|
+
onEndpointType
|
|
1589
|
+
}
|
|
1590
|
+
}) : void 0;
|
|
1399
1591
|
return async (req, res, next) => {
|
|
1400
1592
|
try {
|
|
1401
1593
|
if (skip) return next();
|
|
@@ -1444,10 +1636,19 @@ function createMcpMiddleware(options) {
|
|
|
1444
1636
|
return next();
|
|
1445
1637
|
}
|
|
1446
1638
|
}
|
|
1639
|
+
if (!parsed.toolName && skipMethods.has(parsed.method) && methodGates?.[parsed.method] === void 0) {
|
|
1640
|
+
return next();
|
|
1641
|
+
}
|
|
1642
|
+
const dashboardRules = rulesCache ? dashboardToolRules(await rulesCache.current()) : void 0;
|
|
1447
1643
|
const gateDecision = resolveGate(parsed, {
|
|
1448
1644
|
toolGates,
|
|
1449
|
-
methodGates
|
|
1645
|
+
methodGates,
|
|
1646
|
+
dashboardRules,
|
|
1647
|
+
toolGatePrecedence
|
|
1450
1648
|
});
|
|
1649
|
+
if (!gateDecision.enforce && (parsed.toolName && skipTools.has(parsed.toolName) || !parsed.toolName && skipMethods.has(parsed.method))) {
|
|
1650
|
+
return next();
|
|
1651
|
+
}
|
|
1451
1652
|
const credentials = extractCredentials(
|
|
1452
1653
|
req.headers,
|
|
1453
1654
|
req.query
|
|
@@ -1504,8 +1705,14 @@ function createMcpMiddleware(options) {
|
|
|
1504
1705
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
1505
1706
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
1506
1707
|
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1708
|
+
const attemptRaw = readSingleHeader(req.headers["x-astra-attempt-id"]);
|
|
1709
|
+
const metaAttempt = req.body?.params?._meta?.astrasync?.attemptId;
|
|
1710
|
+
const attemptId = [attemptRaw, metaAttempt].find(
|
|
1711
|
+
(v) => typeof v === "string" && /^att_[0-9a-f]{32}$/.test(v)
|
|
1712
|
+
);
|
|
1507
1713
|
const verificationRequest = {
|
|
1508
1714
|
credentials,
|
|
1715
|
+
...attemptId && { attemptId },
|
|
1509
1716
|
purpose: pdlss.purpose,
|
|
1510
1717
|
action: pdlss.action,
|
|
1511
1718
|
resource: pdlss.resource,
|
|
@@ -1544,6 +1751,20 @@ function createMcpMiddleware(options) {
|
|
|
1544
1751
|
result.stepUpOutcome = outcome;
|
|
1545
1752
|
}
|
|
1546
1753
|
}
|
|
1754
|
+
if (shouldEnforce && gateDecision.additional?.purpose && result.policyAllowed && !requiresHumanApproval(result)) {
|
|
1755
|
+
const extra = mcpToPdlss(parsed, req.path, void 0, void 0, gateDecision.additional);
|
|
1756
|
+
const extraResult = await verify(config, {
|
|
1757
|
+
...verificationRequest,
|
|
1758
|
+
purpose: extra.purpose,
|
|
1759
|
+
action: extra.action,
|
|
1760
|
+
resource: extra.resource
|
|
1761
|
+
});
|
|
1762
|
+
if (!extraResult.policyAllowed || requiresHumanApproval(extraResult)) {
|
|
1763
|
+
result = extraResult;
|
|
1764
|
+
req.agentVerification = result;
|
|
1765
|
+
sessionId = result.sessionId;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1547
1768
|
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
1548
1769
|
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1549
1770
|
return next();
|
|
@@ -1553,14 +1774,14 @@ function createMcpMiddleware(options) {
|
|
|
1553
1774
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1554
1775
|
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1555
1776
|
}
|
|
1556
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1777
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1557
1778
|
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1558
1779
|
});
|
|
1559
1780
|
}
|
|
1560
1781
|
return next();
|
|
1561
1782
|
}
|
|
1562
1783
|
if (!result.policyAllowed) {
|
|
1563
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1784
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1564
1785
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
1565
1786
|
});
|
|
1566
1787
|
}
|
|
@@ -1570,7 +1791,7 @@ function createMcpMiddleware(options) {
|
|
|
1570
1791
|
}
|
|
1571
1792
|
if (requiresHumanApproval(result)) {
|
|
1572
1793
|
annotateApprovalRequired(result);
|
|
1573
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1794
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1574
1795
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
1575
1796
|
});
|
|
1576
1797
|
}
|
|
@@ -1588,7 +1809,7 @@ function createMcpMiddleware(options) {
|
|
|
1588
1809
|
})
|
|
1589
1810
|
);
|
|
1590
1811
|
}
|
|
1591
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1812
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1592
1813
|
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1593
1814
|
});
|
|
1594
1815
|
}
|
|
@@ -1629,11 +1850,13 @@ function createMcpMiddleware(options) {
|
|
|
1629
1850
|
0 && (module.exports = {
|
|
1630
1851
|
MCP_VERIFIED_HOP_HEADER,
|
|
1631
1852
|
createMcpMiddleware,
|
|
1853
|
+
dashboardToolRules,
|
|
1632
1854
|
isVerifiedHopValidFor,
|
|
1633
1855
|
mcpDefaultEnforced,
|
|
1634
1856
|
mcpToPdlss,
|
|
1635
1857
|
parseMcpJsonRpc,
|
|
1636
1858
|
parseVerifiedHop,
|
|
1859
|
+
resolveGate,
|
|
1637
1860
|
serializeVerifiedHop
|
|
1638
1861
|
});
|
|
1639
1862
|
//# sourceMappingURL=mcp.js.map
|