@astrasyncai/verification-gateway 3.0.0 → 3.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/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 +145 -93
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +145 -93
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +29 -11
- package/dist/adapters/mcp.d.ts +29 -11
- package/dist/adapters/mcp.js +43 -102
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +43 -102
- 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 +126 -56
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +126 -56
- 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 +25 -14
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +25 -14
- 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 +3 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +3 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.js +18 -21
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +18 -21
- 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/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- 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 +18 -21
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +18 -21
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-CrfwoNAR.d.ts → express-BowlMHQF.d.ts} +1 -1
- package/dist/{express-ienhAXps.d.mts → express-CeoSdOAZ.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +18 -21
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +18 -21
- 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-CEg_WG6y.d.mts → index-B51W8gn8.d.mts} +1 -1
- package/dist/{index-DC5f8eoQ.d.ts → index-DBmlycVm.d.ts} +1 -1
- package/dist/{index-B5e2IDWU.d.mts → index-DtGziFEm.d.mts} +1 -1
- package/dist/{index-CCdZxvAr.d.ts → index-DzXXBuLm.d.ts} +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +209 -191
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -191
- 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/{nextjs-DSpisQst.d.mts → nextjs-BW1rzr1I.d.mts} +1 -1
- package/dist/{nextjs-66R1KW8e.d.ts → nextjs-V_K0qlAQ.d.ts} +1 -1
- package/dist/{sdk-5U_CBRpr.d.mts → sdk-ZYgI7G9f.d.ts} +14 -3
- package/dist/{sdk-Bm8np66n.d.ts → sdk-e5jg7sqW.d.mts} +14 -3
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +10 -0
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +10 -0
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CgDCUfo8.d.mts → types-BNiLZY0i.d.mts} +1 -1
- package/dist/{types-R5N4ET6x.d.ts → types-DJi-u3fz.d.ts} +1 -1
- package/dist/{types-B3USs-Kx.d.mts → types-rFh4VMH4.d.mts} +30 -2
- package/dist/{types-B3USs-Kx.d.ts → types-rFh4VMH4.d.ts} +30 -2
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -126,7 +126,7 @@ function getCapabilities(accessLevel) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// src/version.ts
|
|
129
|
-
var SDK_VERSION = "3.
|
|
129
|
+
var SDK_VERSION = "3.2.0";
|
|
130
130
|
|
|
131
131
|
// src/well-known.ts
|
|
132
132
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -231,7 +231,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
var verificationCache = /* @__PURE__ */ new Map();
|
|
234
|
-
function getCacheKey(request) {
|
|
234
|
+
function getCacheKey(request, counterpartyId) {
|
|
235
235
|
const c = request.credentials;
|
|
236
236
|
return [
|
|
237
237
|
c.astraId || "",
|
|
@@ -244,6 +244,14 @@ function getCacheKey(request) {
|
|
|
244
244
|
request.jurisdiction || "",
|
|
245
245
|
request.transactionValue ?? "",
|
|
246
246
|
request.currency || "",
|
|
247
|
+
// SECURITY (cross-merchant cache leak): the merchant identity is sent via
|
|
248
|
+
// `config.counterpartyId`, NOT on the request, so it was previously absent
|
|
249
|
+
// from the key — two verifies for the SAME agent/purpose/action/value but
|
|
250
|
+
// DIFFERENT merchants collided, and a grant at a permissive merchant (low
|
|
251
|
+
// trust floor) was served for a stricter one. Same bug class as the
|
|
252
|
+
// duration omission (F-A1-07). counterpartyId affects the backend verdict
|
|
253
|
+
// (trust floor / per-route policy), so it MUST key the cache.
|
|
254
|
+
counterpartyId || "",
|
|
247
255
|
request.counterpartyUrl || "",
|
|
248
256
|
request.counterpartyType || "",
|
|
249
257
|
request.isSubAgentRequest ? "1" : "0",
|
|
@@ -267,8 +275,8 @@ function getCacheKey(request) {
|
|
|
267
275
|
request.callerMetadata?.agentCardUrl || ""
|
|
268
276
|
].join("|");
|
|
269
277
|
}
|
|
270
|
-
function getCachedResult(request) {
|
|
271
|
-
const key = getCacheKey(request);
|
|
278
|
+
function getCachedResult(request, counterpartyId) {
|
|
279
|
+
const key = getCacheKey(request, counterpartyId);
|
|
272
280
|
const cached = verificationCache.get(key);
|
|
273
281
|
if (cached && cached.expiresAt > Date.now()) {
|
|
274
282
|
return cached.result;
|
|
@@ -280,9 +288,9 @@ function getCachedResult(request) {
|
|
|
280
288
|
}
|
|
281
289
|
var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
|
|
282
290
|
var DEFAULT_STEP_UP_TTL_SECONDS = 300;
|
|
283
|
-
function cacheResult(request, result, configuredTtl) {
|
|
291
|
+
function cacheResult(request, result, configuredTtl, counterpartyId) {
|
|
284
292
|
const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
|
|
285
|
-
const key = getCacheKey(request);
|
|
293
|
+
const key = getCacheKey(request, counterpartyId);
|
|
286
294
|
verificationCache.set(key, {
|
|
287
295
|
result,
|
|
288
296
|
expiresAt: Date.now() + ttlSeconds * 1e3
|
|
@@ -480,7 +488,7 @@ async function verify(config, request) {
|
|
|
480
488
|
);
|
|
481
489
|
}
|
|
482
490
|
if (mergedConfig.cacheTtl !== 0) {
|
|
483
|
-
const cached = getCachedResult(request);
|
|
491
|
+
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
484
492
|
if (cached) {
|
|
485
493
|
if (mergedConfig.debug) {
|
|
486
494
|
console.log("[VerificationGateway] Returning cached result");
|
|
@@ -532,8 +540,8 @@ async function verify(config, request) {
|
|
|
532
540
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
533
541
|
// Extract sessionId so decisions can be recorded for denials too
|
|
534
542
|
sessionId: apiResponse.sessionId,
|
|
535
|
-
//
|
|
536
|
-
//
|
|
543
|
+
// Anonymous traffic has no session → correlationId is the per-attempt
|
|
544
|
+
// linking key (the sessionId-equivalent for anonymous callers).
|
|
537
545
|
correlationId: apiResponse.correlationId,
|
|
538
546
|
recommendation: apiResponse.recommendation,
|
|
539
547
|
recommendationReasons: apiResponse.recommendationReasons
|
|
@@ -607,17 +615,14 @@ async function verify(config, request) {
|
|
|
607
615
|
};
|
|
608
616
|
} else if (result.recommendation === "step_up_required") {
|
|
609
617
|
result.requiresStepUp = true;
|
|
610
|
-
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
611
|
-
result.accessLevel = "read-only";
|
|
612
|
-
}
|
|
613
618
|
result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
|
|
614
619
|
}
|
|
615
620
|
if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
|
|
616
|
-
cacheResult(request, result, mergedConfig.cacheTtl);
|
|
621
|
+
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
617
622
|
}
|
|
618
623
|
return result;
|
|
619
624
|
}
|
|
620
|
-
async function recordDecision(config, sessionId, decision, reason
|
|
625
|
+
async function recordDecision(config, sessionId, decision, reason) {
|
|
621
626
|
const headers = { "Content-Type": "application/json" };
|
|
622
627
|
if (config.apiKey) {
|
|
623
628
|
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
@@ -626,36 +631,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
|
|
|
626
631
|
await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
|
|
627
632
|
method: "POST",
|
|
628
633
|
headers,
|
|
629
|
-
body: JSON.stringify({
|
|
630
|
-
decision,
|
|
631
|
-
reason,
|
|
632
|
-
...override && {
|
|
633
|
-
overriddenBy: override.overriddenBy,
|
|
634
|
-
toolName: override.toolName,
|
|
635
|
-
requestedLevel: override.requestedLevel,
|
|
636
|
-
grantedLevel: override.grantedLevel
|
|
637
|
-
}
|
|
638
|
-
})
|
|
639
|
-
}).catch(() => {
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
|
|
643
|
-
const headers = { "Content-Type": "application/json" };
|
|
644
|
-
if (config.apiKey) {
|
|
645
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
646
|
-
headers["X-API-Key"] = config.apiKey;
|
|
647
|
-
}
|
|
648
|
-
await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
|
|
649
|
-
method: "POST",
|
|
650
|
-
headers,
|
|
651
|
-
body: JSON.stringify({
|
|
652
|
-
correlationId,
|
|
653
|
-
reason,
|
|
654
|
-
overriddenBy: override.overriddenBy,
|
|
655
|
-
toolName: override.toolName,
|
|
656
|
-
requestedLevel: override.requestedLevel,
|
|
657
|
-
grantedLevel: override.grantedLevel
|
|
658
|
-
})
|
|
634
|
+
body: JSON.stringify({ decision, reason })
|
|
659
635
|
}).catch(() => {
|
|
660
636
|
});
|
|
661
637
|
}
|
|
@@ -721,6 +697,9 @@ function setHttpHeaders(headers, credentials) {
|
|
|
721
697
|
if (credentials.pdlss?.purpose) {
|
|
722
698
|
const purposeValue = credentials.pdlss.purpose.action ? `${credentials.pdlss.purpose.category}:${credentials.pdlss.purpose.action}` : credentials.pdlss.purpose.category;
|
|
723
699
|
result[`${HEADER_PREFIX}Purpose`] = purposeValue;
|
|
700
|
+
if (credentials.pdlss.purpose.action) {
|
|
701
|
+
result[`${HEADER_PREFIX}Action`] = credentials.pdlss.purpose.action;
|
|
702
|
+
}
|
|
724
703
|
}
|
|
725
704
|
if (credentials.pdlss?.duration?.maxSessionDuration) {
|
|
726
705
|
result[`${HEADER_PREFIX}Duration`] = String(credentials.pdlss.duration.maxSessionDuration);
|
|
@@ -750,6 +729,13 @@ function extractHttpCredentials(headers) {
|
|
|
750
729
|
purpose: { category, action }
|
|
751
730
|
};
|
|
752
731
|
}
|
|
732
|
+
const astraAction = getValue(`${HEADER_PREFIX}Action`) ?? getValue("x-astra-action");
|
|
733
|
+
if (astraAction) {
|
|
734
|
+
credentials.pdlss = {
|
|
735
|
+
...credentials.pdlss,
|
|
736
|
+
purpose: { category: credentials.pdlss?.purpose?.category ?? "", action: astraAction }
|
|
737
|
+
};
|
|
738
|
+
}
|
|
753
739
|
const duration = getValue(`${HEADER_PREFIX}Duration`) ?? getValue("x-astra-duration");
|
|
754
740
|
if (duration) {
|
|
755
741
|
credentials.pdlss = {
|
|
@@ -767,6 +753,85 @@ function extractHttpCredentials(headers) {
|
|
|
767
753
|
return credentials;
|
|
768
754
|
}
|
|
769
755
|
|
|
756
|
+
// src/adapters/http-pdlss.ts
|
|
757
|
+
var HTTP_METHOD_ACTION_TABLE = {
|
|
758
|
+
GET: "data.read",
|
|
759
|
+
HEAD: "data.read",
|
|
760
|
+
OPTIONS: "data.read",
|
|
761
|
+
POST: "data.write",
|
|
762
|
+
PUT: "data.write",
|
|
763
|
+
PATCH: "data.write",
|
|
764
|
+
DELETE: "data.delete"
|
|
765
|
+
};
|
|
766
|
+
var DEFAULT_HTTP_ACTION = "data.write";
|
|
767
|
+
var DEFAULT_HTTP_PURPOSE = "data";
|
|
768
|
+
function actionForHttpMethod(method) {
|
|
769
|
+
return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;
|
|
770
|
+
}
|
|
771
|
+
function normalizePurposeHeader(value) {
|
|
772
|
+
const colon = value.indexOf(":");
|
|
773
|
+
if (colon >= 0) {
|
|
774
|
+
return { purpose: value.slice(0, colon) };
|
|
775
|
+
}
|
|
776
|
+
const dot = value.indexOf(".");
|
|
777
|
+
if (dot > 0 && dot < value.length - 1) {
|
|
778
|
+
return { purpose: value.slice(0, dot), actionCandidate: value };
|
|
779
|
+
}
|
|
780
|
+
return { purpose: value };
|
|
781
|
+
}
|
|
782
|
+
function resolveHttpPdlss(input) {
|
|
783
|
+
const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : void 0;
|
|
784
|
+
let action;
|
|
785
|
+
let actionSource;
|
|
786
|
+
if (input.routeAction) {
|
|
787
|
+
action = input.routeAction;
|
|
788
|
+
actionSource = "route_config";
|
|
789
|
+
} else if (input.hasCustomActionExtractor && input.customAction) {
|
|
790
|
+
action = input.customAction;
|
|
791
|
+
actionSource = "custom_extractor";
|
|
792
|
+
} else if (!input.hasCustomActionExtractor && input.astraAction) {
|
|
793
|
+
action = input.astraAction;
|
|
794
|
+
actionSource = "header";
|
|
795
|
+
} else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {
|
|
796
|
+
action = fromHeader.actionCandidate;
|
|
797
|
+
actionSource = "purpose_header_derived";
|
|
798
|
+
} else {
|
|
799
|
+
action = actionForHttpMethod(input.method);
|
|
800
|
+
actionSource = "method_table";
|
|
801
|
+
}
|
|
802
|
+
let purpose;
|
|
803
|
+
let purposeSource;
|
|
804
|
+
if (input.routePurpose) {
|
|
805
|
+
purpose = input.routePurpose;
|
|
806
|
+
purposeSource = "route_config";
|
|
807
|
+
} else if (input.hasCustomPurposeExtractor) {
|
|
808
|
+
if (input.customPurpose) {
|
|
809
|
+
purpose = input.customPurpose;
|
|
810
|
+
purposeSource = "custom_extractor";
|
|
811
|
+
}
|
|
812
|
+
} else if (fromHeader) {
|
|
813
|
+
purpose = fromHeader.purpose;
|
|
814
|
+
purposeSource = "header";
|
|
815
|
+
} else if (input.legacyPurpose) {
|
|
816
|
+
purpose = input.legacyPurpose;
|
|
817
|
+
purposeSource = "legacy_header";
|
|
818
|
+
} else if (input.queryPurpose) {
|
|
819
|
+
purpose = input.queryPurpose;
|
|
820
|
+
purposeSource = "query";
|
|
821
|
+
}
|
|
822
|
+
if (!purpose) {
|
|
823
|
+
const dot = action.indexOf(".");
|
|
824
|
+
if (dot > 0) {
|
|
825
|
+
purpose = action.slice(0, dot);
|
|
826
|
+
purposeSource = "action_derived";
|
|
827
|
+
} else {
|
|
828
|
+
purpose = DEFAULT_HTTP_PURPOSE;
|
|
829
|
+
purposeSource = "transport_default";
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return { purpose, action, purposeSource, actionSource };
|
|
833
|
+
}
|
|
834
|
+
|
|
770
835
|
// src/pdlss-pre-check.ts
|
|
771
836
|
function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
772
837
|
const failures = [];
|
|
@@ -825,33 +890,25 @@ function defaultExtractCredentials(req) {
|
|
|
825
890
|
function extractAstraSyncCredentials(req) {
|
|
826
891
|
return extractHttpCredentials(req.headers);
|
|
827
892
|
}
|
|
828
|
-
function
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
case "PUT":
|
|
848
|
-
case "PATCH":
|
|
849
|
-
return "write_data";
|
|
850
|
-
case "DELETE":
|
|
851
|
-
return "delete_data";
|
|
852
|
-
default:
|
|
853
|
-
return "general";
|
|
854
|
-
}
|
|
893
|
+
function headerValue(value) {
|
|
894
|
+
if (typeof value === "string") return value;
|
|
895
|
+
if (Array.isArray(value)) return value[0];
|
|
896
|
+
return void 0;
|
|
897
|
+
}
|
|
898
|
+
function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
|
|
899
|
+
return resolveHttpPdlss({
|
|
900
|
+
method: req.method,
|
|
901
|
+
astraPurpose: headerValue(req.headers["x-astra-purpose"]),
|
|
902
|
+
astraAction: headerValue(req.headers["x-astra-action"]),
|
|
903
|
+
legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
|
|
904
|
+
queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
|
|
905
|
+
routePurpose: routeConfig?.purpose,
|
|
906
|
+
routeAction: routeConfig?.action,
|
|
907
|
+
hasCustomPurposeExtractor: !!customExtractPurpose,
|
|
908
|
+
customPurpose: customExtractPurpose?.(req),
|
|
909
|
+
hasCustomActionExtractor: !!customExtractAction,
|
|
910
|
+
customAction: customExtractAction?.(req)
|
|
911
|
+
});
|
|
855
912
|
}
|
|
856
913
|
function matchRoute(pattern, path, opts) {
|
|
857
914
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
@@ -911,6 +968,7 @@ function createMiddleware(options) {
|
|
|
911
968
|
const {
|
|
912
969
|
extractCredentials: customExtractCredentials,
|
|
913
970
|
extractPurpose: customExtractPurpose,
|
|
971
|
+
extractAction: customExtractAction,
|
|
914
972
|
skipPaths = [],
|
|
915
973
|
onDenied = defaultOnDenied,
|
|
916
974
|
recordDecisions,
|
|
@@ -996,7 +1054,21 @@ function createMiddleware(options) {
|
|
|
996
1054
|
}
|
|
997
1055
|
return next();
|
|
998
1056
|
}
|
|
999
|
-
const
|
|
1057
|
+
const pdlssPair = resolveRequestPdlss(
|
|
1058
|
+
req,
|
|
1059
|
+
routeConfig,
|
|
1060
|
+
customExtractPurpose,
|
|
1061
|
+
customExtractAction
|
|
1062
|
+
);
|
|
1063
|
+
const purpose = pdlssPair.purpose;
|
|
1064
|
+
if (config.debug) {
|
|
1065
|
+
console.debug("[express-middleware] pdlss resolved", {
|
|
1066
|
+
purpose_source: pdlssPair.purposeSource,
|
|
1067
|
+
resolved_purpose: pdlssPair.purpose,
|
|
1068
|
+
action_source: pdlssPair.actionSource,
|
|
1069
|
+
resolved_action: pdlssPair.action
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1000
1072
|
const astraCreds = extractAstraSyncCredentials(req);
|
|
1001
1073
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}`;
|
|
1002
1074
|
const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
|
|
@@ -1040,10 +1112,7 @@ function createMiddleware(options) {
|
|
|
1040
1112
|
const result = await verify(config, {
|
|
1041
1113
|
credentials,
|
|
1042
1114
|
purpose,
|
|
1043
|
-
|
|
1044
|
-
// Backend evaluator tolerates either case as defense-in-depth
|
|
1045
|
-
// (round-18.6 batch 2); SDK emits canonical form.
|
|
1046
|
-
action: req.method.toUpperCase(),
|
|
1115
|
+
action: pdlssPair.action,
|
|
1047
1116
|
resource: req.path,
|
|
1048
1117
|
createSession: shouldRecordDecisions,
|
|
1049
1118
|
counterpartyUrl,
|
|
@@ -1081,35 +1150,12 @@ function createMiddleware(options) {
|
|
|
1081
1150
|
}
|
|
1082
1151
|
return next();
|
|
1083
1152
|
}
|
|
1084
|
-
if (!hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
|
|
1085
|
-
const insufficientFailure = {
|
|
1086
|
-
dimension: "access_level.insufficient",
|
|
1087
|
-
message: `Endpoint requires accessLevel '${routeConfig.minAccessLevel}'; agent has '${result.accessLevel}'.`,
|
|
1088
|
-
guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
|
|
1089
|
-
};
|
|
1090
|
-
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
1091
|
-
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
1092
|
-
if (!result.guidance && wellKnownUrls) {
|
|
1093
|
-
result.guidance = {
|
|
1094
|
-
message: insufficientFailure.message,
|
|
1095
|
-
registrationUrl: wellKnownUrls.registrationUrl,
|
|
1096
|
-
documentationUrl: wellKnownUrls.documentationUrl
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1100
|
-
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
dedupeFailures(result);
|
|
1104
|
-
onDenied(result, req, res);
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
1153
|
if (routeConfig.minTrustScore && result.agent) {
|
|
1108
1154
|
if (result.agent.trustScore < routeConfig.minTrustScore) {
|
|
1109
1155
|
const trustFailure = {
|
|
1110
|
-
dimension: "
|
|
1111
|
-
message:
|
|
1112
|
-
guidance: "
|
|
1156
|
+
dimension: "endpoint.trust",
|
|
1157
|
+
message: "Trust below the route requirement for this endpoint.",
|
|
1158
|
+
guidance: "Trust is below this route's floor. Trust is not overridable \u2014 the agent either meets the endpoint's trust policy or it doesn't. Raise the agent's trust via real signals (KYD, blockchain registration, agent-card), or have the operator lower the route's minTrustScore."
|
|
1113
1159
|
};
|
|
1114
1160
|
result.failures = [...result.failures ?? [], trustFailure];
|
|
1115
1161
|
result.denialReasons = [trustFailure.message];
|
|
@@ -1241,28 +1287,15 @@ function extractAstraSyncCredentialsFromNextRequest(request) {
|
|
|
1241
1287
|
});
|
|
1242
1288
|
return extractHttpCredentials(headers);
|
|
1243
1289
|
}
|
|
1244
|
-
function
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
}
|
|
1253
|
-
switch (request.method.toUpperCase()) {
|
|
1254
|
-
case "GET":
|
|
1255
|
-
return "read_data";
|
|
1256
|
-
case "POST":
|
|
1257
|
-
return "write_data";
|
|
1258
|
-
case "PUT":
|
|
1259
|
-
case "PATCH":
|
|
1260
|
-
return "write_data";
|
|
1261
|
-
case "DELETE":
|
|
1262
|
-
return "delete_data";
|
|
1263
|
-
default:
|
|
1264
|
-
return "general";
|
|
1265
|
-
}
|
|
1290
|
+
function resolveNextPdlss(request, routeConfig) {
|
|
1291
|
+
return resolveHttpPdlss({
|
|
1292
|
+
method: request.method,
|
|
1293
|
+
astraPurpose: request.headers.get("x-astra-purpose") ?? void 0,
|
|
1294
|
+
astraAction: request.headers.get("x-astra-action") ?? void 0,
|
|
1295
|
+
legacyPurpose: request.headers.get("x-purpose") ?? void 0,
|
|
1296
|
+
routePurpose: routeConfig?.purpose,
|
|
1297
|
+
routeAction: routeConfig?.action
|
|
1298
|
+
});
|
|
1266
1299
|
}
|
|
1267
1300
|
function generateCommerceShieldHtml(result, options) {
|
|
1268
1301
|
const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
|
|
@@ -1475,7 +1508,16 @@ function createMiddleware2(options) {
|
|
|
1475
1508
|
}
|
|
1476
1509
|
const credentials = extractCredentialsFromNextRequest(request);
|
|
1477
1510
|
const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
|
|
1478
|
-
const
|
|
1511
|
+
const pdlssPair = resolveNextPdlss(request, routeConfig);
|
|
1512
|
+
const purpose = pdlssPair.purpose;
|
|
1513
|
+
if (config.debug) {
|
|
1514
|
+
console.debug("[nextjs-middleware] pdlss resolved", {
|
|
1515
|
+
purpose_source: pdlssPair.purposeSource,
|
|
1516
|
+
resolved_purpose: pdlssPair.purpose,
|
|
1517
|
+
action_source: pdlssPair.actionSource,
|
|
1518
|
+
resolved_action: pdlssPair.action
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1479
1521
|
const astraCreds = extractAstraSyncCredentialsFromNextRequest(request);
|
|
1480
1522
|
const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
|
|
1481
1523
|
if (preCheckFailures.length > 0) {
|
|
@@ -1529,10 +1571,7 @@ function createMiddleware2(options) {
|
|
|
1529
1571
|
const result = await verify(config, {
|
|
1530
1572
|
credentials,
|
|
1531
1573
|
purpose,
|
|
1532
|
-
|
|
1533
|
-
// Backend evaluator tolerates either case as defense-in-depth
|
|
1534
|
-
// (round-18.6 batch 2); SDK emits canonical form.
|
|
1535
|
-
action: request.method.toUpperCase(),
|
|
1574
|
+
action: pdlssPair.action,
|
|
1536
1575
|
resource: pathname,
|
|
1537
1576
|
counterpartyUrl,
|
|
1538
1577
|
counterpartyType: config.counterpartyType || "website",
|
|
@@ -1547,7 +1586,7 @@ function createMiddleware2(options) {
|
|
|
1547
1586
|
agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0
|
|
1548
1587
|
}
|
|
1549
1588
|
});
|
|
1550
|
-
if (!result.identityVerified || !result.policyAllowed
|
|
1589
|
+
if (!result.identityVerified || !result.policyAllowed) {
|
|
1551
1590
|
if (pathname.startsWith("/api/")) {
|
|
1552
1591
|
return NextResponse.json(
|
|
1553
1592
|
{
|
|
@@ -1555,10 +1594,8 @@ function createMiddleware2(options) {
|
|
|
1555
1594
|
error: {
|
|
1556
1595
|
// Round-18 G4: 401 → identity missing (re-auth); 403 → identity
|
|
1557
1596
|
// OK, policy denied (update PDLSS / step up).
|
|
1558
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "
|
|
1597
|
+
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
1559
1598
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1560
|
-
accessLevel: result.accessLevel,
|
|
1561
|
-
required: routeConfig.minAccessLevel,
|
|
1562
1599
|
guidance: result.guidance
|
|
1563
1600
|
}
|
|
1564
1601
|
},
|
|
@@ -1586,7 +1623,6 @@ function createMiddleware2(options) {
|
|
|
1586
1623
|
response.headers.set("X-AstraSync-Access-Level", result.accessLevel);
|
|
1587
1624
|
if (result.agent) {
|
|
1588
1625
|
response.headers.set("X-AstraSync-Agent-Id", result.agent.astraId);
|
|
1589
|
-
response.headers.set("X-AstraSync-Trust-Score", result.agent.trustScore.toString());
|
|
1590
1626
|
}
|
|
1591
1627
|
return response;
|
|
1592
1628
|
};
|
|
@@ -1660,7 +1696,13 @@ var VerificationGatewayClient = class {
|
|
|
1660
1696
|
return this.executeWithRetry(() => quickVerify(this.config, credentials));
|
|
1661
1697
|
}
|
|
1662
1698
|
/**
|
|
1663
|
-
* Check if an agent has a specific access level
|
|
1699
|
+
* Check if an agent has a specific access level.
|
|
1700
|
+
*
|
|
1701
|
+
* @deprecated 3.2.0 — the access-level band is informational only; it no
|
|
1702
|
+
* longer gates in the middleware adapters (post-3.1.0 feedback #1). This
|
|
1703
|
+
* explicit opt-in query still works, but prefer gating on the server-side
|
|
1704
|
+
* policy decision (identity + policy + trust) or per-route `minTrustScore`.
|
|
1705
|
+
* Explicit per-route condition→constraint rules are the Phase-2 successor.
|
|
1664
1706
|
*/
|
|
1665
1707
|
async hasAccess(credentials, requiredLevel) {
|
|
1666
1708
|
const result = await this.quickVerify(credentials);
|
|
@@ -3287,9 +3329,9 @@ function toBuf(bytes) {
|
|
|
3287
3329
|
new Uint8Array(out).set(bytes);
|
|
3288
3330
|
return out;
|
|
3289
3331
|
}
|
|
3290
|
-
function checkTimestamp(
|
|
3291
|
-
if (!
|
|
3292
|
-
const ts = parseTimestamp(
|
|
3332
|
+
function checkTimestamp(headerValue2, toleranceSec, nowFn) {
|
|
3333
|
+
if (!headerValue2) return { ok: false, error: "missing Timestamp header" };
|
|
3334
|
+
const ts = parseTimestamp(headerValue2);
|
|
3293
3335
|
if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
|
|
3294
3336
|
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
3295
3337
|
if (Math.abs(now - ts) > toleranceSec) {
|
|
@@ -3524,14 +3566,14 @@ import {
|
|
|
3524
3566
|
} from "@x402/core/schemas";
|
|
3525
3567
|
import { safeBase64Decode } from "@x402/core/utils";
|
|
3526
3568
|
function extractX402FromRequest(request) {
|
|
3527
|
-
const
|
|
3569
|
+
const headerValue2 = readHeader4(request.headers, "x-payment");
|
|
3528
3570
|
if (request.body && typeof request.body === "object") {
|
|
3529
3571
|
const parsed = tryParsePayload(request.body);
|
|
3530
3572
|
if (parsed) return buildPayloadContext(parsed, "body");
|
|
3531
3573
|
}
|
|
3532
|
-
if (
|
|
3574
|
+
if (headerValue2) {
|
|
3533
3575
|
try {
|
|
3534
|
-
const decoded = safeBase64Decode(
|
|
3576
|
+
const decoded = safeBase64Decode(headerValue2);
|
|
3535
3577
|
if (decoded) {
|
|
3536
3578
|
const json = JSON.parse(decoded);
|
|
3537
3579
|
const parsed = tryParsePayload(json);
|
|
@@ -3554,10 +3596,10 @@ function extractX402FromResponse(response) {
|
|
|
3554
3596
|
const parsed = tryParseRequired(response.body);
|
|
3555
3597
|
if (parsed) return buildRequiredContext(parsed, "body");
|
|
3556
3598
|
}
|
|
3557
|
-
const
|
|
3558
|
-
if (
|
|
3599
|
+
const headerValue2 = readHeader4(response.headers, "x-payment-required");
|
|
3600
|
+
if (headerValue2) {
|
|
3559
3601
|
try {
|
|
3560
|
-
const decoded = safeBase64Decode(
|
|
3602
|
+
const decoded = safeBase64Decode(headerValue2);
|
|
3561
3603
|
if (decoded) {
|
|
3562
3604
|
const json = JSON.parse(decoded);
|
|
3563
3605
|
const parsed = tryParseRequired(json);
|
|
@@ -4393,7 +4435,10 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
4393
4435
|
}
|
|
4394
4436
|
let action;
|
|
4395
4437
|
let actionSource;
|
|
4396
|
-
if (
|
|
4438
|
+
if (toolGate?.action !== void 0) {
|
|
4439
|
+
action = toolGate.action;
|
|
4440
|
+
actionSource = "tool_gate";
|
|
4441
|
+
} else if (headerAction) {
|
|
4397
4442
|
action = headerAction;
|
|
4398
4443
|
actionSource = "header";
|
|
4399
4444
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
@@ -4504,7 +4549,6 @@ function createMcpMiddleware(options) {
|
|
|
4504
4549
|
return next();
|
|
4505
4550
|
}
|
|
4506
4551
|
req.mcpRequest = parsed;
|
|
4507
|
-
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
4508
4552
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
4509
4553
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
4510
4554
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -4541,7 +4585,7 @@ function createMcpMiddleware(options) {
|
|
|
4541
4585
|
return next();
|
|
4542
4586
|
}
|
|
4543
4587
|
}
|
|
4544
|
-
const { level: minAccessLevel
|
|
4588
|
+
const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
|
|
4545
4589
|
toolGates,
|
|
4546
4590
|
methodGates
|
|
4547
4591
|
});
|
|
@@ -4567,7 +4611,7 @@ function createMcpMiddleware(options) {
|
|
|
4567
4611
|
req.path,
|
|
4568
4612
|
headerPurpose,
|
|
4569
4613
|
headerAction,
|
|
4570
|
-
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
4614
|
+
gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : void 0
|
|
4571
4615
|
);
|
|
4572
4616
|
if (config.debug) {
|
|
4573
4617
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
@@ -4577,6 +4621,23 @@ function createMcpMiddleware(options) {
|
|
|
4577
4621
|
resolved_action: pdlss.action
|
|
4578
4622
|
});
|
|
4579
4623
|
}
|
|
4624
|
+
if (!pdlss.purpose) {
|
|
4625
|
+
const id = req.body?.id ?? null;
|
|
4626
|
+
res.status(400).json({
|
|
4627
|
+
jsonrpc: "2.0",
|
|
4628
|
+
id,
|
|
4629
|
+
error: {
|
|
4630
|
+
code: -32602,
|
|
4631
|
+
message: "PDLSS_PURPOSE_REQUIRED",
|
|
4632
|
+
data: {
|
|
4633
|
+
dimension: "pdlss.purpose",
|
|
4634
|
+
detail: "This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool\u2019s purpose in its toolGate config.",
|
|
4635
|
+
resolvedAction: pdlss.action
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
});
|
|
4639
|
+
return;
|
|
4640
|
+
}
|
|
4580
4641
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
4581
4642
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
4582
4643
|
const result = await verify(config, {
|
|
@@ -4600,7 +4661,6 @@ function createMcpMiddleware(options) {
|
|
|
4600
4661
|
});
|
|
4601
4662
|
req.agentVerification = result;
|
|
4602
4663
|
const sessionId = result.sessionId;
|
|
4603
|
-
const correlationId = result.correlationId;
|
|
4604
4664
|
if (!result.identityVerified || !result.policyAllowed) {
|
|
4605
4665
|
if (shouldRecordDecisions && sessionId) {
|
|
4606
4666
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -4621,48 +4681,6 @@ function createMcpMiddleware(options) {
|
|
|
4621
4681
|
}
|
|
4622
4682
|
return next();
|
|
4623
4683
|
}
|
|
4624
|
-
if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
|
|
4625
|
-
const insufficientFailure = {
|
|
4626
|
-
dimension: "access_level.insufficient",
|
|
4627
|
-
message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
|
|
4628
|
-
guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
|
|
4629
|
-
};
|
|
4630
|
-
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
4631
|
-
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
4632
|
-
if (!result.guidance && wellKnownUrls) {
|
|
4633
|
-
result.guidance = {
|
|
4634
|
-
message: insufficientFailure.message,
|
|
4635
|
-
registrationUrl: wellKnownUrls.registrationUrl,
|
|
4636
|
-
documentationUrl: wellKnownUrls.documentationUrl
|
|
4637
|
-
};
|
|
4638
|
-
}
|
|
4639
|
-
if (shouldRecordDecisions) {
|
|
4640
|
-
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
4641
|
-
const override = {
|
|
4642
|
-
overriddenBy: overrideKind,
|
|
4643
|
-
...parsed.toolName && { toolName: parsed.toolName },
|
|
4644
|
-
requestedLevel: minAccessLevel,
|
|
4645
|
-
grantedLevel: result.accessLevel
|
|
4646
|
-
};
|
|
4647
|
-
if (sessionId) {
|
|
4648
|
-
recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
|
|
4649
|
-
() => {
|
|
4650
|
-
}
|
|
4651
|
-
);
|
|
4652
|
-
} else if (correlationId) {
|
|
4653
|
-
recordAnonymousLocalOverride(
|
|
4654
|
-
config,
|
|
4655
|
-
correlationId,
|
|
4656
|
-
override,
|
|
4657
|
-
result.denialReasons?.[0]
|
|
4658
|
-
).catch(() => {
|
|
4659
|
-
});
|
|
4660
|
-
}
|
|
4661
|
-
}
|
|
4662
|
-
dedupeFailures2(result);
|
|
4663
|
-
onDenied(result, req, res);
|
|
4664
|
-
return;
|
|
4665
|
-
}
|
|
4666
4684
|
if (effectiveAstraId) {
|
|
4667
4685
|
res.setHeader(
|
|
4668
4686
|
MCP_VERIFIED_HOP_HEADER,
|