@astrasyncai/verification-gateway 5.11.0 → 5.12.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 +359 -30
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +359 -30
- 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 +168 -41
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +168 -41
- 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 +352 -13
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +352 -13
- 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 +100 -8
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +100 -8
- 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 +284 -7
- package/dist/bin/astrasync-codex-hook.js +284 -7
- package/dist/bin/astrasync-guard.js +284 -7
- package/dist/bin/astrasync.js +297 -9
- package/dist/browser/background.js +284 -7
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +284 -7
- 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 +284 -7
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +284 -7
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +284 -7
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +284 -7
- 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 +337 -55
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +334 -55
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +284 -7
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +284 -7
- 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 -5
- package/dist/index.d.ts +118 -5
- package/dist/index.js +486 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +480 -63
- 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 -4
- package/dist/verify.d.ts +35 -4
- package/dist/verify.js +130 -16
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +129 -16
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/edge-core/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var edge_core_exports = {};
|
|
22
22
|
__export(edge_core_exports, {
|
|
23
23
|
ASAT_HEADERS: () => ASAT_HEADERS,
|
|
24
|
+
BeaconBuffer: () => BeaconBuffer,
|
|
24
25
|
_patterns: () => _patterns,
|
|
25
26
|
applyOutcome: () => applyOutcome,
|
|
26
27
|
buildDenyBody: () => buildDenyBody,
|
|
@@ -31,10 +32,12 @@ __export(edge_core_exports, {
|
|
|
31
32
|
detectCommerce: () => detectCommerce,
|
|
32
33
|
emitBeacon: () => emitBeacon,
|
|
33
34
|
evaluateEdgeRequest: () => evaluateEdgeRequest,
|
|
35
|
+
getBeaconBuffer: () => getBeaconBuffer,
|
|
34
36
|
headerValue: () => headerValue,
|
|
35
37
|
outcomeFromVerifyResult: () => outcomeFromVerifyResult,
|
|
36
38
|
passUntouched: () => passUntouched,
|
|
37
39
|
requestUrl: () => requestUrl,
|
|
40
|
+
resetBeaconBuffer: () => resetBeaconBuffer,
|
|
38
41
|
resolvePolicy: () => resolvePolicy,
|
|
39
42
|
runTier: () => runTier,
|
|
40
43
|
sampled: () => sampled,
|
|
@@ -43,7 +46,7 @@ __export(edge_core_exports, {
|
|
|
43
46
|
module.exports = __toCommonJS(edge_core_exports);
|
|
44
47
|
|
|
45
48
|
// src/version.ts
|
|
46
|
-
var SDK_VERSION = "5.
|
|
49
|
+
var SDK_VERSION = "5.12.0";
|
|
47
50
|
|
|
48
51
|
// src/http.ts
|
|
49
52
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -256,7 +259,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
256
259
|
{
|
|
257
260
|
vendor: "chatgpt",
|
|
258
261
|
displayName: "OpenAI ChatGPT",
|
|
259
|
-
|
|
262
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
263
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
264
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
265
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
260
266
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
261
267
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
262
268
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -398,6 +404,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
398
404
|
return cache.get(apiBaseUrl)?.data;
|
|
399
405
|
}
|
|
400
406
|
|
|
407
|
+
// src/failure-posture.ts
|
|
408
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
409
|
+
"identity.invalid_agent_id",
|
|
410
|
+
"identity.unknown_agent",
|
|
411
|
+
"agent.lookup"
|
|
412
|
+
]);
|
|
413
|
+
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.";
|
|
414
|
+
|
|
401
415
|
// src/verify.ts
|
|
402
416
|
var DEFAULT_CONFIG = {
|
|
403
417
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -643,11 +657,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
643
657
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
644
658
|
// deterministic and intentionally NOT marked retryable.
|
|
645
659
|
...isApiError && { retryable: true },
|
|
660
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
661
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
646
662
|
correlationId: options.correlationId,
|
|
647
663
|
attemptId: options.attemptId,
|
|
648
664
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
649
665
|
};
|
|
650
666
|
}
|
|
667
|
+
async function readJsonSafe(response) {
|
|
668
|
+
try {
|
|
669
|
+
const parsed = await response.json();
|
|
670
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
671
|
+
} catch {
|
|
672
|
+
return {};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function rejectedIdentityGuidance(failures) {
|
|
676
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
677
|
+
if (!f) return void 0;
|
|
678
|
+
if (f.dimension === "agent.lookup") {
|
|
679
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
680
|
+
}
|
|
681
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
682
|
+
}
|
|
683
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
684
|
+
function parseRetryAfterMs(response) {
|
|
685
|
+
const raw = response.headers?.get?.("retry-after");
|
|
686
|
+
if (!raw) return void 0;
|
|
687
|
+
const trimmed = raw.trim();
|
|
688
|
+
let ms;
|
|
689
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
690
|
+
ms = Number(trimmed) * 1e3;
|
|
691
|
+
} else {
|
|
692
|
+
const at = Date.parse(trimmed);
|
|
693
|
+
if (Number.isNaN(at)) return void 0;
|
|
694
|
+
ms = at - Date.now();
|
|
695
|
+
}
|
|
696
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
697
|
+
}
|
|
651
698
|
async function callVerifyAccessAPI(config, request) {
|
|
652
699
|
const { credentials, ...requestData } = request;
|
|
653
700
|
const body = {
|
|
@@ -704,7 +751,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
704
751
|
body: JSON.stringify(body),
|
|
705
752
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
706
753
|
});
|
|
707
|
-
const data = await response
|
|
754
|
+
const data = await readJsonSafe(response);
|
|
755
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
708
756
|
if (response.status === 410) {
|
|
709
757
|
return {
|
|
710
758
|
success: true,
|
|
@@ -742,6 +790,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
742
790
|
}
|
|
743
791
|
};
|
|
744
792
|
}
|
|
793
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
794
|
+
return {
|
|
795
|
+
success: true,
|
|
796
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
797
|
+
access: {
|
|
798
|
+
allowed: false,
|
|
799
|
+
reason: "unknown_agent",
|
|
800
|
+
failures: [
|
|
801
|
+
{
|
|
802
|
+
dimension: "identity.unknown_agent",
|
|
803
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
804
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
}
|
|
745
810
|
if (!response.ok) {
|
|
746
811
|
if (response.status === 401) {
|
|
747
812
|
return {
|
|
@@ -756,9 +821,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
756
821
|
}
|
|
757
822
|
return {
|
|
758
823
|
success: false,
|
|
759
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
824
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
760
825
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
761
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
826
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
827
|
+
httpStatus: response.status,
|
|
828
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
762
829
|
};
|
|
763
830
|
}
|
|
764
831
|
return data;
|
|
@@ -830,12 +897,14 @@ async function verify(config, request, options) {
|
|
|
830
897
|
correlationId: apiResponse.correlationId,
|
|
831
898
|
attemptId: apiResponse.attemptId,
|
|
832
899
|
remediation: apiResponse.remediation,
|
|
900
|
+
httpStatus: apiResponse.httpStatus,
|
|
901
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
833
902
|
urls
|
|
834
903
|
});
|
|
835
904
|
}
|
|
836
905
|
if (!apiResponse.access?.allowed) {
|
|
837
906
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
838
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
907
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
839
908
|
const result2 = {
|
|
840
909
|
identityVerified: idVerifiedFromBackend,
|
|
841
910
|
policyAllowed: false,
|
|
@@ -847,7 +916,10 @@ async function verify(config, request, options) {
|
|
|
847
916
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
848
917
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
849
918
|
guidance: {
|
|
850
|
-
|
|
919
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
920
|
+
// actionable registration guidance on its failure — surface it
|
|
921
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
922
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
851
923
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
852
924
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
853
925
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -947,7 +1019,7 @@ async function verify(config, request, options) {
|
|
|
947
1019
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
948
1020
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
949
1021
|
};
|
|
950
|
-
} else if (result.recommendation === "step_up_required") {
|
|
1022
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
951
1023
|
result.requiresStepUp = true;
|
|
952
1024
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
953
1025
|
}
|
|
@@ -957,21 +1029,6 @@ async function verify(config, request, options) {
|
|
|
957
1029
|
}
|
|
958
1030
|
return result;
|
|
959
1031
|
}
|
|
960
|
-
async function reportUnregisteredAttempt(config, data) {
|
|
961
|
-
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
962
|
-
const { counterpartyUrl, ...rest } = data;
|
|
963
|
-
const body = {
|
|
964
|
-
...rest,
|
|
965
|
-
...counterpartyUrl && { counterpartyUrl },
|
|
966
|
-
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
967
|
-
};
|
|
968
|
-
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
969
|
-
method: "POST",
|
|
970
|
-
headers: { "Content-Type": "application/json" },
|
|
971
|
-
body: JSON.stringify(body)
|
|
972
|
-
}).catch(() => {
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
1032
|
|
|
976
1033
|
// src/edge-core/request.ts
|
|
977
1034
|
function headerValue(req, name) {
|
|
@@ -1507,42 +1564,263 @@ async function withBudget(promise, ms) {
|
|
|
1507
1564
|
if (timer) clearTimeout(timer);
|
|
1508
1565
|
}
|
|
1509
1566
|
}
|
|
1510
|
-
|
|
1567
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1568
|
+
var BeaconBuffer = class {
|
|
1569
|
+
constructor(options = {}) {
|
|
1570
|
+
this.queue = [];
|
|
1571
|
+
this.droppedSinceLast = 0;
|
|
1572
|
+
this.droppedTotal = 0;
|
|
1573
|
+
this.deliveredTotal = 0;
|
|
1574
|
+
this.backoffUntil = 0;
|
|
1575
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1576
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1577
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1578
|
+
this.opts = {
|
|
1579
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1580
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1581
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1582
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1583
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1584
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1585
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1586
|
+
now: options.now ?? Date.now
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1591
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1592
|
+
* becoming its own item.
|
|
1593
|
+
*/
|
|
1594
|
+
enqueue(dest, item, collapseKey) {
|
|
1595
|
+
const now = this.opts.now();
|
|
1596
|
+
if (collapseKey) {
|
|
1597
|
+
const entry = this.collapse.get(collapseKey);
|
|
1598
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1599
|
+
for (const target of [entry.first, entry.held]) {
|
|
1600
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1601
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const held = this.push(
|
|
1606
|
+
dest,
|
|
1607
|
+
{ ...item, repeatCount: 1 },
|
|
1608
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1609
|
+
);
|
|
1610
|
+
entry.held = held;
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
this.collapse.delete(collapseKey);
|
|
1614
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1615
|
+
const oldest = this.collapse.keys().next().value;
|
|
1616
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1617
|
+
}
|
|
1618
|
+
const fresh = { firstSeenAt: now };
|
|
1619
|
+
this.collapse.set(collapseKey, fresh);
|
|
1620
|
+
fresh.first = this.push(dest, item, now);
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
this.push(dest, item, now);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1627
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1628
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1629
|
+
* picked up by the same drain.
|
|
1630
|
+
*/
|
|
1631
|
+
flush() {
|
|
1632
|
+
if (!this.inFlight) {
|
|
1633
|
+
this.inFlight = this.drain().finally(() => {
|
|
1634
|
+
this.inFlight = void 0;
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
return this.inFlight;
|
|
1638
|
+
}
|
|
1639
|
+
/** Counters for tests and diagnostics. */
|
|
1640
|
+
stats() {
|
|
1641
|
+
return {
|
|
1642
|
+
queueDepth: this.queue.length,
|
|
1643
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1644
|
+
droppedTotal: this.droppedTotal,
|
|
1645
|
+
deliveredTotal: this.deliveredTotal,
|
|
1646
|
+
flushing: this.inFlight !== void 0
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
push(dest, item, eligibleAt) {
|
|
1650
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1651
|
+
const entry = {
|
|
1652
|
+
dest,
|
|
1653
|
+
destKey: destKeyOf(dest),
|
|
1654
|
+
item,
|
|
1655
|
+
attempts: 0,
|
|
1656
|
+
eligibleAt,
|
|
1657
|
+
inFlight: false
|
|
1658
|
+
};
|
|
1659
|
+
this.queue.push(entry);
|
|
1660
|
+
return entry;
|
|
1661
|
+
}
|
|
1662
|
+
dropOldest() {
|
|
1663
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1664
|
+
}
|
|
1665
|
+
countDropped(n) {
|
|
1666
|
+
this.droppedSinceLast += n;
|
|
1667
|
+
this.droppedTotal += n;
|
|
1668
|
+
}
|
|
1669
|
+
async drain() {
|
|
1670
|
+
for (; ; ) {
|
|
1671
|
+
const now = this.opts.now();
|
|
1672
|
+
if (now < this.backoffUntil) return;
|
|
1673
|
+
const batch = this.takeBatch(now);
|
|
1674
|
+
if (batch.length === 0) return;
|
|
1675
|
+
const ok = await this.send(batch);
|
|
1676
|
+
if (!ok) return;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1680
|
+
takeBatch(now) {
|
|
1681
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1682
|
+
if (!first) return [];
|
|
1683
|
+
const batch = [];
|
|
1684
|
+
const rest = [];
|
|
1685
|
+
for (const q of this.queue) {
|
|
1686
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1687
|
+
q.inFlight = true;
|
|
1688
|
+
batch.push(q);
|
|
1689
|
+
} else {
|
|
1690
|
+
rest.push(q);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
this.queue = rest;
|
|
1694
|
+
return batch;
|
|
1695
|
+
}
|
|
1696
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1697
|
+
async send(batch) {
|
|
1698
|
+
const dest = batch[0].dest;
|
|
1699
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1700
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1701
|
+
const meta = {
|
|
1702
|
+
droppedSinceLast: reportedDropped,
|
|
1703
|
+
queueDepth: this.queue.length,
|
|
1704
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1705
|
+
version: SDK_VERSION
|
|
1706
|
+
};
|
|
1707
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1708
|
+
const response = await withBudget(
|
|
1709
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1710
|
+
method: "POST",
|
|
1711
|
+
headers: {
|
|
1712
|
+
"Content-Type": "application/json",
|
|
1713
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1714
|
+
},
|
|
1715
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1716
|
+
...controller && { signal: controller.signal }
|
|
1717
|
+
}),
|
|
1718
|
+
this.opts.requestTimeoutMs
|
|
1719
|
+
);
|
|
1720
|
+
if (!response) controller?.abort();
|
|
1721
|
+
const status = response?.status;
|
|
1722
|
+
for (const q of batch) q.inFlight = false;
|
|
1723
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1724
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1725
|
+
this.deliveredTotal += batch.length;
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
if (status === 401 && useKey) {
|
|
1729
|
+
this.rejectedKeys.add(useKey);
|
|
1730
|
+
this.requeue(batch, false);
|
|
1731
|
+
return true;
|
|
1732
|
+
}
|
|
1733
|
+
if (status === 429) {
|
|
1734
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1735
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1736
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1737
|
+
this.requeue(batch, false);
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1740
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1741
|
+
this.countDropped(batch.length);
|
|
1742
|
+
return true;
|
|
1743
|
+
}
|
|
1744
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1745
|
+
this.requeue(batch, true);
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1749
|
+
requeue(batch, countAttempt) {
|
|
1750
|
+
const keep = [];
|
|
1751
|
+
for (const q of batch) {
|
|
1752
|
+
if (countAttempt) q.attempts += 1;
|
|
1753
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1754
|
+
else keep.push(q);
|
|
1755
|
+
}
|
|
1756
|
+
this.queue = [...keep, ...this.queue];
|
|
1757
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1761
|
+
function getBeaconBuffer() {
|
|
1762
|
+
return beaconBuffer;
|
|
1763
|
+
}
|
|
1764
|
+
function resetBeaconBuffer(options) {
|
|
1765
|
+
beaconBuffer = new BeaconBuffer(options);
|
|
1766
|
+
return beaconBuffer;
|
|
1767
|
+
}
|
|
1768
|
+
async function emitBeacon(options, req, classification, budgetMs, queryValueAllowlist, apiKey) {
|
|
1511
1769
|
const attemptId = extractAttemptId(req);
|
|
1512
1770
|
const declaredSource = normalizeDeclaredSource(headerValue(req, "x-astra-source"));
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1771
|
+
const userAgent = headerValue(req, "user-agent");
|
|
1772
|
+
const item = {
|
|
1773
|
+
...options.counterpartyId && { counterpartyId: options.counterpartyId },
|
|
1774
|
+
...options.counterpartyUrl && { counterpartyUrl: options.counterpartyUrl },
|
|
1775
|
+
counterpartyType: "website",
|
|
1776
|
+
sourceIp: req.clientIp,
|
|
1777
|
+
userAgent,
|
|
1778
|
+
requestPath: req.uri,
|
|
1779
|
+
requestMethod: req.method,
|
|
1780
|
+
// Attempt-chain handle (header or ?astra_attempt= deep link) — joins
|
|
1781
|
+
// this beacon into the agent's research chain via pe_attempt_idx.
|
|
1782
|
+
...attemptId && { attemptId },
|
|
1783
|
+
// First-class referer + accept: traffic-source and surface-classifier
|
|
1784
|
+
// inputs (previously buried inside observedMetadata.headers).
|
|
1785
|
+
referer: headerValue(req, "referer"),
|
|
1786
|
+
accept: headerValue(req, "accept"),
|
|
1787
|
+
...declaredSource && { declaredSource },
|
|
1788
|
+
// Evidence fields the backend persists into the event record.
|
|
1789
|
+
protocol: classification.protocol,
|
|
1790
|
+
evidence: classification.evidence,
|
|
1791
|
+
// The full sanitized header + connection capture for the unregistered
|
|
1792
|
+
// caller.
|
|
1793
|
+
observedMetadata: buildObservedMetadata(req, queryValueAllowlist),
|
|
1794
|
+
// Capture time — batched delivery skews createdAt by the flush delay.
|
|
1795
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1796
|
+
};
|
|
1797
|
+
for (const k of Object.keys(item)) if (item[k] === void 0) delete item[k];
|
|
1798
|
+
const buffer = getBeaconBuffer();
|
|
1799
|
+
buffer.enqueue(
|
|
1800
|
+
{
|
|
1801
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1802
|
+
...apiKey && { apiKey },
|
|
1803
|
+
counterparty: options.counterpartyId || options.counterpartyUrl,
|
|
1804
|
+
...options.adapter && { adapter: options.adapter }
|
|
1805
|
+
},
|
|
1806
|
+
item,
|
|
1807
|
+
// Repeat collapse needs a client identity; without an IP, distinct
|
|
1808
|
+
// callers sharing a UA would merge, so don't collapse.
|
|
1809
|
+
req.clientIp ? [
|
|
1810
|
+
options.counterpartyId || options.counterpartyUrl,
|
|
1811
|
+
req.clientIp,
|
|
1812
|
+
userAgent ?? "",
|
|
1813
|
+
req.method,
|
|
1814
|
+
req.uri,
|
|
1815
|
+
attemptId ?? ""
|
|
1816
|
+
].join("\0") : void 0
|
|
1540
1817
|
);
|
|
1818
|
+
const flushing = buffer.flush();
|
|
1541
1819
|
if (options.waitUntil) {
|
|
1542
|
-
options.waitUntil(
|
|
1820
|
+
options.waitUntil(flushing);
|
|
1543
1821
|
return;
|
|
1544
1822
|
}
|
|
1545
|
-
await
|
|
1823
|
+
await withBudget(flushing, budgetMs);
|
|
1546
1824
|
}
|
|
1547
1825
|
function sampled(rate, random = Math.random) {
|
|
1548
1826
|
if (rate <= 0) return false;
|
|
@@ -1707,7 +1985,7 @@ async function runTier(options, req, classification, policy, apiKey, capture) {
|
|
|
1707
1985
|
}
|
|
1708
1986
|
return outcomeFromVerifyResult(classification, result);
|
|
1709
1987
|
}
|
|
1710
|
-
await emitBeacon(options, req, classification, beaconMs, queryValueAllowlist);
|
|
1988
|
+
await emitBeacon(options, req, classification, beaconMs, queryValueAllowlist, apiKey);
|
|
1711
1989
|
const outcome = baseOutcome(classification);
|
|
1712
1990
|
if (policy.mode === "enforce" && policy.depth !== "classify") {
|
|
1713
1991
|
outcome.decision = "deny";
|
|
@@ -1722,7 +2000,8 @@ async function runTier(options, req, classification, policy, apiKey, capture) {
|
|
|
1722
2000
|
req,
|
|
1723
2001
|
{ ...classification, protocol: classification.protocol ?? "ua-heuristic" },
|
|
1724
2002
|
beaconMs,
|
|
1725
|
-
queryValueAllowlist
|
|
2003
|
+
queryValueAllowlist,
|
|
2004
|
+
apiKey
|
|
1726
2005
|
);
|
|
1727
2006
|
}
|
|
1728
2007
|
return baseOutcome(classification);
|
|
@@ -1776,6 +2055,7 @@ async function evaluateEdgeRequest(options, req, config, apiKey) {
|
|
|
1776
2055
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1777
2056
|
0 && (module.exports = {
|
|
1778
2057
|
ASAT_HEADERS,
|
|
2058
|
+
BeaconBuffer,
|
|
1779
2059
|
_patterns,
|
|
1780
2060
|
applyOutcome,
|
|
1781
2061
|
buildDenyBody,
|
|
@@ -1786,10 +2066,12 @@ async function evaluateEdgeRequest(options, req, config, apiKey) {
|
|
|
1786
2066
|
detectCommerce,
|
|
1787
2067
|
emitBeacon,
|
|
1788
2068
|
evaluateEdgeRequest,
|
|
2069
|
+
getBeaconBuffer,
|
|
1789
2070
|
headerValue,
|
|
1790
2071
|
outcomeFromVerifyResult,
|
|
1791
2072
|
passUntouched,
|
|
1792
2073
|
requestUrl,
|
|
2074
|
+
resetBeaconBuffer,
|
|
1793
2075
|
resolvePolicy,
|
|
1794
2076
|
runTier,
|
|
1795
2077
|
sampled,
|