@astrasyncai/verification-gateway 5.11.0 → 5.13.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 +4 -0
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +394 -39
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +394 -39
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +5 -1
- package/dist/adapters/mcp.d.ts +5 -1
- package/dist/adapters/mcp.js +193 -49
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +193 -49
- 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 +387 -22
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +387 -22
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +9 -1
- package/dist/adapters/sdk.d.ts +9 -1
- package/dist/adapters/sdk.js +125 -16
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +125 -16
- package/dist/adapters/sdk.mjs.map +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 +309 -15
- package/dist/bin/astrasync-codex-hook.js +309 -15
- package/dist/bin/astrasync-guard.js +309 -15
- package/dist/bin/astrasync.js +322 -17
- package/dist/browser/background.js +309 -15
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +309 -15
- 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 +309 -15
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +309 -15
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +309 -15
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +309 -15
- 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 +128 -13
- package/dist/edge-core/index.d.ts +128 -13
- package/dist/edge-core/index.js +362 -63
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +359 -63
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +309 -15
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +309 -15
- 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 +118 -15
- package/dist/index.d.ts +118 -15
- package/dist/index.js +521 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -72
- package/dist/index.mjs.map +1 -1
- package/dist/platform-signatures.js +4 -1
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs +4 -1
- package/dist/platform-signatures.mjs.map +1 -1
- 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.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
- package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
- package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
- package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +35 -14
- package/dist/verify.d.ts +35 -14
- package/dist/verify.js +165 -25
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +164 -25
- package/dist/verify.mjs.map +1 -1
- package/package.json +3 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import { NextRequest } from 'next/server';
|
|
3
|
-
import { N as NextJsMiddlewareOptions } from '../types-
|
|
3
|
+
import { N as NextJsMiddlewareOptions } from '../types-BCkwBA84.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Create Next.js middleware for agent verification.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import { NextRequest } from 'next/server';
|
|
3
|
-
import { N as NextJsMiddlewareOptions } from '../types-
|
|
3
|
+
import { N as NextJsMiddlewareOptions } from '../types-BCkwBA84.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Create Next.js middleware for agent verification.
|
package/dist/adapters/nextjs.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(nextjs_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(nextjs_exports);
|
|
37
37
|
|
|
38
38
|
// src/version.ts
|
|
39
|
-
var SDK_VERSION = "5.
|
|
39
|
+
var SDK_VERSION = "5.13.0";
|
|
40
40
|
|
|
41
41
|
// src/http.ts
|
|
42
42
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -129,6 +129,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
129
129
|
return cache.get(apiBaseUrl)?.data;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// src/failure-posture.ts
|
|
133
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
134
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
135
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
136
|
+
"identity.invalid_agent_id",
|
|
137
|
+
"identity.unknown_agent",
|
|
138
|
+
"agent.lookup"
|
|
139
|
+
]);
|
|
140
|
+
var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
|
|
141
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
142
|
+
"verify_access.api_error",
|
|
143
|
+
"middleware.internal_error"
|
|
144
|
+
]);
|
|
145
|
+
function isVerificationUnavailable(result) {
|
|
146
|
+
if (result.misconfigured) return false;
|
|
147
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
148
|
+
}
|
|
149
|
+
function unavailableRetryAfterSeconds(result) {
|
|
150
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
151
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
152
|
+
}
|
|
153
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
154
|
+
}
|
|
155
|
+
function isAnonymousRead(method, credentials) {
|
|
156
|
+
const m = (method ?? "").toUpperCase();
|
|
157
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
158
|
+
return !credentials.astraId && !credentials.jwt;
|
|
159
|
+
}
|
|
160
|
+
function buildVerificationUnavailableBody(result) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: {
|
|
164
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
165
|
+
message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry after the Retry-After interval.",
|
|
166
|
+
retryable: true,
|
|
167
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
168
|
+
...result.failures && { failures: result.failures },
|
|
169
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
170
|
+
guidance: {
|
|
171
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function isRejectedIdentityClaim(result) {
|
|
177
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
178
|
+
}
|
|
179
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
180
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
181
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
182
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
183
|
+
}
|
|
184
|
+
|
|
132
185
|
// src/verify.ts
|
|
133
186
|
var DEFAULT_CONFIG = {
|
|
134
187
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -141,7 +194,7 @@ var DEFAULT_CONFIG = {
|
|
|
141
194
|
};
|
|
142
195
|
var initCheckPerformed = false;
|
|
143
196
|
var deprecationWarningShown = false;
|
|
144
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
197
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
145
198
|
initCheckPerformed = true;
|
|
146
199
|
try {
|
|
147
200
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -159,7 +212,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
159
212
|
);
|
|
160
213
|
}
|
|
161
214
|
if (apiKey) {
|
|
162
|
-
const
|
|
215
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
216
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
163
217
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
164
218
|
});
|
|
165
219
|
if (selfCheck.status === 401) {
|
|
@@ -171,6 +225,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
171
225
|
console.error(message);
|
|
172
226
|
} else if (selfCheck.ok) {
|
|
173
227
|
const body = await selfCheck.json().catch(() => ({}));
|
|
228
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
229
|
+
const message = `[VerificationGateway] the configured AstraSync API key cannot verify for this endpoint (${body.counterparty.message ?? "endpoint not found or owned by another account"}). Every inbound agent will be turned away until the key or counterparty is corrected.`;
|
|
230
|
+
if (strictInit) {
|
|
231
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
232
|
+
}
|
|
233
|
+
console.error(message);
|
|
234
|
+
}
|
|
174
235
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
175
236
|
console.warn(
|
|
176
237
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -309,11 +370,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
309
370
|
const isMisconfig = source === "merchant_misconfig";
|
|
310
371
|
const urls = options.urls;
|
|
311
372
|
const guidance = isMisconfig ? {
|
|
312
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
373
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, rotated, or belongs to another account). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
313
374
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
314
375
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
315
376
|
steps: [
|
|
316
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
377
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
317
378
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
318
379
|
]
|
|
319
380
|
} : isApiError ? {
|
|
@@ -374,11 +435,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
374
435
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
375
436
|
// deterministic and intentionally NOT marked retryable.
|
|
376
437
|
...isApiError && { retryable: true },
|
|
438
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
439
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
377
440
|
correlationId: options.correlationId,
|
|
378
441
|
attemptId: options.attemptId,
|
|
379
442
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
380
443
|
};
|
|
381
444
|
}
|
|
445
|
+
async function readJsonSafe(response) {
|
|
446
|
+
try {
|
|
447
|
+
const parsed = await response.json();
|
|
448
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
449
|
+
} catch {
|
|
450
|
+
return {};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function rejectedIdentityGuidance(failures) {
|
|
454
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
455
|
+
if (!f) return void 0;
|
|
456
|
+
if (f.dimension === "agent.lookup") {
|
|
457
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
458
|
+
}
|
|
459
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
460
|
+
}
|
|
461
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
462
|
+
function parseRetryAfterMs(response) {
|
|
463
|
+
const raw = response.headers?.get?.("retry-after");
|
|
464
|
+
if (!raw) return void 0;
|
|
465
|
+
const trimmed = raw.trim();
|
|
466
|
+
let ms;
|
|
467
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
468
|
+
ms = Number(trimmed) * 1e3;
|
|
469
|
+
} else {
|
|
470
|
+
const at = Date.parse(trimmed);
|
|
471
|
+
if (Number.isNaN(at)) return void 0;
|
|
472
|
+
ms = at - Date.now();
|
|
473
|
+
}
|
|
474
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
475
|
+
}
|
|
382
476
|
async function callVerifyAccessAPI(config, request) {
|
|
383
477
|
const { credentials, ...requestData } = request;
|
|
384
478
|
const body = {
|
|
@@ -435,7 +529,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
435
529
|
body: JSON.stringify(body),
|
|
436
530
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
437
531
|
});
|
|
438
|
-
const data = await response
|
|
532
|
+
const data = await readJsonSafe(response);
|
|
533
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
439
534
|
if (response.status === 410) {
|
|
440
535
|
return {
|
|
441
536
|
success: true,
|
|
@@ -473,13 +568,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
473
568
|
}
|
|
474
569
|
};
|
|
475
570
|
}
|
|
571
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
572
|
+
return {
|
|
573
|
+
success: true,
|
|
574
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
575
|
+
access: {
|
|
576
|
+
allowed: false,
|
|
577
|
+
reason: "unknown_agent",
|
|
578
|
+
failures: [
|
|
579
|
+
{
|
|
580
|
+
dimension: "identity.unknown_agent",
|
|
581
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
582
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
583
|
+
}
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
476
588
|
if (!response.ok) {
|
|
477
|
-
|
|
589
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
590
|
+
if (response.status === 401 || keyMismatch) {
|
|
478
591
|
return {
|
|
479
592
|
success: false,
|
|
480
593
|
error: data.message || data.error || "Integration API key rejected",
|
|
481
594
|
misconfig: true,
|
|
482
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
595
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
483
596
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
484
597
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
485
598
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -487,9 +600,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
487
600
|
}
|
|
488
601
|
return {
|
|
489
602
|
success: false,
|
|
490
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
603
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
491
604
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
492
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
605
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
606
|
+
httpStatus: response.status,
|
|
607
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
493
608
|
};
|
|
494
609
|
}
|
|
495
610
|
return data;
|
|
@@ -510,14 +625,22 @@ async function verify(config, request, options) {
|
|
|
510
625
|
mergedConfig.apiBaseUrl,
|
|
511
626
|
mergedConfig.debug,
|
|
512
627
|
true,
|
|
513
|
-
mergedConfig.apiKey
|
|
628
|
+
mergedConfig.apiKey,
|
|
629
|
+
{
|
|
630
|
+
id: mergedConfig.counterpartyId,
|
|
631
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
632
|
+
}
|
|
514
633
|
);
|
|
515
634
|
} else {
|
|
516
635
|
void performInitCheck(
|
|
517
636
|
mergedConfig.apiBaseUrl,
|
|
518
637
|
mergedConfig.debug,
|
|
519
638
|
false,
|
|
520
|
-
mergedConfig.apiKey
|
|
639
|
+
mergedConfig.apiKey,
|
|
640
|
+
{
|
|
641
|
+
id: mergedConfig.counterpartyId,
|
|
642
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
643
|
+
}
|
|
521
644
|
);
|
|
522
645
|
}
|
|
523
646
|
}
|
|
@@ -561,12 +684,14 @@ async function verify(config, request, options) {
|
|
|
561
684
|
correlationId: apiResponse.correlationId,
|
|
562
685
|
attemptId: apiResponse.attemptId,
|
|
563
686
|
remediation: apiResponse.remediation,
|
|
687
|
+
httpStatus: apiResponse.httpStatus,
|
|
688
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
564
689
|
urls
|
|
565
690
|
});
|
|
566
691
|
}
|
|
567
692
|
if (!apiResponse.access?.allowed) {
|
|
568
693
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
569
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
694
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
570
695
|
const result2 = {
|
|
571
696
|
identityVerified: idVerifiedFromBackend,
|
|
572
697
|
policyAllowed: false,
|
|
@@ -578,7 +703,10 @@ async function verify(config, request, options) {
|
|
|
578
703
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
579
704
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
580
705
|
guidance: {
|
|
581
|
-
|
|
706
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
707
|
+
// actionable registration guidance on its failure — surface it
|
|
708
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
709
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
582
710
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
583
711
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
584
712
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -678,7 +806,7 @@ async function verify(config, request, options) {
|
|
|
678
806
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
679
807
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
680
808
|
};
|
|
681
|
-
} else if (result.recommendation === "step_up_required") {
|
|
809
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
682
810
|
result.requiresStepUp = true;
|
|
683
811
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
684
812
|
}
|
|
@@ -688,6 +816,7 @@ async function verify(config, request, options) {
|
|
|
688
816
|
}
|
|
689
817
|
return result;
|
|
690
818
|
}
|
|
819
|
+
var refusedPolicyReads = /* @__PURE__ */ new Set();
|
|
691
820
|
async function fetchRoutes(config, counterpartyId) {
|
|
692
821
|
if (!counterpartyId) return null;
|
|
693
822
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -700,7 +829,15 @@ async function fetchRoutes(config, counterpartyId) {
|
|
|
700
829
|
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
|
|
701
830
|
{ method: "GET", headers }
|
|
702
831
|
);
|
|
703
|
-
if (!response.ok)
|
|
832
|
+
if (!response.ok) {
|
|
833
|
+
if ((response.status === 401 || response.status === 403 || response.status === 404) && !refusedPolicyReads.has(counterpartyId)) {
|
|
834
|
+
refusedPolicyReads.add(counterpartyId);
|
|
835
|
+
console.error(
|
|
836
|
+
`[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.`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
704
841
|
const body = await response.json();
|
|
705
842
|
return body.data?.routes ?? [];
|
|
706
843
|
} catch {
|
|
@@ -717,7 +854,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
717
854
|
};
|
|
718
855
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
719
856
|
method: "POST",
|
|
720
|
-
headers: {
|
|
857
|
+
headers: {
|
|
858
|
+
"Content-Type": "application/json",
|
|
859
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
860
|
+
},
|
|
721
861
|
body: JSON.stringify(body)
|
|
722
862
|
}).catch(() => {
|
|
723
863
|
});
|
|
@@ -1190,7 +1330,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1190
1330
|
{
|
|
1191
1331
|
vendor: "chatgpt",
|
|
1192
1332
|
displayName: "OpenAI ChatGPT",
|
|
1193
|
-
|
|
1333
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1334
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1335
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1336
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1194
1337
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1195
1338
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1196
1339
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1325,6 +1468,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1325
1468
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1326
1469
|
|
|
1327
1470
|
// src/edge-core/events.ts
|
|
1471
|
+
async function withBudget(promise, ms) {
|
|
1472
|
+
let timer;
|
|
1473
|
+
try {
|
|
1474
|
+
return await Promise.race([
|
|
1475
|
+
promise,
|
|
1476
|
+
new Promise((resolve) => {
|
|
1477
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1478
|
+
})
|
|
1479
|
+
]);
|
|
1480
|
+
} catch {
|
|
1481
|
+
return void 0;
|
|
1482
|
+
} finally {
|
|
1483
|
+
if (timer) clearTimeout(timer);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1487
|
+
var BeaconBuffer = class {
|
|
1488
|
+
constructor(options = {}) {
|
|
1489
|
+
this.queue = [];
|
|
1490
|
+
this.droppedSinceLast = 0;
|
|
1491
|
+
this.droppedTotal = 0;
|
|
1492
|
+
this.deliveredTotal = 0;
|
|
1493
|
+
this.backoffUntil = 0;
|
|
1494
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1495
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1496
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1497
|
+
this.opts = {
|
|
1498
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1499
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1500
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1501
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1502
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1503
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1504
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1505
|
+
now: options.now ?? Date.now
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1510
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1511
|
+
* becoming its own item.
|
|
1512
|
+
*/
|
|
1513
|
+
enqueue(dest, item, collapseKey) {
|
|
1514
|
+
const now = this.opts.now();
|
|
1515
|
+
if (collapseKey) {
|
|
1516
|
+
const entry = this.collapse.get(collapseKey);
|
|
1517
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1518
|
+
for (const target of [entry.first, entry.held]) {
|
|
1519
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1520
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
const held = this.push(
|
|
1525
|
+
dest,
|
|
1526
|
+
{ ...item, repeatCount: 1 },
|
|
1527
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1528
|
+
);
|
|
1529
|
+
entry.held = held;
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
this.collapse.delete(collapseKey);
|
|
1533
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1534
|
+
const oldest = this.collapse.keys().next().value;
|
|
1535
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1536
|
+
}
|
|
1537
|
+
const fresh = { firstSeenAt: now };
|
|
1538
|
+
this.collapse.set(collapseKey, fresh);
|
|
1539
|
+
fresh.first = this.push(dest, item, now);
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
this.push(dest, item, now);
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1546
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1547
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1548
|
+
* picked up by the same drain.
|
|
1549
|
+
*/
|
|
1550
|
+
flush() {
|
|
1551
|
+
if (!this.inFlight) {
|
|
1552
|
+
this.inFlight = this.drain().finally(() => {
|
|
1553
|
+
this.inFlight = void 0;
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
return this.inFlight;
|
|
1557
|
+
}
|
|
1558
|
+
/** Counters for tests and diagnostics. */
|
|
1559
|
+
stats() {
|
|
1560
|
+
return {
|
|
1561
|
+
queueDepth: this.queue.length,
|
|
1562
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1563
|
+
droppedTotal: this.droppedTotal,
|
|
1564
|
+
deliveredTotal: this.deliveredTotal,
|
|
1565
|
+
flushing: this.inFlight !== void 0
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
push(dest, item, eligibleAt) {
|
|
1569
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1570
|
+
const entry = {
|
|
1571
|
+
dest,
|
|
1572
|
+
destKey: destKeyOf(dest),
|
|
1573
|
+
item,
|
|
1574
|
+
attempts: 0,
|
|
1575
|
+
eligibleAt,
|
|
1576
|
+
inFlight: false
|
|
1577
|
+
};
|
|
1578
|
+
this.queue.push(entry);
|
|
1579
|
+
return entry;
|
|
1580
|
+
}
|
|
1581
|
+
dropOldest() {
|
|
1582
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1583
|
+
}
|
|
1584
|
+
countDropped(n) {
|
|
1585
|
+
this.droppedSinceLast += n;
|
|
1586
|
+
this.droppedTotal += n;
|
|
1587
|
+
}
|
|
1588
|
+
async drain() {
|
|
1589
|
+
for (; ; ) {
|
|
1590
|
+
const now = this.opts.now();
|
|
1591
|
+
if (now < this.backoffUntil) return;
|
|
1592
|
+
const batch = this.takeBatch(now);
|
|
1593
|
+
if (batch.length === 0) return;
|
|
1594
|
+
const ok = await this.send(batch);
|
|
1595
|
+
if (!ok) return;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1599
|
+
takeBatch(now) {
|
|
1600
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1601
|
+
if (!first) return [];
|
|
1602
|
+
const batch = [];
|
|
1603
|
+
const rest = [];
|
|
1604
|
+
for (const q of this.queue) {
|
|
1605
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1606
|
+
q.inFlight = true;
|
|
1607
|
+
batch.push(q);
|
|
1608
|
+
} else {
|
|
1609
|
+
rest.push(q);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
this.queue = rest;
|
|
1613
|
+
return batch;
|
|
1614
|
+
}
|
|
1615
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1616
|
+
async send(batch) {
|
|
1617
|
+
const dest = batch[0].dest;
|
|
1618
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1619
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1620
|
+
const meta = {
|
|
1621
|
+
droppedSinceLast: reportedDropped,
|
|
1622
|
+
queueDepth: this.queue.length,
|
|
1623
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1624
|
+
version: SDK_VERSION
|
|
1625
|
+
};
|
|
1626
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1627
|
+
const response = await withBudget(
|
|
1628
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1629
|
+
method: "POST",
|
|
1630
|
+
headers: {
|
|
1631
|
+
"Content-Type": "application/json",
|
|
1632
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1633
|
+
},
|
|
1634
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1635
|
+
...controller && { signal: controller.signal }
|
|
1636
|
+
}),
|
|
1637
|
+
this.opts.requestTimeoutMs
|
|
1638
|
+
);
|
|
1639
|
+
if (!response) controller?.abort();
|
|
1640
|
+
const status = response?.status;
|
|
1641
|
+
for (const q of batch) q.inFlight = false;
|
|
1642
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1643
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1644
|
+
this.deliveredTotal += batch.length;
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
if (status === 401 && useKey) {
|
|
1648
|
+
this.rejectedKeys.add(useKey);
|
|
1649
|
+
this.requeue(batch, false);
|
|
1650
|
+
return true;
|
|
1651
|
+
}
|
|
1652
|
+
if (status === 429) {
|
|
1653
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1654
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1655
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1656
|
+
this.requeue(batch, false);
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1660
|
+
this.countDropped(batch.length);
|
|
1661
|
+
return true;
|
|
1662
|
+
}
|
|
1663
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1664
|
+
this.requeue(batch, true);
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1668
|
+
requeue(batch, countAttempt) {
|
|
1669
|
+
const keep = [];
|
|
1670
|
+
for (const q of batch) {
|
|
1671
|
+
if (countAttempt) q.attempts += 1;
|
|
1672
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1673
|
+
else keep.push(q);
|
|
1674
|
+
}
|
|
1675
|
+
this.queue = [...keep, ...this.queue];
|
|
1676
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1328
1680
|
function sampled(rate, random = Math.random) {
|
|
1329
1681
|
if (rate <= 0) return false;
|
|
1330
1682
|
if (rate >= 1) return true;
|
|
@@ -1942,6 +2294,20 @@ function createMiddleware(options) {
|
|
|
1942
2294
|
}
|
|
1943
2295
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
1944
2296
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2297
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
2298
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
2299
|
+
const passThrough = NextResponse.next();
|
|
2300
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2301
|
+
return passThrough;
|
|
2302
|
+
}
|
|
2303
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
2304
|
+
status: 503,
|
|
2305
|
+
headers: {
|
|
2306
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
2307
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
1945
2311
|
if (!result.policyAllowed || approvalRequired) {
|
|
1946
2312
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
1947
2313
|
return NextResponse.json(
|
|
@@ -1959,13 +2325,12 @@ function createMiddleware(options) {
|
|
|
1959
2325
|
);
|
|
1960
2326
|
}
|
|
1961
2327
|
if (pathname.startsWith("/api/")) {
|
|
2328
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
1962
2329
|
return NextResponse.json(
|
|
1963
2330
|
{
|
|
1964
2331
|
success: false,
|
|
1965
2332
|
error: {
|
|
1966
|
-
|
|
1967
|
-
// OK, policy denied (update PDLSS / step up).
|
|
1968
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
2333
|
+
code: denyStatus.code,
|
|
1969
2334
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1970
2335
|
guidance: result.guidance,
|
|
1971
2336
|
failures: result.failures,
|
|
@@ -1975,7 +2340,7 @@ function createMiddleware(options) {
|
|
|
1975
2340
|
}
|
|
1976
2341
|
},
|
|
1977
2342
|
{
|
|
1978
|
-
status:
|
|
2343
|
+
status: denyStatus.status,
|
|
1979
2344
|
...stepUpHoldOutcome && {
|
|
1980
2345
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
1981
2346
|
}
|