@astrasyncai/verification-gateway 4.0.0 → 4.2.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 +106 -29
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +398 -15
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +398 -15
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +3 -3
- package/dist/adapters/http-pdlss.d.ts +3 -3
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +56 -36
- package/dist/adapters/mcp.d.ts +56 -36
- package/dist/adapters/mcp.js +220 -3
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +220 -3
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +433 -18
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +433 -18
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +2 -2
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +2 -2
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +13 -13
- package/dist/bin/astrasync-codex-hook.js +13 -13
- package/dist/bin/astrasync-guard.js +100 -13
- package/dist/bin/astrasync.js +168 -27
- package/dist/browser/background.js +13 -13
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +13 -13
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/codex/index.d.mts +3 -3
- package/dist/codex/index.d.ts +3 -3
- package/dist/codex/index.js +24 -13
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +24 -13
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +13 -13
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +13 -13
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +22 -7
- package/dist/edge-config.d.ts +22 -7
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +425 -0
- package/dist/edge-core/index.d.ts +425 -0
- package/dist/edge-core/index.js +1482 -0
- package/dist/edge-core/index.js.map +1 -0
- package/dist/edge-core/index.mjs +1437 -0
- package/dist/edge-core/index.mjs.map +1 -0
- package/dist/{express-BZajRs4i.d.mts → express-BVd1_3FE.d.ts} +9 -7
- package/dist/{express-CjCUB1k7.d.ts → express-D_4hTn5Z.d.mts} +9 -7
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +13 -13
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +13 -13
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-adgKhujR.d.ts → index-B0YHu_SP.d.ts} +30 -27
- package/dist/{index-B0cyMZDL.d.mts → index-BPEBlOsE.d.mts} +30 -27
- package/dist/{index-BRYteOqO.d.mts → index-DQb5-_1X.d.mts} +1 -1
- package/dist/{index-BZQdVqNw.d.ts → index-T1aBoUcc.d.ts} +1 -1
- package/dist/index.d.mts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +477 -265
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +474 -265
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/metadata-capture.d.mts +63 -4
- package/dist/metadata-capture.d.ts +63 -4
- package/dist/metadata-capture.js +67 -0
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +64 -0
- package/dist/metadata-capture.mjs.map +1 -1
- package/dist/{nextjs-DeUTnOBd.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
- package/dist/{nextjs-D_iFf5k-.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
- package/dist/platform-signatures.d.mts +5 -6
- package/dist/platform-signatures.d.ts +5 -6
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.d.mts +10 -11
- package/dist/registration/index.d.ts +10 -11
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-BM3mwEAq.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
- package/dist/{sdk-_il1Q41f.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CHKCgDFC.d.ts → types-BLUx92FJ.d.ts} +1 -1
- package/dist/{types-DfnP7JiM.d.mts → types-DqfPU5Bl.d.mts} +1 -1
- package/dist/{types-BcDf9Kk0.d.mts → types-r850cOt0.d.mts} +106 -88
- package/dist/{types-fTQD448H.d.ts → types-sMxBT-nM.d.ts} +106 -88
- package/dist/ui/index.d.mts +11 -7
- package/dist/ui/index.d.ts +11 -7
- package/dist/ui/index.js +13 -9
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +10 -8
- package/dist/ui/index.mjs.map +1 -1
- package/dist/verify.d.mts +8 -7
- package/dist/verify.d.ts +8 -7
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
// src/edge-config.ts
|
|
2
|
+
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
3
|
+
version: 1,
|
|
4
|
+
mode: "observe",
|
|
5
|
+
depth: "classify",
|
|
6
|
+
pathRules: [],
|
|
7
|
+
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
8
|
+
failurePosture: "open"
|
|
9
|
+
});
|
|
10
|
+
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
11
|
+
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
12
|
+
function matchEdgePathRule(rules, path) {
|
|
13
|
+
for (const rule of rules) {
|
|
14
|
+
if (globToRegExp(rule.pattern).test(path)) return rule;
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
function globToRegExp(pattern) {
|
|
19
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
20
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/metadata-capture.ts
|
|
24
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
25
|
+
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
26
|
+
"cookie",
|
|
27
|
+
"set-cookie",
|
|
28
|
+
"x-csrf-token",
|
|
29
|
+
"proxy-authorization",
|
|
30
|
+
"x-astrasync-signature",
|
|
31
|
+
"x-astrasync-secret",
|
|
32
|
+
"x-hub-signature",
|
|
33
|
+
"x-hub-signature-256"
|
|
34
|
+
]);
|
|
35
|
+
var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
|
|
36
|
+
var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
37
|
+
"authorization",
|
|
38
|
+
"x-api-key",
|
|
39
|
+
"x-goog-api-key",
|
|
40
|
+
"api-key"
|
|
41
|
+
]);
|
|
42
|
+
function isSignatureHeader(name) {
|
|
43
|
+
return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
|
|
44
|
+
}
|
|
45
|
+
var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
46
|
+
"openai-organization",
|
|
47
|
+
"openai-project",
|
|
48
|
+
"openai-version",
|
|
49
|
+
"openai-processing-ms",
|
|
50
|
+
"anthropic-version",
|
|
51
|
+
"anthropic-beta",
|
|
52
|
+
"x-goog-user-project",
|
|
53
|
+
"x-goog-api-client",
|
|
54
|
+
"user-agent",
|
|
55
|
+
"x-agent-card-url",
|
|
56
|
+
"x-agent-provider"
|
|
57
|
+
]);
|
|
58
|
+
var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
|
|
59
|
+
var KEY_FORMAT_PREFIXES = [
|
|
60
|
+
"sk-ant-api03",
|
|
61
|
+
"sk-ant-api",
|
|
62
|
+
"sk-ant",
|
|
63
|
+
"sk-proj",
|
|
64
|
+
"sk-svcacct",
|
|
65
|
+
"sk-or-v1",
|
|
66
|
+
"sk-or",
|
|
67
|
+
"sk-lf",
|
|
68
|
+
"sk-",
|
|
69
|
+
"gsk_",
|
|
70
|
+
"aiza",
|
|
71
|
+
"ya29",
|
|
72
|
+
"ghp_",
|
|
73
|
+
"gho_",
|
|
74
|
+
"github_pat_",
|
|
75
|
+
"xai-",
|
|
76
|
+
"pplx-",
|
|
77
|
+
"r8_",
|
|
78
|
+
"hf_",
|
|
79
|
+
"astra-",
|
|
80
|
+
"astrae-"
|
|
81
|
+
];
|
|
82
|
+
var MAX_HEADERS = 64;
|
|
83
|
+
var MAX_HEADER_VALUE_BYTES = 1024;
|
|
84
|
+
var MAX_TOTAL_BYTES = 16 * 1024;
|
|
85
|
+
function byteLength(s) {
|
|
86
|
+
let bytes = 0;
|
|
87
|
+
for (let i = 0; i < s.length; i++) {
|
|
88
|
+
const code = s.charCodeAt(i);
|
|
89
|
+
if (code < 128) bytes += 1;
|
|
90
|
+
else if (code < 2048) bytes += 2;
|
|
91
|
+
else if (code >= 55296 && code <= 56319) {
|
|
92
|
+
bytes += 4;
|
|
93
|
+
i++;
|
|
94
|
+
} else bytes += 3;
|
|
95
|
+
}
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
function extractApiKeyFormat(rawValue) {
|
|
99
|
+
if (!rawValue) return void 0;
|
|
100
|
+
let value = rawValue.trim();
|
|
101
|
+
const spaceIdx = value.indexOf(" ");
|
|
102
|
+
if (spaceIdx > 0) {
|
|
103
|
+
const scheme = value.slice(0, spaceIdx).toLowerCase();
|
|
104
|
+
if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
|
|
105
|
+
value = value.slice(spaceIdx + 1).trim();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const lower = value.toLowerCase();
|
|
109
|
+
for (const prefix of KEY_FORMAT_PREFIXES) {
|
|
110
|
+
if (lower.startsWith(prefix)) {
|
|
111
|
+
return prefix;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
function isSecretHeader(name) {
|
|
117
|
+
if (SECRET_HEADER_NAMES.has(name)) return true;
|
|
118
|
+
if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
|
|
119
|
+
if (isSignatureHeader(name)) return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
function isPlatformHeader(name) {
|
|
123
|
+
if (PLATFORM_HEADER_NAMES.has(name)) return true;
|
|
124
|
+
return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
|
|
125
|
+
}
|
|
126
|
+
function sanitizeHeaders(raw) {
|
|
127
|
+
const headers = {};
|
|
128
|
+
const platformHeaders = {};
|
|
129
|
+
let apiKeyFormat;
|
|
130
|
+
let totalBytes = 0;
|
|
131
|
+
let count = 0;
|
|
132
|
+
if (!raw) return { headers };
|
|
133
|
+
for (const rawName of Object.keys(raw)) {
|
|
134
|
+
if (count >= MAX_HEADERS) break;
|
|
135
|
+
const name = rawName.toLowerCase();
|
|
136
|
+
const rawValue = raw[rawName];
|
|
137
|
+
if (rawValue === void 0) continue;
|
|
138
|
+
const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
|
|
139
|
+
if (CREDENTIAL_HEADER_NAMES.has(name)) {
|
|
140
|
+
const fmt = extractApiKeyFormat(joined);
|
|
141
|
+
if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (isSecretHeader(name)) continue;
|
|
145
|
+
let value = joined;
|
|
146
|
+
if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
|
|
147
|
+
value = value.slice(0, MAX_HEADER_VALUE_BYTES);
|
|
148
|
+
}
|
|
149
|
+
const entryBytes = byteLength(name) + byteLength(value);
|
|
150
|
+
if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
|
|
151
|
+
totalBytes += entryBytes;
|
|
152
|
+
count++;
|
|
153
|
+
headers[name] = value;
|
|
154
|
+
if (isPlatformHeader(name)) platformHeaders[name] = value;
|
|
155
|
+
}
|
|
156
|
+
const result = { headers };
|
|
157
|
+
if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
|
|
158
|
+
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/platform-signatures.ts
|
|
163
|
+
var PLATFORM_AGENT_SIGNATURES = [
|
|
164
|
+
{
|
|
165
|
+
vendor: "claude",
|
|
166
|
+
displayName: "Anthropic Claude",
|
|
167
|
+
uaPatterns: [/claude/i, /anthropic/i],
|
|
168
|
+
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
169
|
+
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
170
|
+
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
vendor: "chatgpt",
|
|
174
|
+
displayName: "OpenAI ChatGPT",
|
|
175
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
176
|
+
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
177
|
+
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
178
|
+
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
vendor: "gemini",
|
|
182
|
+
displayName: "Google Gemini",
|
|
183
|
+
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
184
|
+
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
185
|
+
apiKeyFormatPatterns: [/^aiza/i],
|
|
186
|
+
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
vendor: "cursor",
|
|
190
|
+
displayName: "Cursor",
|
|
191
|
+
uaPatterns: [/cursor/i],
|
|
192
|
+
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
vendor: "goose",
|
|
196
|
+
displayName: "Block Goose",
|
|
197
|
+
uaPatterns: [/goose/i, /block-goose/i],
|
|
198
|
+
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
vendor: "perplexity",
|
|
202
|
+
displayName: "Perplexity",
|
|
203
|
+
uaPatterns: [/perplexity/i],
|
|
204
|
+
agentCardPatterns: [/perplexity\.ai/i],
|
|
205
|
+
apiKeyFormatPatterns: [/^pplx-/i]
|
|
206
|
+
}
|
|
207
|
+
];
|
|
208
|
+
function matchPlatformSignature(signatures, input) {
|
|
209
|
+
const ua = input.userAgent ?? "";
|
|
210
|
+
const cardUrl = input.agentCardUrl ?? "";
|
|
211
|
+
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
212
|
+
const headerLines = [];
|
|
213
|
+
if (input.platformHeaders) {
|
|
214
|
+
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
215
|
+
headerLines.push(name, `${name}: ${value}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const sig of signatures) {
|
|
219
|
+
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
220
|
+
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
221
|
+
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
222
|
+
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
223
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
224
|
+
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
225
|
+
const cardHost = (() => {
|
|
226
|
+
try {
|
|
227
|
+
return cardUrl ? new URL(cardUrl).host : "";
|
|
228
|
+
} catch {
|
|
229
|
+
return "";
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
233
|
+
return {
|
|
234
|
+
vendor: sig.vendor,
|
|
235
|
+
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
236
|
+
displayName: sig.displayName
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
function detectPlatformFingerprint(input) {
|
|
243
|
+
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/access-levels.ts
|
|
247
|
+
function getTrustLevel(score) {
|
|
248
|
+
if (score >= 80) return "PLATINUM";
|
|
249
|
+
if (score >= 60) return "GOLD";
|
|
250
|
+
if (score >= 40) return "SILVER";
|
|
251
|
+
return "BRONZE";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/version.ts
|
|
255
|
+
var SDK_VERSION = "4.1.0";
|
|
256
|
+
|
|
257
|
+
// src/well-known.ts
|
|
258
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
259
|
+
var cache = /* @__PURE__ */ new Map();
|
|
260
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
261
|
+
return cache.get(apiBaseUrl)?.data;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/verify.ts
|
|
265
|
+
var DEFAULT_CONFIG = {
|
|
266
|
+
apiBaseUrl: "https://astrasync.ai/api",
|
|
267
|
+
// v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no
|
|
268
|
+
// access). Pre-rename this defaulted to `'guidance'`, which combined with
|
|
269
|
+
// a route gated at `'guidance'` to silently let unverified traffic
|
|
270
|
+
// through (`hasMinimumAccess('guidance', 'guidance') === true`).
|
|
271
|
+
defaultAccessLevel: "none",
|
|
272
|
+
// minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.
|
|
273
|
+
// cacheTtl deliberately unset. When undefined, cacheResult applies the 60s
|
|
274
|
+
// default; the caller's explicit value is honoured for cacheable results.
|
|
275
|
+
// Denials and step-up escalations are NEVER cached (3.6.0) regardless of
|
|
276
|
+
// cacheTtl. Set cacheTtl: 0 to disable caching entirely.
|
|
277
|
+
debug: false
|
|
278
|
+
};
|
|
279
|
+
var initCheckPerformed = false;
|
|
280
|
+
var deprecationWarningShown = false;
|
|
281
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
282
|
+
initCheckPerformed = true;
|
|
283
|
+
try {
|
|
284
|
+
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
285
|
+
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
286
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
287
|
+
if (contentType.startsWith("text/html")) {
|
|
288
|
+
const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
|
|
289
|
+
if (strictInit) {
|
|
290
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
291
|
+
}
|
|
292
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
293
|
+
} else if (debug) {
|
|
294
|
+
console.log(
|
|
295
|
+
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (apiKey) {
|
|
299
|
+
const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
300
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
301
|
+
});
|
|
302
|
+
if (selfCheck.status === 401) {
|
|
303
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
304
|
+
const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
|
|
305
|
+
if (strictInit) {
|
|
306
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
307
|
+
}
|
|
308
|
+
console.error(message);
|
|
309
|
+
} else if (selfCheck.ok) {
|
|
310
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
311
|
+
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
312
|
+
console.warn(
|
|
313
|
+
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
314
|
+
);
|
|
315
|
+
} else if (debug) {
|
|
316
|
+
console.log("[VerificationGateway] API key self-check passed");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
if (strictInit) {
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
if (debug) {
|
|
325
|
+
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
var verificationCache = /* @__PURE__ */ new Map();
|
|
330
|
+
function getCacheKey(request, counterpartyId) {
|
|
331
|
+
const c = request.credentials;
|
|
332
|
+
return [
|
|
333
|
+
c.astraId || "",
|
|
334
|
+
c.apiKey || "",
|
|
335
|
+
c.jwt || "",
|
|
336
|
+
request.purpose || "",
|
|
337
|
+
request.action || "",
|
|
338
|
+
request.resourceType || "",
|
|
339
|
+
request.resource || "",
|
|
340
|
+
request.jurisdiction || "",
|
|
341
|
+
request.transactionValue ?? "",
|
|
342
|
+
request.currency || "",
|
|
343
|
+
// SECURITY (cross-merchant cache leak): the merchant identity is sent via
|
|
344
|
+
// `config.counterpartyId`, NOT on the request, so it was previously absent
|
|
345
|
+
// from the key — two verifies for the SAME agent/purpose/action/value but
|
|
346
|
+
// DIFFERENT merchants collided, and a grant at a permissive merchant (low
|
|
347
|
+
// trust floor) was served for a stricter one. Same bug class as the
|
|
348
|
+
// duration omission (F-A1-07). counterpartyId affects the backend verdict
|
|
349
|
+
// (trust floor / per-route policy), so it MUST key the cache.
|
|
350
|
+
counterpartyId || "",
|
|
351
|
+
request.counterpartyUrl || "",
|
|
352
|
+
request.counterpartyType || "",
|
|
353
|
+
request.isSubAgentRequest ? "1" : "0",
|
|
354
|
+
request.parentAgentId || "",
|
|
355
|
+
request.subAgentDepth ?? "",
|
|
356
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
357
|
+
// backend verdict. Without these, two requests with different
|
|
358
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
359
|
+
// the shorter-duration allow served the longer-duration request.
|
|
360
|
+
request.durationRequired ?? "",
|
|
361
|
+
request.invocationProtocol || "",
|
|
362
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
363
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
364
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
365
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
366
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
367
|
+
// be cached across IPs).
|
|
368
|
+
request.callerMetadata?.sourceIp || "",
|
|
369
|
+
request.callerMetadata?.userAgent || "",
|
|
370
|
+
request.callerMetadata?.forwardedFor || "",
|
|
371
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
372
|
+
].join("|");
|
|
373
|
+
}
|
|
374
|
+
function getCachedResult(request, counterpartyId) {
|
|
375
|
+
const key = getCacheKey(request, counterpartyId);
|
|
376
|
+
const cached = verificationCache.get(key);
|
|
377
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
378
|
+
return cached.result;
|
|
379
|
+
}
|
|
380
|
+
if (cached) {
|
|
381
|
+
verificationCache.delete(key);
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
var DEFAULT_CACHE_TTL_SECONDS = 60;
|
|
386
|
+
function cacheResult(request, result, configuredTtl, counterpartyId) {
|
|
387
|
+
const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : DEFAULT_CACHE_TTL_SECONDS;
|
|
388
|
+
const key = getCacheKey(request, counterpartyId);
|
|
389
|
+
verificationCache.set(key, {
|
|
390
|
+
result,
|
|
391
|
+
expiresAt: Date.now() + ttlSeconds * 1e3
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function extractCredentials(headers, query) {
|
|
395
|
+
const credentials = {};
|
|
396
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
397
|
+
const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
|
|
398
|
+
if (astraIdHeader) {
|
|
399
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
400
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
401
|
+
credentials.astraId = raw;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
405
|
+
if (apiKeyHeader) {
|
|
406
|
+
credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
|
|
407
|
+
}
|
|
408
|
+
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
409
|
+
if (authHeader) {
|
|
410
|
+
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
411
|
+
if (typeof authValue === "string") {
|
|
412
|
+
credentials.authorizationHeader = authValue;
|
|
413
|
+
if (authValue.startsWith("Bearer ")) {
|
|
414
|
+
credentials.jwt = authValue.slice(7);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (query) {
|
|
419
|
+
if (query.astraId && !credentials.astraId) {
|
|
420
|
+
credentials.astraId = query.astraId;
|
|
421
|
+
}
|
|
422
|
+
if (query.apiKey && !credentials.apiKey) {
|
|
423
|
+
credentials.apiKey = query.apiKey;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return credentials;
|
|
427
|
+
}
|
|
428
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
429
|
+
const source = options.source ?? "no_credentials";
|
|
430
|
+
const isApiError = source === "api_error";
|
|
431
|
+
const isMisconfig = source === "merchant_misconfig";
|
|
432
|
+
const urls = options.urls;
|
|
433
|
+
const guidance = isMisconfig ? {
|
|
434
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
435
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
436
|
+
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
437
|
+
steps: [
|
|
438
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
|
|
439
|
+
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
440
|
+
]
|
|
441
|
+
} : isApiError ? {
|
|
442
|
+
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
443
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
444
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
445
|
+
steps: [
|
|
446
|
+
"Retry the request with exponential backoff",
|
|
447
|
+
"If failures persist, share the correlationId with support"
|
|
448
|
+
]
|
|
449
|
+
} : {
|
|
450
|
+
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
451
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
452
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
453
|
+
steps: [
|
|
454
|
+
"Register for an AstraSync account",
|
|
455
|
+
"Create and register your agent",
|
|
456
|
+
"Add your ASTRA-ID to request headers",
|
|
457
|
+
"Retry your request"
|
|
458
|
+
]
|
|
459
|
+
};
|
|
460
|
+
return {
|
|
461
|
+
// Round-18 G4: createGuidanceResponse fires for unverified-agent path or
|
|
462
|
+
// API-error fallback. Identity is not verified (no agent resolved);
|
|
463
|
+
// policy is not evaluated (we never reached the gate).
|
|
464
|
+
identityVerified: false,
|
|
465
|
+
policyAllowed: false,
|
|
466
|
+
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
467
|
+
// Adapters additionally short-circuit on `!identityVerified ||
|
|
468
|
+
// !policyAllowed` before the gate check, but the access level still has
|
|
469
|
+
// to be honest at the data layer so downstream consumers (SDK adapters
|
|
470
|
+
// in other languages, custom integrations) inherit the correct
|
|
471
|
+
// semantics.
|
|
472
|
+
accessLevel: "none",
|
|
473
|
+
guidance,
|
|
474
|
+
denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
|
|
475
|
+
// Round-10 (#47, O5): on API-error fallback, surface a typed failure so
|
|
476
|
+
// partners (and their custom onDenied handlers) can branch on
|
|
477
|
+
// dimension. Without this, the synthesised stub was indistinguishable
|
|
478
|
+
// from a real policy deny.
|
|
479
|
+
failures: isMisconfig ? [
|
|
480
|
+
{
|
|
481
|
+
dimension: "verify_access.merchant_misconfigured",
|
|
482
|
+
message: reason ?? "The integration API key was rejected by AstraSync",
|
|
483
|
+
guidance: guidance.message
|
|
484
|
+
}
|
|
485
|
+
] : isApiError ? [
|
|
486
|
+
{
|
|
487
|
+
dimension: "verify_access.api_error",
|
|
488
|
+
message: reason ?? "Verification temporarily unavailable",
|
|
489
|
+
guidance: guidance.message
|
|
490
|
+
}
|
|
491
|
+
] : void 0,
|
|
492
|
+
// 4.0.0: typed marker so adapters render the misconfig envelope (503 +
|
|
493
|
+
// MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
|
|
494
|
+
...isMisconfig && { misconfigured: true },
|
|
495
|
+
correlationId: options.correlationId,
|
|
496
|
+
attemptId: options.attemptId,
|
|
497
|
+
verifiedAt: /* @__PURE__ */ new Date()
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
async function callVerifyAccessAPI(config, request) {
|
|
501
|
+
const { credentials, ...requestData } = request;
|
|
502
|
+
const body = {
|
|
503
|
+
...credentials.astraId && { agentId: credentials.astraId },
|
|
504
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
505
|
+
};
|
|
506
|
+
if (requestData.action) body.action = requestData.action;
|
|
507
|
+
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
508
|
+
if (requestData.resource) body.resource = requestData.resource;
|
|
509
|
+
if (requestData.jurisdiction) body.jurisdiction = requestData.jurisdiction;
|
|
510
|
+
if (requestData.transactionValue) body.transactionValue = requestData.transactionValue;
|
|
511
|
+
if (requestData.currency) body.currency = requestData.currency;
|
|
512
|
+
if (requestData.isSubAgentRequest) body.isSubAgentRequest = requestData.isSubAgentRequest;
|
|
513
|
+
if (requestData.parentAgentId) body.parentAgentId = requestData.parentAgentId;
|
|
514
|
+
if (requestData.subAgentDepth !== void 0) body.subAgentDepth = requestData.subAgentDepth;
|
|
515
|
+
if (requestData.enableRuntimeChallenge)
|
|
516
|
+
body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
|
|
517
|
+
if (requestData.createSession) body.createSession = requestData.createSession;
|
|
518
|
+
if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
|
|
519
|
+
if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
|
|
520
|
+
if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;
|
|
521
|
+
if (config.counterpartyId) body.counterpartyId = config.counterpartyId;
|
|
522
|
+
if (requestData.runtimeChallengeOptions)
|
|
523
|
+
body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
|
|
524
|
+
if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;
|
|
525
|
+
if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
|
|
526
|
+
if (requestData.attemptId) body.attemptId = requestData.attemptId;
|
|
527
|
+
if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
|
|
528
|
+
body.sdkVersion = SDK_VERSION;
|
|
529
|
+
if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
|
|
530
|
+
const meta = {
|
|
531
|
+
...requestData.clientIp && { sourceIp: requestData.clientIp },
|
|
532
|
+
...requestData.userAgent && { userAgent: requestData.userAgent },
|
|
533
|
+
...requestData.callerMetadata
|
|
534
|
+
};
|
|
535
|
+
if (Object.keys(meta).length > 0) body.callerMetadata = meta;
|
|
536
|
+
}
|
|
537
|
+
const headers = {
|
|
538
|
+
"Content-Type": "application/json",
|
|
539
|
+
...config.customHeaders
|
|
540
|
+
};
|
|
541
|
+
if (config.apiKey) {
|
|
542
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
543
|
+
headers["X-API-Key"] = config.apiKey;
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
547
|
+
method: "POST",
|
|
548
|
+
headers,
|
|
549
|
+
body: JSON.stringify(body),
|
|
550
|
+
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
551
|
+
});
|
|
552
|
+
const data = await response.json();
|
|
553
|
+
if (response.status === 410) {
|
|
554
|
+
return {
|
|
555
|
+
success: true,
|
|
556
|
+
// 3.12.0 (Trust Record): keep the server-echoed attempt handle even on
|
|
557
|
+
// the synthesised 410 deny shape (echoed on every branch).
|
|
558
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
559
|
+
access: {
|
|
560
|
+
allowed: false,
|
|
561
|
+
accessLevel: "none",
|
|
562
|
+
reason: "endpoint_deactivated",
|
|
563
|
+
failures: [
|
|
564
|
+
{
|
|
565
|
+
dimension: "endpoint.deactivated",
|
|
566
|
+
message: typeof data?.message === "string" ? data.message : "Endpoint has been deactivated",
|
|
567
|
+
guidance: typeof data?.guidance === "string" ? data.guidance : "Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."
|
|
568
|
+
}
|
|
569
|
+
]
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (!response.ok) {
|
|
574
|
+
if (response.status === 401) {
|
|
575
|
+
return {
|
|
576
|
+
success: false,
|
|
577
|
+
error: data.message || data.error || "Integration API key rejected",
|
|
578
|
+
misconfig: true,
|
|
579
|
+
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
580
|
+
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
581
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
582
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
success: false,
|
|
587
|
+
error: data.message || data.error || `API returned ${response.status}`,
|
|
588
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
589
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return data;
|
|
593
|
+
} catch (error) {
|
|
594
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
595
|
+
return {
|
|
596
|
+
success: false,
|
|
597
|
+
error: `Failed to call verify-access API: ${message}`
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function verify(config, request, options) {
|
|
602
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
603
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
604
|
+
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
605
|
+
if (mergedConfig.strictInit) {
|
|
606
|
+
await performInitCheck(
|
|
607
|
+
mergedConfig.apiBaseUrl,
|
|
608
|
+
mergedConfig.debug,
|
|
609
|
+
true,
|
|
610
|
+
mergedConfig.apiKey
|
|
611
|
+
);
|
|
612
|
+
} else {
|
|
613
|
+
void performInitCheck(
|
|
614
|
+
mergedConfig.apiBaseUrl,
|
|
615
|
+
mergedConfig.debug,
|
|
616
|
+
false,
|
|
617
|
+
mergedConfig.apiKey
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
622
|
+
deprecationWarningShown = true;
|
|
623
|
+
console.warn(
|
|
624
|
+
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access-level decisions (the SDK reads access.accessLevel from the verify-access response). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {
|
|
628
|
+
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
629
|
+
if (cached) {
|
|
630
|
+
if (mergedConfig.debug) {
|
|
631
|
+
console.log("[VerificationGateway] Returning cached result");
|
|
632
|
+
}
|
|
633
|
+
const hit = { ...cached };
|
|
634
|
+
if (request.attemptId) {
|
|
635
|
+
hit.attemptId = request.attemptId;
|
|
636
|
+
} else {
|
|
637
|
+
delete hit.attemptId;
|
|
638
|
+
}
|
|
639
|
+
return hit;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const enrichedRequest = { ...request };
|
|
643
|
+
if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
|
|
644
|
+
enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
|
|
645
|
+
}
|
|
646
|
+
if (!enrichedRequest.counterpartyType && mergedConfig.counterpartyType) {
|
|
647
|
+
enrichedRequest.counterpartyType = mergedConfig.counterpartyType;
|
|
648
|
+
}
|
|
649
|
+
if (mergedConfig.debug) {
|
|
650
|
+
console.log("[VerificationGateway] Calling verify-access API");
|
|
651
|
+
}
|
|
652
|
+
const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);
|
|
653
|
+
const rawServerDenialReasons = apiResponse.denialReasons;
|
|
654
|
+
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
655
|
+
if (!apiResponse.success) {
|
|
656
|
+
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
657
|
+
source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
|
|
658
|
+
correlationId: apiResponse.correlationId,
|
|
659
|
+
attemptId: apiResponse.attemptId,
|
|
660
|
+
remediation: apiResponse.remediation,
|
|
661
|
+
urls
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
if (!apiResponse.access?.allowed) {
|
|
665
|
+
const aggregatedFailures = apiResponse.access?.failures;
|
|
666
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
667
|
+
const result2 = {
|
|
668
|
+
identityVerified: idVerifiedFromBackend,
|
|
669
|
+
policyAllowed: false,
|
|
670
|
+
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
671
|
+
// Pre-rename this hardcoded `'guidance'`, which conflated with the
|
|
672
|
+
// colocated `guidance: {...}` help-payload object below and let
|
|
673
|
+
// denied requests pass any route gated at `'guidance'` because
|
|
674
|
+
// `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now
|
|
675
|
+
// ALSO short-circuit on `!identityVerified || !policyAllowed` before
|
|
676
|
+
// the gate check — belt-and-braces.
|
|
677
|
+
accessLevel: "none",
|
|
678
|
+
// 3.6.0: the server now emits top-level denialReasons on every deny
|
|
679
|
+
// path — prefer it verbatim; the failure-message reconstruction remains
|
|
680
|
+
// as the fallback for older backends.
|
|
681
|
+
denialReasons: serverDenialReasons ?? (aggregatedFailures && aggregatedFailures.length > 0 ? aggregatedFailures.map((f) => f.message) : apiResponse.access?.reason ? [apiResponse.access.reason] : ["Access denied"]),
|
|
682
|
+
failures: aggregatedFailures,
|
|
683
|
+
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
684
|
+
requiresApproval: apiResponse.access?.requiresApproval,
|
|
685
|
+
guidance: {
|
|
686
|
+
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
687
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
688
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
689
|
+
},
|
|
690
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
691
|
+
// Extract sessionId so decisions can be recorded for denials too
|
|
692
|
+
sessionId: apiResponse.sessionId,
|
|
693
|
+
// Anonymous traffic has no session → correlationId is the per-attempt
|
|
694
|
+
// linking key (the sessionId-equivalent for anonymous callers).
|
|
695
|
+
correlationId: apiResponse.correlationId,
|
|
696
|
+
// 3.12.0 (Trust Record): server echoes the attempt-chain handle on
|
|
697
|
+
// every branch — denials included (the funnel outlives one deny).
|
|
698
|
+
attemptId: apiResponse.attemptId,
|
|
699
|
+
recommendation: apiResponse.recommendation,
|
|
700
|
+
recommendationReasons: apiResponse.recommendationReasons,
|
|
701
|
+
stepUpApproval: apiResponse.stepUpApproval,
|
|
702
|
+
settlement: apiResponse.settlement
|
|
703
|
+
};
|
|
704
|
+
return result2;
|
|
705
|
+
}
|
|
706
|
+
const agent = apiResponse.agent ? {
|
|
707
|
+
astraId: apiResponse.agent.astraId,
|
|
708
|
+
name: apiResponse.agent.name,
|
|
709
|
+
trustScore: apiResponse.agent.trustScore,
|
|
710
|
+
trustLevel: getTrustLevel(apiResponse.agent.trustScore),
|
|
711
|
+
blockchainVerified: apiResponse.agent.blockchainStatus === "verified",
|
|
712
|
+
status: apiResponse.agent.agentStatus
|
|
713
|
+
} : void 0;
|
|
714
|
+
const developer = apiResponse.developer ? {
|
|
715
|
+
astradId: apiResponse.developer.kyaOwnerId,
|
|
716
|
+
name: apiResponse.developer.fullName,
|
|
717
|
+
trustScore: apiResponse.developer.trustScore || 0,
|
|
718
|
+
verified: apiResponse.developer.identityVerified
|
|
719
|
+
} : void 0;
|
|
720
|
+
const organization = apiResponse.organization ? {
|
|
721
|
+
name: apiResponse.organization.name,
|
|
722
|
+
verified: apiResponse.organization.verified,
|
|
723
|
+
trustScore: apiResponse.organization.trustScore
|
|
724
|
+
} : void 0;
|
|
725
|
+
const verificationContext = apiResponse.verificationContext;
|
|
726
|
+
const accessLevel = apiResponse.access?.accessLevel ?? "standard";
|
|
727
|
+
const result = {
|
|
728
|
+
// Round-18 G4: backend allowed access. Identity is verified (we resolved
|
|
729
|
+
// the caller to an agent) and policy passed all gates. Read idVerified
|
|
730
|
+
// from verificationContext for symmetry with the deny branch; default true
|
|
731
|
+
// on success path since `access.allowed === true` implies identity was
|
|
732
|
+
// resolvable (anonymous-allow paths flow through createGuidanceResponse).
|
|
733
|
+
identityVerified: apiResponse.verificationContext?.idVerified !== false,
|
|
734
|
+
policyAllowed: true,
|
|
735
|
+
accessLevel,
|
|
736
|
+
agent,
|
|
737
|
+
developer,
|
|
738
|
+
organization,
|
|
739
|
+
appliedPolicy: apiResponse.access?.appliedPolicy,
|
|
740
|
+
verificationContext,
|
|
741
|
+
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
742
|
+
requiresApproval: apiResponse.access?.requiresApproval,
|
|
743
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
744
|
+
cacheTtl: mergedConfig.cacheTtl,
|
|
745
|
+
// Handshake Protocol v10 enhanced fields (present when backend returns them)
|
|
746
|
+
sessionId: apiResponse.sessionId,
|
|
747
|
+
// v2.3.10 (defect #34, round-4): anonymous responses surface correlationId
|
|
748
|
+
// (no session row exists for unverified callers).
|
|
749
|
+
correlationId: apiResponse.correlationId,
|
|
750
|
+
// 3.12.0 (Trust Record): server-echoed attempt-chain handle.
|
|
751
|
+
attemptId: apiResponse.attemptId,
|
|
752
|
+
runtimeChallenge: apiResponse.runtimeChallenge,
|
|
753
|
+
tokenGuidance: apiResponse.tokenGuidance,
|
|
754
|
+
recommendation: apiResponse.recommendation,
|
|
755
|
+
recommendationReasons: apiResponse.recommendationReasons,
|
|
756
|
+
warningHeader: apiResponse.warningHeader,
|
|
757
|
+
stepUpApproval: apiResponse.stepUpApproval,
|
|
758
|
+
settlement: apiResponse.settlement
|
|
759
|
+
};
|
|
760
|
+
if (result.recommendation === "deny") {
|
|
761
|
+
result.policyAllowed = false;
|
|
762
|
+
result.accessLevel = "none";
|
|
763
|
+
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Access denied by AstraSync recommendation"];
|
|
764
|
+
result.guidance = result.runtimeChallenge ? {
|
|
765
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
766
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
767
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
768
|
+
} : {
|
|
769
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
770
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
771
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
772
|
+
};
|
|
773
|
+
} else if (result.recommendation === "step_up_required") {
|
|
774
|
+
result.requiresStepUp = true;
|
|
775
|
+
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
776
|
+
}
|
|
777
|
+
const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
|
|
778
|
+
if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
|
|
779
|
+
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
780
|
+
}
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
async function reportUnregisteredAttempt(config, data) {
|
|
784
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
785
|
+
const { counterpartyUrl, ...rest } = data;
|
|
786
|
+
const body = {
|
|
787
|
+
...rest,
|
|
788
|
+
...counterpartyUrl && { counterpartyUrl },
|
|
789
|
+
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
790
|
+
};
|
|
791
|
+
await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
792
|
+
method: "POST",
|
|
793
|
+
headers: { "Content-Type": "application/json" },
|
|
794
|
+
body: JSON.stringify(body)
|
|
795
|
+
}).catch(() => {
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/edge-core/request.ts
|
|
800
|
+
function headerValue(req, name) {
|
|
801
|
+
return req.headers[name.toLowerCase()];
|
|
802
|
+
}
|
|
803
|
+
function requestUrl(req, host) {
|
|
804
|
+
const qs = req.querystring ? `?${req.querystring}` : "";
|
|
805
|
+
return `https://${host}${req.uri}${qs}`;
|
|
806
|
+
}
|
|
807
|
+
function buildObservedMetadata(req) {
|
|
808
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
|
|
809
|
+
const connection = req.connection ?? {};
|
|
810
|
+
return {
|
|
811
|
+
headers,
|
|
812
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
813
|
+
...platformHeaders && { platformHeaders },
|
|
814
|
+
...Object.keys(connection).length > 0 && { connection },
|
|
815
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/edge-core/detect.ts
|
|
820
|
+
var SD_JWT_PATTERN = /^eyJ[\w-]+\.[\w-]*\.[\w-]+~/;
|
|
821
|
+
function decodeBase64Url(seg) {
|
|
822
|
+
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
|
|
823
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
824
|
+
const binary = atob(padded);
|
|
825
|
+
const bytes = new Uint8Array(binary.length);
|
|
826
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
827
|
+
return new TextDecoder().decode(bytes);
|
|
828
|
+
}
|
|
829
|
+
function sniffJwtHeader(compact) {
|
|
830
|
+
try {
|
|
831
|
+
const seg = compact.split(".")[0];
|
|
832
|
+
return JSON.parse(decodeBase64Url(seg));
|
|
833
|
+
} catch {
|
|
834
|
+
return void 0;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function parseJsonBody(bodyText) {
|
|
838
|
+
if (!bodyText) return void 0;
|
|
839
|
+
try {
|
|
840
|
+
const parsed = JSON.parse(bodyText);
|
|
841
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
842
|
+
} catch {
|
|
843
|
+
return void 0;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function detectCommerce(req, host) {
|
|
847
|
+
const url = requestUrl(req, host);
|
|
848
|
+
const method = req.method;
|
|
849
|
+
const headers = req.headers;
|
|
850
|
+
const jsonBody = parseJsonBody(req.bodyText);
|
|
851
|
+
const truncated = req.bodyTruncated ? { bodyTruncated: true } : {};
|
|
852
|
+
const authHeader = headerValue(req, "authorization");
|
|
853
|
+
const bearer = authHeader?.replace(/^Bearer\s+/i, "");
|
|
854
|
+
const mandateHeader = headerValue(req, "x-payment-mandate") ?? headerValue(req, "payment-mandate");
|
|
855
|
+
const sdJwtCandidate = [mandateHeader, bearer].find((v) => v && SD_JWT_PATTERN.test(v));
|
|
856
|
+
if (sdJwtCandidate) {
|
|
857
|
+
const typ = sniffJwtHeader(sdJwtCandidate)?.typ;
|
|
858
|
+
return {
|
|
859
|
+
protocol: "vi-sd-jwt",
|
|
860
|
+
artifacts: { viSdJwt: sdJwtCandidate },
|
|
861
|
+
evidence: { typ, ...truncated }
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
const signature = headerValue(req, "signature");
|
|
865
|
+
const signatureInput = headerValue(req, "signature-input");
|
|
866
|
+
if (signature && signatureInput) {
|
|
867
|
+
const signatureAgent = headerValue(req, "signature-agent");
|
|
868
|
+
const registryHint = headerValue(req, "x-agent-registry");
|
|
869
|
+
const lowerInput = signatureInput.toLowerCase();
|
|
870
|
+
const protocol = signatureAgent ? "rfc9421-web-bot-auth" : registryHint?.toLowerCase().includes("visa") || lowerInput.includes("visa") ? "rfc9421-tap" : registryHint?.toLowerCase().includes("mastercard") || lowerInput.includes("mastercard") ? "rfc9421-agent-pay" : "rfc9421";
|
|
871
|
+
return {
|
|
872
|
+
protocol,
|
|
873
|
+
artifacts: {
|
|
874
|
+
rfc9421: { headers, method, url, ...req.bodyText && { body: req.bodyText } }
|
|
875
|
+
},
|
|
876
|
+
evidence: {
|
|
877
|
+
...signatureAgent && { signatureAgent },
|
|
878
|
+
...registryHint && { registryHint },
|
|
879
|
+
keyid: signatureInput.match(/keyid="([^"]+)"/)?.[1],
|
|
880
|
+
...truncated
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const acpTimestamp = headerValue(req, "timestamp");
|
|
885
|
+
if (signature && !signatureInput && (acpTimestamp || headerValue(req, "idempotency-key"))) {
|
|
886
|
+
return {
|
|
887
|
+
protocol: "acp",
|
|
888
|
+
artifacts: {
|
|
889
|
+
acpRequest: {
|
|
890
|
+
method,
|
|
891
|
+
url,
|
|
892
|
+
headers,
|
|
893
|
+
...req.bodyText && { rawBody: req.bodyText },
|
|
894
|
+
...jsonBody && { body: jsonBody }
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
evidence: { hasTimestamp: Boolean(acpTimestamp), ...truncated }
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (/\/checkout[-_]sessions(\/|$)/.test(req.uri)) {
|
|
901
|
+
return {
|
|
902
|
+
protocol: "ucp",
|
|
903
|
+
artifacts: { ucpRequest: { method, url, ...jsonBody && { body: jsonBody } } },
|
|
904
|
+
evidence: { path: req.uri, ...truncated }
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
if (jsonBody) {
|
|
908
|
+
const intent = jsonBody.intent_mandate ?? jsonBody.intentMandate;
|
|
909
|
+
const cart = jsonBody.cart_mandate ?? jsonBody.cartMandate;
|
|
910
|
+
const payment = jsonBody.payment_mandate ?? jsonBody.paymentMandate;
|
|
911
|
+
if (typeof intent === "string" || typeof cart === "string" || typeof payment === "string") {
|
|
912
|
+
return {
|
|
913
|
+
protocol: "ap2",
|
|
914
|
+
artifacts: {
|
|
915
|
+
ap2Mandates: {
|
|
916
|
+
...typeof intent === "string" && { intent },
|
|
917
|
+
...typeof cart === "string" && { cart },
|
|
918
|
+
...typeof payment === "string" && { payment }
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
evidence: {
|
|
922
|
+
mandatesPresent: [
|
|
923
|
+
typeof intent === "string" && "intent",
|
|
924
|
+
typeof cart === "string" && "cart",
|
|
925
|
+
typeof payment === "string" && "payment"
|
|
926
|
+
].filter(Boolean),
|
|
927
|
+
...truncated
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (authHeader && /^Payment\s+/i.test(authHeader)) {
|
|
933
|
+
return {
|
|
934
|
+
protocol: "mpp",
|
|
935
|
+
artifacts: {
|
|
936
|
+
mppRequest: {
|
|
937
|
+
method,
|
|
938
|
+
url,
|
|
939
|
+
headers,
|
|
940
|
+
...req.bodyText && { rawBody: req.bodyText },
|
|
941
|
+
...jsonBody && { body: jsonBody }
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
evidence: truncated
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
if (headerValue(req, "x-payment")) {
|
|
948
|
+
return {
|
|
949
|
+
protocol: "x402",
|
|
950
|
+
artifacts: {
|
|
951
|
+
x402Request: { method, url, headers, ...jsonBody && { body: jsonBody } }
|
|
952
|
+
},
|
|
953
|
+
evidence: truncated
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
return void 0;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/edge-core/classify.ts
|
|
960
|
+
var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
|
|
961
|
+
var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
|
|
962
|
+
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
963
|
+
function resolvePolicy(config, path) {
|
|
964
|
+
if (STATIC_EXTENSIONS.test(path)) {
|
|
965
|
+
return { mode: config.mode, depth: config.depth, skip: true };
|
|
966
|
+
}
|
|
967
|
+
const rule = matchEdgePathRule(config.pathRules, path);
|
|
968
|
+
if (!rule) return { mode: config.mode, depth: config.depth, skip: false };
|
|
969
|
+
return {
|
|
970
|
+
mode: rule.mode ?? config.mode,
|
|
971
|
+
depth: rule.depth ?? config.depth,
|
|
972
|
+
skip: rule.skip === true
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function classify(req, host, depth) {
|
|
976
|
+
const userAgent = headerValue(req, "user-agent") ?? "";
|
|
977
|
+
const credentials = extractCredentials(req.headers, {});
|
|
978
|
+
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
979
|
+
return { tier: "verified", protocol: "x-astra" };
|
|
980
|
+
}
|
|
981
|
+
const { apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
|
|
982
|
+
const detectionInput = { userAgent, apiKeyFormat, platformHeaders };
|
|
983
|
+
if (depth !== "classify") {
|
|
984
|
+
const commerce = detectCommerce(req, host);
|
|
985
|
+
if (commerce) {
|
|
986
|
+
return {
|
|
987
|
+
tier: "identified",
|
|
988
|
+
protocol: commerce.protocol,
|
|
989
|
+
artifacts: commerce.artifacts,
|
|
990
|
+
evidence: commerce.evidence,
|
|
991
|
+
fingerprint: detectPlatformFingerprint(detectionInput)
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const fingerprint = detectPlatformFingerprint(detectionInput);
|
|
996
|
+
if (fingerprint) {
|
|
997
|
+
return { tier: "identified", protocol: "platform-ua", fingerprint };
|
|
998
|
+
}
|
|
999
|
+
if (!userAgent || BOT_UA_PATTERN.test(userAgent) || !BROWSER_UA_PATTERN.test(userAgent)) {
|
|
1000
|
+
return { tier: "anonymous-bot", evidence: { userAgent: userAgent || "(none)" } };
|
|
1001
|
+
}
|
|
1002
|
+
if (!headerValue(req, "sec-fetch-mode") && !headerValue(req, "accept-language")) {
|
|
1003
|
+
return { tier: "anonymous-bot", evidence: { userAgent, spoofedBrowser: true } };
|
|
1004
|
+
}
|
|
1005
|
+
return { tier: "human" };
|
|
1006
|
+
}
|
|
1007
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1008
|
+
|
|
1009
|
+
// src/edge-core/messaging.ts
|
|
1010
|
+
var PROTOCOL_MESSAGES = {
|
|
1011
|
+
"vi-sd-jwt": "This request presented Visa Intelligent Commerce credentials, but the agent is not verified with AstraSync. Register the agent to pair your VI credentials with a verified identity and mandate.",
|
|
1012
|
+
"rfc9421-agent-pay": "This request carried a Mastercard Agent Pay signature, but the agent is not verified with AstraSync. Register the agent to pair Agent Pay credentials with a verified identity and mandate.",
|
|
1013
|
+
"rfc9421-tap": "This request carried a Visa TAP signature, but the agent is not verified with AstraSync. Register the agent to pair TAP credentials with a verified identity and mandate.",
|
|
1014
|
+
"rfc9421-web-bot-auth": "This request carried a Web Bot Auth signature, but the agent is not verified with AstraSync. Register the agent to link your signed-agent identity to a verified principal.",
|
|
1015
|
+
rfc9421: "This request carried an RFC 9421 HTTP message signature, but the agent is not verified with AstraSync. Register the agent to link the signing identity to a verified principal.",
|
|
1016
|
+
acp: "This request used the Agentic Commerce Protocol, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
|
|
1017
|
+
ucp: "This request used a UCP checkout session, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
|
|
1018
|
+
ap2: "This request presented AP2 mandates, but the agent is not verified with AstraSync. Register the agent to pair your mandates with a verified identity.",
|
|
1019
|
+
mpp: "This request presented MPP payment credentials, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
|
|
1020
|
+
x402: "This request used x402 payment headers, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
|
|
1021
|
+
"platform-ua": "This traffic was identified as a platform AI agent without AstraSync credentials. Register the agent to access this property with a verified identity.",
|
|
1022
|
+
"x-astra": "The presented AstraSync credentials failed verification or were denied by this property\u2019s access policy."
|
|
1023
|
+
};
|
|
1024
|
+
function buildDenyBody(outcome, apiBaseUrl) {
|
|
1025
|
+
const origin = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
1026
|
+
const protocolMessage = (outcome.protocol && PROTOCOL_MESSAGES[outcome.protocol]) ?? "This request could not be verified by AstraSync.";
|
|
1027
|
+
return {
|
|
1028
|
+
success: false,
|
|
1029
|
+
error: {
|
|
1030
|
+
// UNAUTHORIZED = identity failed (register/re-authenticate helps);
|
|
1031
|
+
// INSUFFICIENT_ACCESS = identity fine, policy denied (fix the mandate).
|
|
1032
|
+
code: outcome.tier === "verified" ? "INSUFFICIENT_ACCESS" : "UNAUTHORIZED",
|
|
1033
|
+
message: protocolMessage,
|
|
1034
|
+
...outcome.protocol && { protocol: outcome.protocol },
|
|
1035
|
+
failures: outcome.reasons,
|
|
1036
|
+
...outcome.correlationId && { correlationId: outcome.correlationId },
|
|
1037
|
+
guidance: {
|
|
1038
|
+
// `||` not `??`: verify() fills these with EMPTY STRINGS when the
|
|
1039
|
+
// well-known cache is cold — empty means "use the fallback".
|
|
1040
|
+
message: outcome.guidance?.message || "Register this agent with AstraSync to receive verifiable credentials.",
|
|
1041
|
+
registrationUrl: outcome.guidance?.registrationUrl || `${origin}/register`,
|
|
1042
|
+
documentationUrl: outcome.guidance?.documentationUrl || `${origin}/docs/edge-adapter`
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function buildManualReviewBody(outcome) {
|
|
1048
|
+
return {
|
|
1049
|
+
success: false,
|
|
1050
|
+
status: "manual_review",
|
|
1051
|
+
message: "This action requires human approval. Poll the step-up approval endpoint and retry once approved.",
|
|
1052
|
+
...outcome.stepUp && { stepUpApproval: outcome.stepUp },
|
|
1053
|
+
...outcome.correlationId && { correlationId: outcome.correlationId }
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/edge-core/enforce.ts
|
|
1058
|
+
var ASAT_HEADERS = {
|
|
1059
|
+
trustScore: "X-AstraSync-Trust-Score",
|
|
1060
|
+
agentId: "X-AstraSync-Agent-Id",
|
|
1061
|
+
developerId: "X-AstraSync-Developer-Id",
|
|
1062
|
+
permissions: "X-AstraSync-Permissions",
|
|
1063
|
+
observedDecision: "X-AstraSync-Observed-Decision"
|
|
1064
|
+
};
|
|
1065
|
+
function passUntouched() {
|
|
1066
|
+
return { action: "pass", injectHeaders: {}, stripHeaderPrefixes: [] };
|
|
1067
|
+
}
|
|
1068
|
+
function applyOutcome(options, outcome, policy) {
|
|
1069
|
+
const injectHeaders = {};
|
|
1070
|
+
if (outcome.misconfig) {
|
|
1071
|
+
injectHeaders[ASAT_HEADERS.observedDecision] = "misconfig";
|
|
1072
|
+
}
|
|
1073
|
+
if (policy.mode !== "enforce") {
|
|
1074
|
+
if (outcome.decision !== "allow") {
|
|
1075
|
+
injectHeaders[ASAT_HEADERS.observedDecision] = outcome.decision;
|
|
1076
|
+
}
|
|
1077
|
+
Object.assign(injectHeaders, asatHeaders(outcome));
|
|
1078
|
+
return { action: "pass", injectHeaders, stripHeaderPrefixes: ["x-astrasync-"] };
|
|
1079
|
+
}
|
|
1080
|
+
switch (outcome.decision) {
|
|
1081
|
+
case "deny":
|
|
1082
|
+
return {
|
|
1083
|
+
action: "respond",
|
|
1084
|
+
status: 403,
|
|
1085
|
+
headers: {},
|
|
1086
|
+
bodyJson: buildDenyBody(outcome, options.apiBaseUrl)
|
|
1087
|
+
};
|
|
1088
|
+
case "manual_review":
|
|
1089
|
+
return {
|
|
1090
|
+
action: "respond",
|
|
1091
|
+
status: 202,
|
|
1092
|
+
headers: { "Retry-After": "30" },
|
|
1093
|
+
bodyJson: buildManualReviewBody(outcome)
|
|
1094
|
+
};
|
|
1095
|
+
default:
|
|
1096
|
+
Object.assign(injectHeaders, asatHeaders(outcome));
|
|
1097
|
+
return { action: "pass", injectHeaders, stripHeaderPrefixes: ["x-astrasync-"] };
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function asatHeaders(outcome) {
|
|
1101
|
+
if (!outcome.asat) return {};
|
|
1102
|
+
const { trustScore, agentId, developerId, accessLevel } = outcome.asat;
|
|
1103
|
+
return {
|
|
1104
|
+
...trustScore !== void 0 && { [ASAT_HEADERS.trustScore]: String(trustScore) },
|
|
1105
|
+
...agentId && { [ASAT_HEADERS.agentId]: agentId },
|
|
1106
|
+
...developerId && { [ASAT_HEADERS.developerId]: developerId },
|
|
1107
|
+
...accessLevel && { [ASAT_HEADERS.permissions]: accessLevel }
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/adapters/http-pdlss.ts
|
|
1112
|
+
var HTTP_METHOD_ACTION_TABLE = {
|
|
1113
|
+
GET: "data.read",
|
|
1114
|
+
HEAD: "data.read",
|
|
1115
|
+
OPTIONS: "data.read",
|
|
1116
|
+
POST: "data.write",
|
|
1117
|
+
PUT: "data.write",
|
|
1118
|
+
PATCH: "data.write",
|
|
1119
|
+
DELETE: "data.delete"
|
|
1120
|
+
};
|
|
1121
|
+
var DEFAULT_HTTP_ACTION = "data.write";
|
|
1122
|
+
var DEFAULT_HTTP_PURPOSE = "data";
|
|
1123
|
+
function actionForHttpMethod(method) {
|
|
1124
|
+
return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;
|
|
1125
|
+
}
|
|
1126
|
+
function normalizePurposeHeader(value) {
|
|
1127
|
+
const colon = value.indexOf(":");
|
|
1128
|
+
if (colon >= 0) {
|
|
1129
|
+
return { purpose: value.slice(0, colon) };
|
|
1130
|
+
}
|
|
1131
|
+
const dot = value.indexOf(".");
|
|
1132
|
+
if (dot > 0 && dot < value.length - 1) {
|
|
1133
|
+
return { purpose: value.slice(0, dot), actionCandidate: value };
|
|
1134
|
+
}
|
|
1135
|
+
return { purpose: value };
|
|
1136
|
+
}
|
|
1137
|
+
function resolveHttpPdlss(input) {
|
|
1138
|
+
const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : void 0;
|
|
1139
|
+
let action;
|
|
1140
|
+
let actionSource;
|
|
1141
|
+
if (input.routeAction) {
|
|
1142
|
+
action = input.routeAction;
|
|
1143
|
+
actionSource = "route_config";
|
|
1144
|
+
} else if (input.hasCustomActionExtractor && input.customAction) {
|
|
1145
|
+
action = input.customAction;
|
|
1146
|
+
actionSource = "custom_extractor";
|
|
1147
|
+
} else if (!input.hasCustomActionExtractor && input.astraAction) {
|
|
1148
|
+
action = input.astraAction;
|
|
1149
|
+
actionSource = "header";
|
|
1150
|
+
} else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {
|
|
1151
|
+
action = fromHeader.actionCandidate;
|
|
1152
|
+
actionSource = "purpose_header_derived";
|
|
1153
|
+
} else {
|
|
1154
|
+
action = actionForHttpMethod(input.method);
|
|
1155
|
+
actionSource = "method_table";
|
|
1156
|
+
}
|
|
1157
|
+
let purpose;
|
|
1158
|
+
let purposeSource;
|
|
1159
|
+
if (input.routePurpose) {
|
|
1160
|
+
purpose = input.routePurpose;
|
|
1161
|
+
purposeSource = "route_config";
|
|
1162
|
+
} else if (input.hasCustomPurposeExtractor) {
|
|
1163
|
+
if (input.customPurpose) {
|
|
1164
|
+
purpose = input.customPurpose;
|
|
1165
|
+
purposeSource = "custom_extractor";
|
|
1166
|
+
}
|
|
1167
|
+
} else if (fromHeader) {
|
|
1168
|
+
purpose = fromHeader.purpose;
|
|
1169
|
+
purposeSource = "header";
|
|
1170
|
+
} else if (input.legacyPurpose) {
|
|
1171
|
+
purpose = input.legacyPurpose;
|
|
1172
|
+
purposeSource = "legacy_header";
|
|
1173
|
+
} else if (input.queryPurpose) {
|
|
1174
|
+
purpose = input.queryPurpose;
|
|
1175
|
+
purposeSource = "query";
|
|
1176
|
+
}
|
|
1177
|
+
if (!purpose) {
|
|
1178
|
+
const dot = action.indexOf(".");
|
|
1179
|
+
if (dot > 0) {
|
|
1180
|
+
purpose = action.slice(0, dot);
|
|
1181
|
+
purposeSource = "action_derived";
|
|
1182
|
+
} else {
|
|
1183
|
+
purpose = DEFAULT_HTTP_PURPOSE;
|
|
1184
|
+
purposeSource = "transport_default";
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return { purpose, action, purposeSource, actionSource };
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/edge-core/events.ts
|
|
1191
|
+
async function withBudget(promise, ms) {
|
|
1192
|
+
let timer;
|
|
1193
|
+
try {
|
|
1194
|
+
return await Promise.race([
|
|
1195
|
+
promise,
|
|
1196
|
+
new Promise((resolve) => {
|
|
1197
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1198
|
+
})
|
|
1199
|
+
]);
|
|
1200
|
+
} catch {
|
|
1201
|
+
return void 0;
|
|
1202
|
+
} finally {
|
|
1203
|
+
if (timer) clearTimeout(timer);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
async function emitBeacon(options, req, classification, budgetMs) {
|
|
1207
|
+
const budgeted = withBudget(
|
|
1208
|
+
reportUnregisteredAttempt(
|
|
1209
|
+
{ apiBaseUrl: options.apiBaseUrl, counterpartyId: options.counterpartyId },
|
|
1210
|
+
{
|
|
1211
|
+
counterpartyUrl: options.counterpartyUrl,
|
|
1212
|
+
counterpartyType: "website",
|
|
1213
|
+
sourceIp: req.clientIp,
|
|
1214
|
+
userAgent: headerValue(req, "user-agent"),
|
|
1215
|
+
requestPath: req.uri,
|
|
1216
|
+
requestMethod: req.method,
|
|
1217
|
+
// Evidence fields the backend persists into the event record.
|
|
1218
|
+
protocol: classification.protocol,
|
|
1219
|
+
evidence: classification.evidence,
|
|
1220
|
+
// The full sanitized header + connection capture for the
|
|
1221
|
+
// unregistered caller (fire-and-forget, so free).
|
|
1222
|
+
observedMetadata: buildObservedMetadata(req)
|
|
1223
|
+
}
|
|
1224
|
+
),
|
|
1225
|
+
budgetMs
|
|
1226
|
+
);
|
|
1227
|
+
if (options.waitUntil) {
|
|
1228
|
+
options.waitUntil(budgeted);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
await budgeted;
|
|
1232
|
+
}
|
|
1233
|
+
function sampled(rate, random = Math.random) {
|
|
1234
|
+
if (rate <= 0) return false;
|
|
1235
|
+
if (rate >= 1) return true;
|
|
1236
|
+
return random() < rate;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/edge-core/pipeline.ts
|
|
1240
|
+
function baseOutcome(classification) {
|
|
1241
|
+
return {
|
|
1242
|
+
tier: classification.tier,
|
|
1243
|
+
decision: "allow",
|
|
1244
|
+
reasons: [],
|
|
1245
|
+
protocol: classification.protocol
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function buildVerificationRequest(options, req, classification, includeArtifacts) {
|
|
1249
|
+
const pdlss = resolveHttpPdlss({
|
|
1250
|
+
method: req.method,
|
|
1251
|
+
astraPurpose: headerValue(req, "x-astra-purpose"),
|
|
1252
|
+
astraAction: headerValue(req, "x-astra-action")
|
|
1253
|
+
});
|
|
1254
|
+
const attemptId = headerValue(req, "x-astra-attempt-id");
|
|
1255
|
+
return {
|
|
1256
|
+
...attemptId && /^att_[0-9a-f]{32}$/.test(attemptId) && { attemptId },
|
|
1257
|
+
credentials: extractCredentials(req.headers, {}),
|
|
1258
|
+
purpose: pdlss.purpose,
|
|
1259
|
+
action: pdlss.action,
|
|
1260
|
+
resource: req.uri,
|
|
1261
|
+
createSession: true,
|
|
1262
|
+
counterpartyType: "website",
|
|
1263
|
+
counterpartyUrl: options.counterpartyUrl,
|
|
1264
|
+
callerMetadata: {
|
|
1265
|
+
sourceIp: req.clientIp,
|
|
1266
|
+
userAgent: headerValue(req, "user-agent"),
|
|
1267
|
+
referer: headerValue(req, "referer"),
|
|
1268
|
+
host: headerValue(req, "host"),
|
|
1269
|
+
// The full sanitized header + connection capture. The backend stores
|
|
1270
|
+
// it fire-and-forget (zero decision latency) and uses the key-format
|
|
1271
|
+
// / platform headers for attribution.
|
|
1272
|
+
observedMetadata: buildObservedMetadata(req)
|
|
1273
|
+
},
|
|
1274
|
+
...includeArtifacts && classification.artifacts && { commerceArtifacts: classification.artifacts }
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
function outcomeFromVerifyResult(classification, result) {
|
|
1278
|
+
const outcome = baseOutcome(classification);
|
|
1279
|
+
outcome.sessionId = result.sessionId;
|
|
1280
|
+
outcome.correlationId = result.correlationId;
|
|
1281
|
+
outcome.guidance = result.guidance;
|
|
1282
|
+
if (result.misconfigured) {
|
|
1283
|
+
outcome.reasons = ["verification key misconfigured \u2014 fail-open (merchant_misconfig)"];
|
|
1284
|
+
outcome.misconfig = true;
|
|
1285
|
+
return outcome;
|
|
1286
|
+
}
|
|
1287
|
+
if (result.requiresStepUp || result.stepUpApproval) {
|
|
1288
|
+
outcome.decision = "manual_review";
|
|
1289
|
+
outcome.reasons = ["step-up approval required"];
|
|
1290
|
+
outcome.stepUp = {
|
|
1291
|
+
pollUrl: result.stepUpApproval?.pollUrl,
|
|
1292
|
+
expiresAt: result.stepUpApproval?.expiresAt
|
|
1293
|
+
};
|
|
1294
|
+
return outcome;
|
|
1295
|
+
}
|
|
1296
|
+
if (!result.identityVerified || !result.policyAllowed) {
|
|
1297
|
+
outcome.decision = "deny";
|
|
1298
|
+
outcome.reasons = result.denialReasons?.length ? result.denialReasons : [!result.identityVerified ? "identity not verified" : "denied by access policy"];
|
|
1299
|
+
return outcome;
|
|
1300
|
+
}
|
|
1301
|
+
outcome.asat = {
|
|
1302
|
+
trustScore: result.agent?.trustScore,
|
|
1303
|
+
agentId: result.agent?.astraId,
|
|
1304
|
+
developerId: result.developer?.astradId,
|
|
1305
|
+
accessLevel: result.accessLevel
|
|
1306
|
+
};
|
|
1307
|
+
return outcome;
|
|
1308
|
+
}
|
|
1309
|
+
async function runTier(options, req, classification, policy, apiKey, sampling) {
|
|
1310
|
+
const budgets = options.budgets ?? {};
|
|
1311
|
+
const beaconMs = budgets.beaconMs ?? 300;
|
|
1312
|
+
const verifyMs = policy.mode === "enforce" ? budgets.verifyEnforceMs ?? 3e3 : budgets.verifyObserveMs ?? 1500;
|
|
1313
|
+
const gatewayConfig = {
|
|
1314
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1315
|
+
...apiKey && { apiKey },
|
|
1316
|
+
counterpartyId: options.counterpartyId,
|
|
1317
|
+
// The gateway handles its own error semantics; init self-probes don't
|
|
1318
|
+
// belong in a per-request edge function.
|
|
1319
|
+
disableInitChecks: true
|
|
1320
|
+
};
|
|
1321
|
+
try {
|
|
1322
|
+
switch (classification.tier) {
|
|
1323
|
+
case "verified": {
|
|
1324
|
+
if (policy.depth === "classify") {
|
|
1325
|
+
return baseOutcome(classification);
|
|
1326
|
+
}
|
|
1327
|
+
const result = await withBudget(
|
|
1328
|
+
verify(
|
|
1329
|
+
gatewayConfig,
|
|
1330
|
+
buildVerificationRequest(options, req, classification, policy.depth === "authorize")
|
|
1331
|
+
),
|
|
1332
|
+
verifyMs
|
|
1333
|
+
);
|
|
1334
|
+
if (!result) {
|
|
1335
|
+
const outcome = baseOutcome(classification);
|
|
1336
|
+
outcome.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
|
|
1337
|
+
return outcome;
|
|
1338
|
+
}
|
|
1339
|
+
return outcomeFromVerifyResult(classification, result);
|
|
1340
|
+
}
|
|
1341
|
+
case "identified": {
|
|
1342
|
+
if (policy.depth === "authorize" && classification.artifacts) {
|
|
1343
|
+
const result = await withBudget(
|
|
1344
|
+
verify(gatewayConfig, buildVerificationRequest(options, req, classification, true)),
|
|
1345
|
+
verifyMs
|
|
1346
|
+
);
|
|
1347
|
+
if (!result) {
|
|
1348
|
+
const outcome2 = baseOutcome(classification);
|
|
1349
|
+
outcome2.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
|
|
1350
|
+
return outcome2;
|
|
1351
|
+
}
|
|
1352
|
+
return outcomeFromVerifyResult(classification, result);
|
|
1353
|
+
}
|
|
1354
|
+
await emitBeacon(options, req, classification, beaconMs);
|
|
1355
|
+
const outcome = baseOutcome(classification);
|
|
1356
|
+
if (policy.mode === "enforce" && policy.depth !== "classify") {
|
|
1357
|
+
outcome.decision = "deny";
|
|
1358
|
+
outcome.reasons = ["agent is not registered with AstraSync"];
|
|
1359
|
+
}
|
|
1360
|
+
return outcome;
|
|
1361
|
+
}
|
|
1362
|
+
case "anonymous-bot": {
|
|
1363
|
+
if (sampled(sampling.anonymousBeaconRate, options.random)) {
|
|
1364
|
+
await emitBeacon(
|
|
1365
|
+
options,
|
|
1366
|
+
req,
|
|
1367
|
+
{ ...classification, protocol: classification.protocol ?? "ua-heuristic" },
|
|
1368
|
+
beaconMs
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
return baseOutcome(classification);
|
|
1372
|
+
}
|
|
1373
|
+
default:
|
|
1374
|
+
return baseOutcome(classification);
|
|
1375
|
+
}
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
const outcome = baseOutcome(classification);
|
|
1378
|
+
outcome.reasons = [
|
|
1379
|
+
`edge pipeline error \u2014 fail-open: ${err instanceof Error ? err.message : String(err)}`
|
|
1380
|
+
];
|
|
1381
|
+
return outcome;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/edge-core/evaluate.ts
|
|
1386
|
+
async function evaluateEdgeRequest(options, req, config, apiKey) {
|
|
1387
|
+
const policy = resolvePolicy(config, req.uri);
|
|
1388
|
+
try {
|
|
1389
|
+
if (policy.skip) return { policy, application: passUntouched() };
|
|
1390
|
+
const host = headerValue(req, "x-forwarded-host") ?? headerValue(req, "host") ?? new URL(options.counterpartyUrl).host;
|
|
1391
|
+
const classification = classify(req, host, policy.depth);
|
|
1392
|
+
if (classification.tier === "human") {
|
|
1393
|
+
return { policy, classification, application: passUntouched() };
|
|
1394
|
+
}
|
|
1395
|
+
const outcome = await runTier(options, req, classification, policy, apiKey, config.sampling);
|
|
1396
|
+
if (outcome.decision !== "allow") {
|
|
1397
|
+
options.log?.(
|
|
1398
|
+
"info",
|
|
1399
|
+
policy.mode === "enforce" ? `${outcome.decision.toUpperCase()}: ${req.method} ${req.uri}` : `[OBSERVE] would have been ${outcome.decision.toUpperCase()}: ${req.method} ${req.uri}`,
|
|
1400
|
+
{ tier: outcome.tier, protocol: outcome.protocol, reasons: outcome.reasons }
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
return {
|
|
1404
|
+
policy,
|
|
1405
|
+
classification,
|
|
1406
|
+
outcome,
|
|
1407
|
+
application: applyOutcome(options, outcome, policy)
|
|
1408
|
+
};
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
options.log?.("error", "edge evaluation error \u2014 passing request through", {
|
|
1411
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1412
|
+
});
|
|
1413
|
+
return { policy, application: passUntouched() };
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
export {
|
|
1417
|
+
ASAT_HEADERS,
|
|
1418
|
+
_patterns,
|
|
1419
|
+
applyOutcome,
|
|
1420
|
+
buildDenyBody,
|
|
1421
|
+
buildManualReviewBody,
|
|
1422
|
+
buildObservedMetadata,
|
|
1423
|
+
buildVerificationRequest,
|
|
1424
|
+
classify,
|
|
1425
|
+
detectCommerce,
|
|
1426
|
+
emitBeacon,
|
|
1427
|
+
evaluateEdgeRequest,
|
|
1428
|
+
headerValue,
|
|
1429
|
+
outcomeFromVerifyResult,
|
|
1430
|
+
passUntouched,
|
|
1431
|
+
requestUrl,
|
|
1432
|
+
resolvePolicy,
|
|
1433
|
+
runTier,
|
|
1434
|
+
sampled,
|
|
1435
|
+
withBudget
|
|
1436
|
+
};
|
|
1437
|
+
//# sourceMappingURL=index.mjs.map
|