@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
package/dist/adapters/nextjs.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var SDK_VERSION = "5.
|
|
2
|
+
var SDK_VERSION = "5.13.0";
|
|
3
3
|
|
|
4
4
|
// src/http.ts
|
|
5
5
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -92,6 +92,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
92
92
|
return cache.get(apiBaseUrl)?.data;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// src/failure-posture.ts
|
|
96
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
97
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
98
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
99
|
+
"identity.invalid_agent_id",
|
|
100
|
+
"identity.unknown_agent",
|
|
101
|
+
"agent.lookup"
|
|
102
|
+
]);
|
|
103
|
+
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.";
|
|
104
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
105
|
+
"verify_access.api_error",
|
|
106
|
+
"middleware.internal_error"
|
|
107
|
+
]);
|
|
108
|
+
function isVerificationUnavailable(result) {
|
|
109
|
+
if (result.misconfigured) return false;
|
|
110
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
111
|
+
}
|
|
112
|
+
function unavailableRetryAfterSeconds(result) {
|
|
113
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
114
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
115
|
+
}
|
|
116
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
117
|
+
}
|
|
118
|
+
function isAnonymousRead(method, credentials) {
|
|
119
|
+
const m = (method ?? "").toUpperCase();
|
|
120
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
121
|
+
return !credentials.astraId && !credentials.jwt;
|
|
122
|
+
}
|
|
123
|
+
function buildVerificationUnavailableBody(result) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: {
|
|
127
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
128
|
+
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.",
|
|
129
|
+
retryable: true,
|
|
130
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
131
|
+
...result.failures && { failures: result.failures },
|
|
132
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
133
|
+
guidance: {
|
|
134
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function isRejectedIdentityClaim(result) {
|
|
140
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
141
|
+
}
|
|
142
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
143
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
144
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
145
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
// src/verify.ts
|
|
96
149
|
var DEFAULT_CONFIG = {
|
|
97
150
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -104,7 +157,7 @@ var DEFAULT_CONFIG = {
|
|
|
104
157
|
};
|
|
105
158
|
var initCheckPerformed = false;
|
|
106
159
|
var deprecationWarningShown = false;
|
|
107
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
160
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
108
161
|
initCheckPerformed = true;
|
|
109
162
|
try {
|
|
110
163
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -122,7 +175,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
122
175
|
);
|
|
123
176
|
}
|
|
124
177
|
if (apiKey) {
|
|
125
|
-
const
|
|
178
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
179
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
126
180
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
127
181
|
});
|
|
128
182
|
if (selfCheck.status === 401) {
|
|
@@ -134,6 +188,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
134
188
|
console.error(message);
|
|
135
189
|
} else if (selfCheck.ok) {
|
|
136
190
|
const body = await selfCheck.json().catch(() => ({}));
|
|
191
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
192
|
+
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.`;
|
|
193
|
+
if (strictInit) {
|
|
194
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
195
|
+
}
|
|
196
|
+
console.error(message);
|
|
197
|
+
}
|
|
137
198
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
138
199
|
console.warn(
|
|
139
200
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -272,11 +333,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
272
333
|
const isMisconfig = source === "merchant_misconfig";
|
|
273
334
|
const urls = options.urls;
|
|
274
335
|
const guidance = isMisconfig ? {
|
|
275
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
336
|
+
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.",
|
|
276
337
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
277
338
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
278
339
|
steps: [
|
|
279
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
340
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
280
341
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
281
342
|
]
|
|
282
343
|
} : isApiError ? {
|
|
@@ -337,11 +398,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
337
398
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
338
399
|
// deterministic and intentionally NOT marked retryable.
|
|
339
400
|
...isApiError && { retryable: true },
|
|
401
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
402
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
340
403
|
correlationId: options.correlationId,
|
|
341
404
|
attemptId: options.attemptId,
|
|
342
405
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
343
406
|
};
|
|
344
407
|
}
|
|
408
|
+
async function readJsonSafe(response) {
|
|
409
|
+
try {
|
|
410
|
+
const parsed = await response.json();
|
|
411
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
412
|
+
} catch {
|
|
413
|
+
return {};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function rejectedIdentityGuidance(failures) {
|
|
417
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
418
|
+
if (!f) return void 0;
|
|
419
|
+
if (f.dimension === "agent.lookup") {
|
|
420
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
421
|
+
}
|
|
422
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
423
|
+
}
|
|
424
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
425
|
+
function parseRetryAfterMs(response) {
|
|
426
|
+
const raw = response.headers?.get?.("retry-after");
|
|
427
|
+
if (!raw) return void 0;
|
|
428
|
+
const trimmed = raw.trim();
|
|
429
|
+
let ms;
|
|
430
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
431
|
+
ms = Number(trimmed) * 1e3;
|
|
432
|
+
} else {
|
|
433
|
+
const at = Date.parse(trimmed);
|
|
434
|
+
if (Number.isNaN(at)) return void 0;
|
|
435
|
+
ms = at - Date.now();
|
|
436
|
+
}
|
|
437
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
438
|
+
}
|
|
345
439
|
async function callVerifyAccessAPI(config, request) {
|
|
346
440
|
const { credentials, ...requestData } = request;
|
|
347
441
|
const body = {
|
|
@@ -398,7 +492,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
398
492
|
body: JSON.stringify(body),
|
|
399
493
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
400
494
|
});
|
|
401
|
-
const data = await response
|
|
495
|
+
const data = await readJsonSafe(response);
|
|
496
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
402
497
|
if (response.status === 410) {
|
|
403
498
|
return {
|
|
404
499
|
success: true,
|
|
@@ -436,13 +531,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
436
531
|
}
|
|
437
532
|
};
|
|
438
533
|
}
|
|
534
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
535
|
+
return {
|
|
536
|
+
success: true,
|
|
537
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
538
|
+
access: {
|
|
539
|
+
allowed: false,
|
|
540
|
+
reason: "unknown_agent",
|
|
541
|
+
failures: [
|
|
542
|
+
{
|
|
543
|
+
dimension: "identity.unknown_agent",
|
|
544
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
545
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
546
|
+
}
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
439
551
|
if (!response.ok) {
|
|
440
|
-
|
|
552
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
553
|
+
if (response.status === 401 || keyMismatch) {
|
|
441
554
|
return {
|
|
442
555
|
success: false,
|
|
443
556
|
error: data.message || data.error || "Integration API key rejected",
|
|
444
557
|
misconfig: true,
|
|
445
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
558
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
446
559
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
447
560
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
448
561
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -450,9 +563,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
450
563
|
}
|
|
451
564
|
return {
|
|
452
565
|
success: false,
|
|
453
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
566
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
454
567
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
455
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
568
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
569
|
+
httpStatus: response.status,
|
|
570
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
456
571
|
};
|
|
457
572
|
}
|
|
458
573
|
return data;
|
|
@@ -473,14 +588,22 @@ async function verify(config, request, options) {
|
|
|
473
588
|
mergedConfig.apiBaseUrl,
|
|
474
589
|
mergedConfig.debug,
|
|
475
590
|
true,
|
|
476
|
-
mergedConfig.apiKey
|
|
591
|
+
mergedConfig.apiKey,
|
|
592
|
+
{
|
|
593
|
+
id: mergedConfig.counterpartyId,
|
|
594
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
595
|
+
}
|
|
477
596
|
);
|
|
478
597
|
} else {
|
|
479
598
|
void performInitCheck(
|
|
480
599
|
mergedConfig.apiBaseUrl,
|
|
481
600
|
mergedConfig.debug,
|
|
482
601
|
false,
|
|
483
|
-
mergedConfig.apiKey
|
|
602
|
+
mergedConfig.apiKey,
|
|
603
|
+
{
|
|
604
|
+
id: mergedConfig.counterpartyId,
|
|
605
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
606
|
+
}
|
|
484
607
|
);
|
|
485
608
|
}
|
|
486
609
|
}
|
|
@@ -524,12 +647,14 @@ async function verify(config, request, options) {
|
|
|
524
647
|
correlationId: apiResponse.correlationId,
|
|
525
648
|
attemptId: apiResponse.attemptId,
|
|
526
649
|
remediation: apiResponse.remediation,
|
|
650
|
+
httpStatus: apiResponse.httpStatus,
|
|
651
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
527
652
|
urls
|
|
528
653
|
});
|
|
529
654
|
}
|
|
530
655
|
if (!apiResponse.access?.allowed) {
|
|
531
656
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
532
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
657
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
533
658
|
const result2 = {
|
|
534
659
|
identityVerified: idVerifiedFromBackend,
|
|
535
660
|
policyAllowed: false,
|
|
@@ -541,7 +666,10 @@ async function verify(config, request, options) {
|
|
|
541
666
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
542
667
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
543
668
|
guidance: {
|
|
544
|
-
|
|
669
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
670
|
+
// actionable registration guidance on its failure — surface it
|
|
671
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
672
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
545
673
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
546
674
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
547
675
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -641,7 +769,7 @@ async function verify(config, request, options) {
|
|
|
641
769
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
642
770
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
643
771
|
};
|
|
644
|
-
} else if (result.recommendation === "step_up_required") {
|
|
772
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
645
773
|
result.requiresStepUp = true;
|
|
646
774
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
647
775
|
}
|
|
@@ -651,6 +779,7 @@ async function verify(config, request, options) {
|
|
|
651
779
|
}
|
|
652
780
|
return result;
|
|
653
781
|
}
|
|
782
|
+
var refusedPolicyReads = /* @__PURE__ */ new Set();
|
|
654
783
|
async function fetchRoutes(config, counterpartyId) {
|
|
655
784
|
if (!counterpartyId) return null;
|
|
656
785
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -663,7 +792,15 @@ async function fetchRoutes(config, counterpartyId) {
|
|
|
663
792
|
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
|
|
664
793
|
{ method: "GET", headers }
|
|
665
794
|
);
|
|
666
|
-
if (!response.ok)
|
|
795
|
+
if (!response.ok) {
|
|
796
|
+
if ((response.status === 401 || response.status === 403 || response.status === 404) && !refusedPolicyReads.has(counterpartyId)) {
|
|
797
|
+
refusedPolicyReads.add(counterpartyId);
|
|
798
|
+
console.error(
|
|
799
|
+
`[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.`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
667
804
|
const body = await response.json();
|
|
668
805
|
return body.data?.routes ?? [];
|
|
669
806
|
} catch {
|
|
@@ -680,7 +817,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
680
817
|
};
|
|
681
818
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
682
819
|
method: "POST",
|
|
683
|
-
headers: {
|
|
820
|
+
headers: {
|
|
821
|
+
"Content-Type": "application/json",
|
|
822
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
823
|
+
},
|
|
684
824
|
body: JSON.stringify(body)
|
|
685
825
|
}).catch(() => {
|
|
686
826
|
});
|
|
@@ -1153,7 +1293,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1153
1293
|
{
|
|
1154
1294
|
vendor: "chatgpt",
|
|
1155
1295
|
displayName: "OpenAI ChatGPT",
|
|
1156
|
-
|
|
1296
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1297
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1298
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1299
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1157
1300
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1158
1301
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1159
1302
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1288,6 +1431,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1288
1431
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1289
1432
|
|
|
1290
1433
|
// src/edge-core/events.ts
|
|
1434
|
+
async function withBudget(promise, ms) {
|
|
1435
|
+
let timer;
|
|
1436
|
+
try {
|
|
1437
|
+
return await Promise.race([
|
|
1438
|
+
promise,
|
|
1439
|
+
new Promise((resolve) => {
|
|
1440
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1441
|
+
})
|
|
1442
|
+
]);
|
|
1443
|
+
} catch {
|
|
1444
|
+
return void 0;
|
|
1445
|
+
} finally {
|
|
1446
|
+
if (timer) clearTimeout(timer);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1450
|
+
var BeaconBuffer = class {
|
|
1451
|
+
constructor(options = {}) {
|
|
1452
|
+
this.queue = [];
|
|
1453
|
+
this.droppedSinceLast = 0;
|
|
1454
|
+
this.droppedTotal = 0;
|
|
1455
|
+
this.deliveredTotal = 0;
|
|
1456
|
+
this.backoffUntil = 0;
|
|
1457
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1458
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1459
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1460
|
+
this.opts = {
|
|
1461
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1462
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1463
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1464
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1465
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1466
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1467
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1468
|
+
now: options.now ?? Date.now
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1473
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1474
|
+
* becoming its own item.
|
|
1475
|
+
*/
|
|
1476
|
+
enqueue(dest, item, collapseKey) {
|
|
1477
|
+
const now = this.opts.now();
|
|
1478
|
+
if (collapseKey) {
|
|
1479
|
+
const entry = this.collapse.get(collapseKey);
|
|
1480
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1481
|
+
for (const target of [entry.first, entry.held]) {
|
|
1482
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1483
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
const held = this.push(
|
|
1488
|
+
dest,
|
|
1489
|
+
{ ...item, repeatCount: 1 },
|
|
1490
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1491
|
+
);
|
|
1492
|
+
entry.held = held;
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
this.collapse.delete(collapseKey);
|
|
1496
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1497
|
+
const oldest = this.collapse.keys().next().value;
|
|
1498
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1499
|
+
}
|
|
1500
|
+
const fresh = { firstSeenAt: now };
|
|
1501
|
+
this.collapse.set(collapseKey, fresh);
|
|
1502
|
+
fresh.first = this.push(dest, item, now);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
this.push(dest, item, now);
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1509
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1510
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1511
|
+
* picked up by the same drain.
|
|
1512
|
+
*/
|
|
1513
|
+
flush() {
|
|
1514
|
+
if (!this.inFlight) {
|
|
1515
|
+
this.inFlight = this.drain().finally(() => {
|
|
1516
|
+
this.inFlight = void 0;
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
return this.inFlight;
|
|
1520
|
+
}
|
|
1521
|
+
/** Counters for tests and diagnostics. */
|
|
1522
|
+
stats() {
|
|
1523
|
+
return {
|
|
1524
|
+
queueDepth: this.queue.length,
|
|
1525
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1526
|
+
droppedTotal: this.droppedTotal,
|
|
1527
|
+
deliveredTotal: this.deliveredTotal,
|
|
1528
|
+
flushing: this.inFlight !== void 0
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
push(dest, item, eligibleAt) {
|
|
1532
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1533
|
+
const entry = {
|
|
1534
|
+
dest,
|
|
1535
|
+
destKey: destKeyOf(dest),
|
|
1536
|
+
item,
|
|
1537
|
+
attempts: 0,
|
|
1538
|
+
eligibleAt,
|
|
1539
|
+
inFlight: false
|
|
1540
|
+
};
|
|
1541
|
+
this.queue.push(entry);
|
|
1542
|
+
return entry;
|
|
1543
|
+
}
|
|
1544
|
+
dropOldest() {
|
|
1545
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1546
|
+
}
|
|
1547
|
+
countDropped(n) {
|
|
1548
|
+
this.droppedSinceLast += n;
|
|
1549
|
+
this.droppedTotal += n;
|
|
1550
|
+
}
|
|
1551
|
+
async drain() {
|
|
1552
|
+
for (; ; ) {
|
|
1553
|
+
const now = this.opts.now();
|
|
1554
|
+
if (now < this.backoffUntil) return;
|
|
1555
|
+
const batch = this.takeBatch(now);
|
|
1556
|
+
if (batch.length === 0) return;
|
|
1557
|
+
const ok = await this.send(batch);
|
|
1558
|
+
if (!ok) return;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1562
|
+
takeBatch(now) {
|
|
1563
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1564
|
+
if (!first) return [];
|
|
1565
|
+
const batch = [];
|
|
1566
|
+
const rest = [];
|
|
1567
|
+
for (const q of this.queue) {
|
|
1568
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1569
|
+
q.inFlight = true;
|
|
1570
|
+
batch.push(q);
|
|
1571
|
+
} else {
|
|
1572
|
+
rest.push(q);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
this.queue = rest;
|
|
1576
|
+
return batch;
|
|
1577
|
+
}
|
|
1578
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1579
|
+
async send(batch) {
|
|
1580
|
+
const dest = batch[0].dest;
|
|
1581
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1582
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1583
|
+
const meta = {
|
|
1584
|
+
droppedSinceLast: reportedDropped,
|
|
1585
|
+
queueDepth: this.queue.length,
|
|
1586
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1587
|
+
version: SDK_VERSION
|
|
1588
|
+
};
|
|
1589
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1590
|
+
const response = await withBudget(
|
|
1591
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1592
|
+
method: "POST",
|
|
1593
|
+
headers: {
|
|
1594
|
+
"Content-Type": "application/json",
|
|
1595
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1596
|
+
},
|
|
1597
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1598
|
+
...controller && { signal: controller.signal }
|
|
1599
|
+
}),
|
|
1600
|
+
this.opts.requestTimeoutMs
|
|
1601
|
+
);
|
|
1602
|
+
if (!response) controller?.abort();
|
|
1603
|
+
const status = response?.status;
|
|
1604
|
+
for (const q of batch) q.inFlight = false;
|
|
1605
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1606
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1607
|
+
this.deliveredTotal += batch.length;
|
|
1608
|
+
return true;
|
|
1609
|
+
}
|
|
1610
|
+
if (status === 401 && useKey) {
|
|
1611
|
+
this.rejectedKeys.add(useKey);
|
|
1612
|
+
this.requeue(batch, false);
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1615
|
+
if (status === 429) {
|
|
1616
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1617
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1618
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1619
|
+
this.requeue(batch, false);
|
|
1620
|
+
return false;
|
|
1621
|
+
}
|
|
1622
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1623
|
+
this.countDropped(batch.length);
|
|
1624
|
+
return true;
|
|
1625
|
+
}
|
|
1626
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1627
|
+
this.requeue(batch, true);
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1631
|
+
requeue(batch, countAttempt) {
|
|
1632
|
+
const keep = [];
|
|
1633
|
+
for (const q of batch) {
|
|
1634
|
+
if (countAttempt) q.attempts += 1;
|
|
1635
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1636
|
+
else keep.push(q);
|
|
1637
|
+
}
|
|
1638
|
+
this.queue = [...keep, ...this.queue];
|
|
1639
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1291
1643
|
function sampled(rate, random = Math.random) {
|
|
1292
1644
|
if (rate <= 0) return false;
|
|
1293
1645
|
if (rate >= 1) return true;
|
|
@@ -1905,6 +2257,20 @@ function createMiddleware(options) {
|
|
|
1905
2257
|
}
|
|
1906
2258
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
1907
2259
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2260
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
2261
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
2262
|
+
const passThrough = NextResponse.next();
|
|
2263
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2264
|
+
return passThrough;
|
|
2265
|
+
}
|
|
2266
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
2267
|
+
status: 503,
|
|
2268
|
+
headers: {
|
|
2269
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
2270
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
1908
2274
|
if (!result.policyAllowed || approvalRequired) {
|
|
1909
2275
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
1910
2276
|
return NextResponse.json(
|
|
@@ -1922,13 +2288,12 @@ function createMiddleware(options) {
|
|
|
1922
2288
|
);
|
|
1923
2289
|
}
|
|
1924
2290
|
if (pathname.startsWith("/api/")) {
|
|
2291
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
1925
2292
|
return NextResponse.json(
|
|
1926
2293
|
{
|
|
1927
2294
|
success: false,
|
|
1928
2295
|
error: {
|
|
1929
|
-
|
|
1930
|
-
// OK, policy denied (update PDLSS / step up).
|
|
1931
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
2296
|
+
code: denyStatus.code,
|
|
1932
2297
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1933
2298
|
guidance: result.guidance,
|
|
1934
2299
|
failures: result.failures,
|
|
@@ -1938,7 +2303,7 @@ function createMiddleware(options) {
|
|
|
1938
2303
|
}
|
|
1939
2304
|
},
|
|
1940
2305
|
{
|
|
1941
|
-
status:
|
|
2306
|
+
status: denyStatus.status,
|
|
1942
2307
|
...stepUpHoldOutcome && {
|
|
1943
2308
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
1944
2309
|
}
|