@adcp/sdk 13.0.0-rc.8 → 13.0.0-rc.9
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/lib/adapters/roster-account-store.d.mts +9 -8
- package/dist/lib/adapters/roster-account-store.d.ts +9 -8
- package/dist/lib/adapters/roster-account-store.d.ts.map +1 -1
- package/dist/lib/adapters/roster-account-store.js +2 -1
- package/dist/lib/adapters/roster-account-store.js.map +1 -1
- package/dist/lib/adapters/roster-account-store.mjs +2 -1
- package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
- package/dist/lib/net/address-guards.d.ts.map +1 -1
- package/dist/lib/net/address-guards.js +6 -0
- package/dist/lib/net/address-guards.js.map +1 -1
- package/dist/lib/net/address-guards.mjs +6 -0
- package/dist/lib/net/address-guards.mjs.map +1 -1
- package/dist/lib/net/ssrf-fetch.d.ts.map +1 -1
- package/dist/lib/net/ssrf-fetch.js +66 -53
- package/dist/lib/net/ssrf-fetch.js.map +1 -1
- package/dist/lib/net/ssrf-fetch.mjs +66 -53
- package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-mode.d.mts +2 -1
- package/dist/lib/server/account-mode.d.ts +2 -1
- package/dist/lib/server/account-mode.d.ts.map +1 -1
- package/dist/lib/server/account-mode.js +1 -0
- package/dist/lib/server/account-mode.js.map +1 -1
- package/dist/lib/server/account-mode.mjs +1 -0
- package/dist/lib/server/account-mode.mjs.map +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +10 -6
- package/dist/lib/server/create-adcp-server.d.ts +10 -6
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +2 -1
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +2 -1
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/decisioning/account.d.mts +16 -11
- package/dist/lib/server/decisioning/account.d.ts +16 -11
- package/dist/lib/server/decisioning/account.d.ts.map +1 -1
- package/dist/lib/server/decisioning/account.js.map +1 -1
- package/dist/lib/server/decisioning/account.mjs.map +1 -1
- package/dist/lib/server/decisioning/pagination.d.mts +2 -0
- package/dist/lib/server/decisioning/pagination.d.ts +2 -0
- package/dist/lib/server/decisioning/pagination.d.ts.map +1 -1
- package/dist/lib/server/decisioning/pagination.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.d.mts +20 -14
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts +20 -14
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +32 -6
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +32 -6
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/sales.d.mts +16 -4
- package/dist/lib/server/decisioning/specialisms/sales.d.ts +16 -4
- package/dist/lib/server/decisioning/specialisms/sales.d.ts.map +1 -1
- package/dist/lib/server/decisioning/specialisms/sales.js.map +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/server/legacy/v5/index.d.mts +2 -1
- package/dist/lib/server/legacy/v5/index.d.ts +2 -1
- package/dist/lib/server/legacy/v5/index.d.ts.map +1 -1
- package/dist/lib/server/legacy/v5/index.js.map +1 -1
- package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.d.mts +2 -0
- package/dist/lib/testing/compliance/comply.d.ts +2 -0
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +45 -14
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +45 -14
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/compliance/index.d.mts +1 -1
- package/dist/lib/testing/compliance/index.d.ts +1 -1
- package/dist/lib/testing/compliance/index.d.ts.map +1 -1
- package/dist/lib/testing/compliance/index.js.map +1 -1
- package/dist/lib/testing/compliance/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.d.ts.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.js +9 -1
- package/dist/lib/testing/compliance/storyboard-tracks.js.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +9 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
- package/dist/lib/testing/compliance/types.d.mts +26 -25
- package/dist/lib/testing/compliance/types.d.ts +26 -25
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/compliance/types.js.map +1 -1
- package/dist/lib/testing/index.d.mts +2 -2
- package/dist/lib/testing/index.d.ts +2 -2
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js +16 -0
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/index.mjs +16 -0
- package/dist/lib/testing/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.js +20 -12
- package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.mjs +20 -12
- package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
- package/dist/lib/testing/storyboard/creative-assets.d.mts +23 -0
- package/dist/lib/testing/storyboard/creative-assets.d.ts +23 -0
- package/dist/lib/testing/storyboard/creative-assets.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/creative-assets.js +322 -45
- package/dist/lib/testing/storyboard/creative-assets.js.map +1 -1
- package/dist/lib/testing/storyboard/creative-assets.mjs +321 -45
- package/dist/lib/testing/storyboard/creative-assets.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.d.mts +4 -1
- package/dist/lib/testing/storyboard/index.d.ts +4 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js +20 -0
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs +19 -0
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.mts +21 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.ts +22 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.js +668 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.js.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.mjs +638 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.mjs.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.mts +5 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.ts +6 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.js +44 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.js.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.mjs +20 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/index.mjs.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.mts +38 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.ts +39 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.js +17 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.js.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.mjs +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/types.mjs.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.mts +32 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.ts +33 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.js +105 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.js.map +1 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.mjs +80 -0
- package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.mjs.map +1 -0
- package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/probes.js +2 -1
- package/dist/lib/testing/storyboard/probes.js.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +2 -1
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +382 -69
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +387 -70
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/seeding.js +3 -1
- package/dist/lib/testing/storyboard/seeding.js.map +1 -1
- package/dist/lib/testing/storyboard/seeding.mjs +3 -1
- package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +16 -2
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +16 -2
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/trusted-match-context-replay.d.mts +11 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.d.ts +12 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.js +340 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.js.map +1 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.mjs +316 -0
- package/dist/lib/testing/storyboard/trusted-match-context-replay.mjs.map +1 -0
- package/dist/lib/testing/storyboard/types.d.mts +84 -15
- package/dist/lib/testing/storyboard/types.d.ts +84 -15
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js +2 -0
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +2 -0
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +3 -0
- package/dist/lib/testing/storyboard/validations.d.ts +3 -0
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +64 -24
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +64 -24
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.d.mts +8 -2
- package/dist/lib/types/server-payload-aliases.d.ts +8 -2
- package/dist/lib/types/server-payload-aliases.d.ts.map +1 -1
- package/dist/lib/types/server-payload-aliases.js.map +1 -1
- package/dist/lib/upstream-recorder/recorder.d.mts +7 -18
- package/dist/lib/upstream-recorder/recorder.d.ts +7 -18
- package/dist/lib/upstream-recorder/recorder.d.ts.map +1 -1
- package/dist/lib/upstream-recorder/recorder.js +17 -10
- package/dist/lib/upstream-recorder/recorder.js.map +1 -1
- package/dist/lib/upstream-recorder/recorder.mjs +12 -5
- package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
- package/dist/lib/utils/redact-secrets.d.ts.map +1 -1
- package/dist/lib/utils/redact-secrets.js +7 -1
- package/dist/lib/utils/redact-secrets.js.map +1 -1
- package/dist/lib/utils/redact-secrets.mjs +7 -1
- package/dist/lib/utils/redact-secrets.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/docs/guides/BUILD-AN-AGENT.md +4 -2
- package/docs/llms.txt +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var grader_exports = {};
|
|
20
|
+
__export(grader_exports, {
|
|
21
|
+
buildAuthorizationServerMetadataUrl: () => buildAuthorizationServerMetadataUrl,
|
|
22
|
+
buildProtectedResourceMetadataUrl: () => buildProtectedResourceMetadataUrl,
|
|
23
|
+
gradeOAuthMetadataGraph: () => gradeOAuthMetadataGraph,
|
|
24
|
+
gradeOAuthMetadataGraphWithTransport: () => gradeOAuthMetadataGraphWithTransport,
|
|
25
|
+
normalizeOAuthResourceForComparison: () => normalizeOAuthResourceForComparison,
|
|
26
|
+
redactOAuthUrlForOutput: () => redactOAuthUrlForOutput,
|
|
27
|
+
redactOAuthUrlsInText: () => redactOAuthUrlsInText
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(grader_exports);
|
|
30
|
+
var import_address_guards = require('../../../net/address-guards.js');
|
|
31
|
+
var import_ssrf_fetch = require('../../../net/ssrf-fetch.js');
|
|
32
|
+
const LIMITS = Object.freeze({
|
|
33
|
+
authorizationServers: 16,
|
|
34
|
+
uniqueUrls: 64,
|
|
35
|
+
totalRequests: 64,
|
|
36
|
+
responseBytes: 2 * 1024 * 1024,
|
|
37
|
+
responseBytesPerRequest: 256 * 1024,
|
|
38
|
+
redirects: 3,
|
|
39
|
+
wholeRunMs: 6e4
|
|
40
|
+
});
|
|
41
|
+
const DIAGNOSTIC_ORDER = Object.freeze([
|
|
42
|
+
"oauth_graph_limit_exceeded",
|
|
43
|
+
"oauth_fetch_blocked",
|
|
44
|
+
"oauth_protected_resource_metadata_unavailable",
|
|
45
|
+
"oauth_protected_resource_metadata_invalid",
|
|
46
|
+
"oauth_resource_mismatch",
|
|
47
|
+
"oauth_authorization_servers_empty",
|
|
48
|
+
"oauth_authorization_server_url_invalid",
|
|
49
|
+
"oauth_authorization_server_metadata_unavailable",
|
|
50
|
+
"oauth_authorization_server_metadata_invalid",
|
|
51
|
+
"oauth_issuer_mismatch",
|
|
52
|
+
"oauth_endpoint_url_invalid",
|
|
53
|
+
"oauth_endpoint_unreachable",
|
|
54
|
+
"oauth_jwks_unavailable"
|
|
55
|
+
]);
|
|
56
|
+
function normalizeOAuthResourceForComparison(value) {
|
|
57
|
+
const match = /^([A-Za-z][A-Za-z\d+.-]*):\/\/([^/?#]*)([\s\S]*)$/.exec(value);
|
|
58
|
+
if (!match) return value;
|
|
59
|
+
const scheme = match[1].toLowerCase();
|
|
60
|
+
const authority = match[2];
|
|
61
|
+
const remainder = match[3];
|
|
62
|
+
const at = authority.lastIndexOf("@");
|
|
63
|
+
const userinfo = at >= 0 ? authority.slice(0, at + 1) : "";
|
|
64
|
+
const hostAndPort = at >= 0 ? authority.slice(at + 1) : authority;
|
|
65
|
+
let host;
|
|
66
|
+
let port = "";
|
|
67
|
+
if (hostAndPort.startsWith("[")) {
|
|
68
|
+
const bracket = hostAndPort.indexOf("]");
|
|
69
|
+
if (bracket < 0) return value;
|
|
70
|
+
host = hostAndPort.slice(0, bracket + 1).toLowerCase();
|
|
71
|
+
const suffix = hostAndPort.slice(bracket + 1);
|
|
72
|
+
if (suffix && !/^:\d+$/.test(suffix)) return value;
|
|
73
|
+
port = suffix.slice(1);
|
|
74
|
+
} else {
|
|
75
|
+
const colon = hostAndPort.lastIndexOf(":");
|
|
76
|
+
if (colon >= 0) {
|
|
77
|
+
const suffix = hostAndPort.slice(colon + 1);
|
|
78
|
+
if (!/^\d+$/.test(suffix)) return value;
|
|
79
|
+
host = hostAndPort.slice(0, colon).toLowerCase();
|
|
80
|
+
port = suffix;
|
|
81
|
+
} else {
|
|
82
|
+
host = hostAndPort.toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!host) return value;
|
|
86
|
+
const defaultPort = scheme === "https" ? "443" : scheme === "http" ? "80" : void 0;
|
|
87
|
+
const renderedPort = port && port !== defaultPort ? `:${port}` : "";
|
|
88
|
+
return `${scheme}://${userinfo}${host}${renderedPort}${remainder}`;
|
|
89
|
+
}
|
|
90
|
+
function redactOAuthUrlForOutput(value) {
|
|
91
|
+
try {
|
|
92
|
+
const u = new URL(value);
|
|
93
|
+
if (u.username || u.password) {
|
|
94
|
+
u.username = "REDACTED";
|
|
95
|
+
u.password = "REDACTED";
|
|
96
|
+
}
|
|
97
|
+
for (const key of [...u.searchParams.keys()]) u.searchParams.set(key, "REDACTED");
|
|
98
|
+
if (u.hash) u.hash = "#REDACTED";
|
|
99
|
+
return u.href;
|
|
100
|
+
} catch {
|
|
101
|
+
return "[invalid URL]";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function redactOAuthUrlsInText(value) {
|
|
105
|
+
return value.replace(/https?:\/\/[^\s"'<>]+/gi, (url) => redactOAuthUrlForOutput(url));
|
|
106
|
+
}
|
|
107
|
+
function buildProtectedResourceMetadataUrl(agentUrl) {
|
|
108
|
+
const u = new URL(agentUrl);
|
|
109
|
+
return `${u.origin}/.well-known/oauth-protected-resource${u.pathname}`;
|
|
110
|
+
}
|
|
111
|
+
function buildAuthorizationServerMetadataUrl(issuer) {
|
|
112
|
+
const u = new URL(issuer);
|
|
113
|
+
const issuerPath = u.pathname === "/" ? "" : u.pathname;
|
|
114
|
+
return `${u.origin}/.well-known/oauth-authorization-server${issuerPath}`;
|
|
115
|
+
}
|
|
116
|
+
async function gradeOAuthMetadataGraph(agentUrl, options = {}) {
|
|
117
|
+
return gradeOAuthMetadataGraphWithTransport(agentUrl, options);
|
|
118
|
+
}
|
|
119
|
+
async function gradeOAuthMetadataGraphWithTransport(agentUrl, options = {}) {
|
|
120
|
+
const timeoutSignal = AbortSignal.timeout(LIMITS.wholeRunMs);
|
|
121
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
|
|
122
|
+
const trustedDevelopmentOrigin = options.allowHttp === true ? parseOrigin(agentUrl) : void 0;
|
|
123
|
+
const graphOptions = { ...options, trustedDevelopmentOrigin };
|
|
124
|
+
const state = {
|
|
125
|
+
totalRequests: 0,
|
|
126
|
+
totalResponseBytes: 0,
|
|
127
|
+
uniqueUrls: /* @__PURE__ */ new Set(),
|
|
128
|
+
cache: /* @__PURE__ */ new Map(),
|
|
129
|
+
responses: /* @__PURE__ */ new Map(),
|
|
130
|
+
observations: []
|
|
131
|
+
};
|
|
132
|
+
const findings = [];
|
|
133
|
+
let protectedResourceUrl;
|
|
134
|
+
try {
|
|
135
|
+
protectedResourceUrl = buildProtectedResourceMetadataUrl(agentUrl);
|
|
136
|
+
} catch {
|
|
137
|
+
protectedResourceUrl = redactOAuthUrlForOutput(agentUrl);
|
|
138
|
+
findings.push(finding("oauth_protected_resource_metadata_invalid", "Agent URL is not an absolute URL."));
|
|
139
|
+
return finish(protectedResourceUrl, emptyProbe(protectedResourceUrl), state, findings);
|
|
140
|
+
}
|
|
141
|
+
const protectedResource = await fetchDocument(protectedResourceUrl, graphOptions, signal, state, {
|
|
142
|
+
followRedirects: true,
|
|
143
|
+
kind: "protected_resource_metadata"
|
|
144
|
+
});
|
|
145
|
+
if (protectedResource.blocked) {
|
|
146
|
+
findings.push(fetchFailureFinding("oauth_fetch_blocked", protectedResource.result, void 0));
|
|
147
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
148
|
+
}
|
|
149
|
+
if (protectedResource.limitExceeded) {
|
|
150
|
+
findings.push(fetchFailureFinding("oauth_graph_limit_exceeded", protectedResource.result, void 0));
|
|
151
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
152
|
+
}
|
|
153
|
+
if (protectedResource.result.error || protectedResource.result.status !== 200) {
|
|
154
|
+
findings.push(
|
|
155
|
+
fetchFailureFinding("oauth_protected_resource_metadata_unavailable", protectedResource.result, void 0)
|
|
156
|
+
);
|
|
157
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
158
|
+
}
|
|
159
|
+
const prm = asObject(protectedResource.result.body);
|
|
160
|
+
if (!prm || !isJsonContentType(protectedResource.result.headers["content-type"])) {
|
|
161
|
+
findings.push(
|
|
162
|
+
finding(
|
|
163
|
+
"oauth_protected_resource_metadata_invalid",
|
|
164
|
+
"Protected-resource metadata must be an application/json object.",
|
|
165
|
+
protectedResource.result.url
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
169
|
+
}
|
|
170
|
+
const resource = prm.resource;
|
|
171
|
+
if (typeof resource !== "string" || !isAllowedAbsoluteUrl(resource, trustedDevelopmentOrigin, false)) {
|
|
172
|
+
findings.push(
|
|
173
|
+
finding(
|
|
174
|
+
"oauth_protected_resource_metadata_invalid",
|
|
175
|
+
"Protected-resource metadata resource must be an absolute HTTPS URI.",
|
|
176
|
+
protectedResource.result.url,
|
|
177
|
+
"resource"
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
} else if (normalizeOAuthResourceForComparison(resource) !== normalizeOAuthResourceForComparison(agentUrl)) {
|
|
181
|
+
findings.push(
|
|
182
|
+
finding(
|
|
183
|
+
"oauth_resource_mismatch",
|
|
184
|
+
"Protected-resource metadata resource does not match the agent endpoint.",
|
|
185
|
+
protectedResource.result.url,
|
|
186
|
+
"resource"
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const authorizationServers = prm.authorization_servers;
|
|
191
|
+
if (!Array.isArray(authorizationServers) || authorizationServers.length === 0) {
|
|
192
|
+
findings.push(
|
|
193
|
+
finding(
|
|
194
|
+
"oauth_authorization_servers_empty",
|
|
195
|
+
"authorization_servers must be a non-empty array of issuer URI strings.",
|
|
196
|
+
protectedResource.result.url,
|
|
197
|
+
"authorization_servers"
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
201
|
+
}
|
|
202
|
+
if (authorizationServers.length > LIMITS.authorizationServers) {
|
|
203
|
+
findings.push(
|
|
204
|
+
finding(
|
|
205
|
+
"oauth_graph_limit_exceeded",
|
|
206
|
+
`authorization_servers exceeds the graph limit of ${LIMITS.authorizationServers}.`,
|
|
207
|
+
protectedResource.result.url,
|
|
208
|
+
"authorization_servers"
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
212
|
+
}
|
|
213
|
+
for (let index = 0; index < authorizationServers.length; index++) {
|
|
214
|
+
const issuer = authorizationServers[index];
|
|
215
|
+
if (typeof issuer !== "string" || !isIssuerUrl(issuer, trustedDevelopmentOrigin)) {
|
|
216
|
+
findings.push(
|
|
217
|
+
finding(
|
|
218
|
+
"oauth_authorization_server_url_invalid",
|
|
219
|
+
"Authorization-server identifier must be an absolute HTTPS issuer URI without userinfo, query, or fragment.",
|
|
220
|
+
typeof issuer === "string" ? issuer : protectedResource.result.url,
|
|
221
|
+
`authorization_servers[${index}]`,
|
|
222
|
+
index
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const metadataUrl = buildAuthorizationServerMetadataUrl(issuer);
|
|
228
|
+
const metadata = await fetchDocument(metadataUrl, graphOptions, signal, state, {
|
|
229
|
+
followRedirects: true,
|
|
230
|
+
kind: "authorization_server_metadata",
|
|
231
|
+
authorizationServerIndex: index
|
|
232
|
+
});
|
|
233
|
+
if (metadata.blocked) {
|
|
234
|
+
findings.push(fetchFailureFinding("oauth_fetch_blocked", metadata.result, index));
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (metadata.limitExceeded) {
|
|
238
|
+
findings.push(fetchFailureFinding("oauth_graph_limit_exceeded", metadata.result, index));
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (metadata.result.error || metadata.result.status !== 200) {
|
|
242
|
+
findings.push(fetchFailureFinding("oauth_authorization_server_metadata_unavailable", metadata.result, index));
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const document = asObject(metadata.result.body);
|
|
246
|
+
if (!document || !isJsonContentType(metadata.result.headers["content-type"])) {
|
|
247
|
+
findings.push(
|
|
248
|
+
finding(
|
|
249
|
+
"oauth_authorization_server_metadata_invalid",
|
|
250
|
+
"Authorization-server metadata must be an application/json object.",
|
|
251
|
+
metadata.result.url,
|
|
252
|
+
void 0,
|
|
253
|
+
index
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const issuerField = document.issuer;
|
|
259
|
+
if (typeof issuerField !== "string" || !isIssuerUrl(issuerField, trustedDevelopmentOrigin)) {
|
|
260
|
+
findings.push(
|
|
261
|
+
finding(
|
|
262
|
+
"oauth_authorization_server_metadata_invalid",
|
|
263
|
+
"Authorization-server metadata issuer is missing or invalid.",
|
|
264
|
+
metadata.result.url,
|
|
265
|
+
"issuer",
|
|
266
|
+
index
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
} else if (issuerField !== issuer) {
|
|
270
|
+
findings.push(
|
|
271
|
+
finding(
|
|
272
|
+
"oauth_issuer_mismatch",
|
|
273
|
+
"Authorization-server metadata issuer is not exactly equal to the advertised issuer.",
|
|
274
|
+
metadata.result.url,
|
|
275
|
+
"issuer",
|
|
276
|
+
index
|
|
277
|
+
)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const shape = validateAuthorizationServerShape(document);
|
|
281
|
+
if (!shape.ok) {
|
|
282
|
+
findings.push(
|
|
283
|
+
finding("oauth_authorization_server_metadata_invalid", shape.message, metadata.result.url, shape.field, index)
|
|
284
|
+
);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const endpointEntries = [
|
|
288
|
+
{
|
|
289
|
+
field: "authorization_endpoint",
|
|
290
|
+
kind: "authorization_endpoint",
|
|
291
|
+
probe: shape.needsAuthorizationEndpoint,
|
|
292
|
+
followRedirects: false
|
|
293
|
+
},
|
|
294
|
+
{ field: "token_endpoint", kind: "token_endpoint", probe: shape.needsTokenEndpoint, followRedirects: false },
|
|
295
|
+
{ field: "jwks_uri", kind: "jwks_uri", probe: document.jwks_uri !== void 0, followRedirects: true },
|
|
296
|
+
{ field: "registration_endpoint", kind: "authorization_endpoint", probe: false, followRedirects: false }
|
|
297
|
+
];
|
|
298
|
+
let endpointShapeFailed = false;
|
|
299
|
+
for (const endpoint of endpointEntries) {
|
|
300
|
+
const raw = document[endpoint.field];
|
|
301
|
+
if (raw === void 0) continue;
|
|
302
|
+
if (typeof raw !== "string" || !isAllowedAbsoluteUrl(raw, trustedDevelopmentOrigin, true)) {
|
|
303
|
+
findings.push(
|
|
304
|
+
finding(
|
|
305
|
+
"oauth_endpoint_url_invalid",
|
|
306
|
+
`${endpoint.field} must be an absolute HTTPS URL without credentials or a fragment.`,
|
|
307
|
+
metadata.result.url,
|
|
308
|
+
endpoint.field,
|
|
309
|
+
index
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
endpointShapeFailed = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (endpointShapeFailed) continue;
|
|
316
|
+
for (const endpoint of endpointEntries) {
|
|
317
|
+
if (!endpoint.probe) continue;
|
|
318
|
+
const url = document[endpoint.field];
|
|
319
|
+
const probed = await fetchDocument(url, graphOptions, signal, state, {
|
|
320
|
+
followRedirects: endpoint.followRedirects,
|
|
321
|
+
kind: endpoint.kind,
|
|
322
|
+
authorizationServerIndex: index
|
|
323
|
+
});
|
|
324
|
+
if (probed.blocked) {
|
|
325
|
+
findings.push(fetchFailureFinding("oauth_fetch_blocked", probed.result, index));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (probed.limitExceeded) {
|
|
329
|
+
findings.push(fetchFailureFinding("oauth_graph_limit_exceeded", probed.result, index));
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (endpoint.field === "jwks_uri") {
|
|
333
|
+
const jwks = asObject(probed.result.body);
|
|
334
|
+
if (probed.result.error || probed.result.status !== 200 || !isJsonContentType(probed.result.headers["content-type"]) || !jwks || !Array.isArray(jwks.keys)) {
|
|
335
|
+
findings.push(fetchFailureFinding("oauth_jwks_unavailable", probed.result, index));
|
|
336
|
+
}
|
|
337
|
+
} else if ((endpoint.field === "authorization_endpoint" || endpoint.field === "token_endpoint") && !endpointReachable(endpoint.field, probed.result)) {
|
|
338
|
+
findings.push(fetchFailureFinding("oauth_endpoint_unreachable", probed.result, index));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return finish(protectedResourceUrl, protectedResource.result, state, findings);
|
|
343
|
+
}
|
|
344
|
+
function validateAuthorizationServerShape(document) {
|
|
345
|
+
const grants = document.grant_types_supported;
|
|
346
|
+
if (grants !== void 0 && !isNonEmptyStringArray(grants)) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
message: "grant_types_supported must be a non-empty string array when present.",
|
|
350
|
+
field: "grant_types_supported"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const effectiveGrants = grants === void 0 ? ["authorization_code", "implicit"] : grants;
|
|
354
|
+
const responseTypes = document.response_types_supported;
|
|
355
|
+
if (responseTypes !== void 0 && !isNonEmptyStringArray(responseTypes)) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
message: "response_types_supported must be a non-empty string array when present.",
|
|
359
|
+
field: "response_types_supported"
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const responseTypeUsesAuthorization = Array.isArray(responseTypes) ? responseTypes.some((value) => value.split(/\s+/).some((part) => part === "code" || part === "token")) : false;
|
|
363
|
+
const needsAuthorizationEndpoint = effectiveGrants.some((grant) => grant === "authorization_code" || grant === "implicit") || responseTypeUsesAuthorization;
|
|
364
|
+
const needsTokenEndpoint = !(effectiveGrants.length === 1 && effectiveGrants[0] === "implicit");
|
|
365
|
+
if (needsAuthorizationEndpoint && !isNonEmptyStringArray(responseTypes)) {
|
|
366
|
+
return {
|
|
367
|
+
ok: false,
|
|
368
|
+
message: "response_types_supported is required when the authorization endpoint is used.",
|
|
369
|
+
field: "response_types_supported"
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (needsAuthorizationEndpoint && typeof document.authorization_endpoint !== "string") {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
message: "authorization_endpoint is required for the effective grant and response-type set.",
|
|
376
|
+
field: "authorization_endpoint"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (needsTokenEndpoint && typeof document.token_endpoint !== "string") {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
message: "token_endpoint is required unless the effective grant set is implicit-only.",
|
|
383
|
+
field: "token_endpoint"
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return { ok: true, needsAuthorizationEndpoint, needsTokenEndpoint };
|
|
387
|
+
}
|
|
388
|
+
function endpointReachable(field, result) {
|
|
389
|
+
if (result.error) return false;
|
|
390
|
+
const status = result.status;
|
|
391
|
+
if (field === "token_endpoint") return status > 0 && status < 500 && status !== 410;
|
|
392
|
+
if (status >= 200 && status < 400) return true;
|
|
393
|
+
return status === 400 || status === 401 || status === 403 || status === 405 || status === 429;
|
|
394
|
+
}
|
|
395
|
+
async function fetchDocument(initialUrl, options, signal, state, fetchOptions) {
|
|
396
|
+
const cacheKey = `${fetchOptions.followRedirects ? "follow" : "manual"} ${initialUrl}`;
|
|
397
|
+
const cached = state.cache.get(cacheKey);
|
|
398
|
+
if (cached) return cached;
|
|
399
|
+
let current = initialUrl;
|
|
400
|
+
let redirects = 0;
|
|
401
|
+
while (true) {
|
|
402
|
+
throwIfCallerAborted(options.signal);
|
|
403
|
+
if (signal.aborted) {
|
|
404
|
+
const outcome = failedFetch(current, "OAuth metadata graph exceeded its whole-run timeout.");
|
|
405
|
+
outcome.limitExceeded = true;
|
|
406
|
+
state.cache.set(cacheKey, outcome);
|
|
407
|
+
return outcome;
|
|
408
|
+
}
|
|
409
|
+
let response = state.responses.get(current);
|
|
410
|
+
if (!response) {
|
|
411
|
+
if (!state.uniqueUrls.has(current) && state.uniqueUrls.size >= LIMITS.uniqueUrls) {
|
|
412
|
+
const outcome = failedFetch(current, `OAuth metadata graph exceeded ${LIMITS.uniqueUrls} unique URLs.`);
|
|
413
|
+
outcome.limitExceeded = true;
|
|
414
|
+
state.cache.set(cacheKey, outcome);
|
|
415
|
+
return outcome;
|
|
416
|
+
}
|
|
417
|
+
state.uniqueUrls.add(current);
|
|
418
|
+
if (state.totalRequests >= LIMITS.totalRequests) {
|
|
419
|
+
const outcome = failedFetch(current, `OAuth metadata graph exceeded ${LIMITS.totalRequests} requests.`);
|
|
420
|
+
outcome.limitExceeded = true;
|
|
421
|
+
state.cache.set(cacheKey, outcome);
|
|
422
|
+
return outcome;
|
|
423
|
+
}
|
|
424
|
+
state.totalRequests++;
|
|
425
|
+
try {
|
|
426
|
+
response = options.transport ? await options.transport.fetch(current, { signal, maxBodyBytes: LIMITS.responseBytesPerRequest }) : await defaultTransportFetch(current, options.trustedDevelopmentOrigin, signal, options.trustedFetchFn);
|
|
427
|
+
if (response.connectedPeerAddress && isBlockedAddress(
|
|
428
|
+
response.connectedPeerAddress,
|
|
429
|
+
isTrustedDevelopmentUrl(current, options.trustedDevelopmentOrigin)
|
|
430
|
+
)) {
|
|
431
|
+
const outcome = failedFetch(current, "Connected peer address was rejected by the SSRF policy.");
|
|
432
|
+
outcome.blocked = true;
|
|
433
|
+
state.cache.set(cacheKey, outcome);
|
|
434
|
+
return outcome;
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
throwIfCallerAborted(options.signal);
|
|
438
|
+
const outcome = failedFetch(current, safeFetchError(error));
|
|
439
|
+
if (signal.aborted || error instanceof import_ssrf_fetch.SsrfRefusedError && error.code === "body_exceeds_limit") {
|
|
440
|
+
outcome.limitExceeded = true;
|
|
441
|
+
} else {
|
|
442
|
+
outcome.blocked = error instanceof import_ssrf_fetch.SsrfRefusedError && !["dns_lookup_failed", "dns_empty"].includes(error.code);
|
|
443
|
+
}
|
|
444
|
+
state.cache.set(cacheKey, outcome);
|
|
445
|
+
return outcome;
|
|
446
|
+
}
|
|
447
|
+
state.totalResponseBytes += response.body.byteLength;
|
|
448
|
+
if (state.totalResponseBytes > LIMITS.responseBytes) {
|
|
449
|
+
const outcome = failedFetch(current, `OAuth metadata graph exceeded ${LIMITS.responseBytes} response bytes.`);
|
|
450
|
+
outcome.limitExceeded = true;
|
|
451
|
+
state.cache.set(cacheKey, outcome);
|
|
452
|
+
return outcome;
|
|
453
|
+
}
|
|
454
|
+
state.responses.set(current, response);
|
|
455
|
+
}
|
|
456
|
+
const result = {
|
|
457
|
+
url: redactOAuthUrlForOutput(current),
|
|
458
|
+
status: response.status,
|
|
459
|
+
headers: response.headers,
|
|
460
|
+
body: decodeBody(response.body, response.headers["content-type"])
|
|
461
|
+
};
|
|
462
|
+
state.observations.push({
|
|
463
|
+
kind: fetchOptions.kind,
|
|
464
|
+
url: result.url,
|
|
465
|
+
status: result.status,
|
|
466
|
+
...fetchOptions.authorizationServerIndex !== void 0 && {
|
|
467
|
+
authorization_server_index: fetchOptions.authorizationServerIndex
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
if (!fetchOptions.followRedirects || !isRedirectStatus(response.status)) {
|
|
471
|
+
const outcome = { result, bytes: response.body.byteLength };
|
|
472
|
+
state.cache.set(cacheKey, outcome);
|
|
473
|
+
return outcome;
|
|
474
|
+
}
|
|
475
|
+
const location = response.headers.location;
|
|
476
|
+
if (!location) {
|
|
477
|
+
const outcome = { result, bytes: response.body.byteLength };
|
|
478
|
+
state.cache.set(cacheKey, outcome);
|
|
479
|
+
return outcome;
|
|
480
|
+
}
|
|
481
|
+
if (redirects >= LIMITS.redirects) {
|
|
482
|
+
const outcome = failedFetch(current, `OAuth metadata redirect count exceeded ${LIMITS.redirects}.`);
|
|
483
|
+
outcome.limitExceeded = true;
|
|
484
|
+
state.cache.set(cacheKey, outcome);
|
|
485
|
+
return outcome;
|
|
486
|
+
}
|
|
487
|
+
let next;
|
|
488
|
+
try {
|
|
489
|
+
next = new URL(location, current).href;
|
|
490
|
+
} catch {
|
|
491
|
+
const outcome = failedFetch(current, "OAuth metadata redirect Location was not a valid URL.");
|
|
492
|
+
outcome.blocked = true;
|
|
493
|
+
state.cache.set(cacheKey, outcome);
|
|
494
|
+
return outcome;
|
|
495
|
+
}
|
|
496
|
+
if (!isAllowedAbsoluteUrl(next, options.trustedDevelopmentOrigin, true)) {
|
|
497
|
+
const outcome = failedFetch(next, "OAuth metadata redirect target violated the URL policy.");
|
|
498
|
+
outcome.blocked = true;
|
|
499
|
+
state.cache.set(cacheKey, outcome);
|
|
500
|
+
return outcome;
|
|
501
|
+
}
|
|
502
|
+
redirects++;
|
|
503
|
+
current = next;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function defaultTransportFetch(url, trustedDevelopmentOrigin, signal, trustedFetchFn) {
|
|
507
|
+
const result = await (0, import_ssrf_fetch.ssrfSafeFetch)(url, {
|
|
508
|
+
method: "GET",
|
|
509
|
+
headers: { accept: "application/json", "accept-encoding": "identity" },
|
|
510
|
+
allowPrivateIp: isTrustedDevelopmentUrl(url, trustedDevelopmentOrigin),
|
|
511
|
+
signal,
|
|
512
|
+
timeoutMs: 1e4,
|
|
513
|
+
maxBodyBytes: LIMITS.responseBytesPerRequest,
|
|
514
|
+
...trustedFetchFn && { trustedFetchFn }
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
status: result.status,
|
|
518
|
+
headers: result.headers,
|
|
519
|
+
body: result.body,
|
|
520
|
+
connectedPeerAddress: result.connectionPinned ? result.pinnedAddress : void 0
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function finish(protectedResourceUrl, protectedResourceResult, state, findings) {
|
|
524
|
+
const sorted = findings.slice().sort((a, b) => {
|
|
525
|
+
const codeOrder = DIAGNOSTIC_ORDER.indexOf(a.code) - DIAGNOSTIC_ORDER.indexOf(b.code);
|
|
526
|
+
if (codeOrder !== 0) return codeOrder;
|
|
527
|
+
return (a.authorization_server_index ?? -1) - (b.authorization_server_index ?? -1);
|
|
528
|
+
});
|
|
529
|
+
const first = sorted[0];
|
|
530
|
+
return {
|
|
531
|
+
success: first === void 0,
|
|
532
|
+
protected_resource_url: redactOAuthUrlForOutput(protectedResourceUrl),
|
|
533
|
+
protected_resource_result: sanitizeProtectedResourceResult(protectedResourceResult),
|
|
534
|
+
observations: state.observations,
|
|
535
|
+
findings: sorted,
|
|
536
|
+
...first && { error_code: first.code, error: first.message },
|
|
537
|
+
total_requests: state.totalRequests,
|
|
538
|
+
total_response_bytes: state.totalResponseBytes
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function finding(code, message, url, field, authorizationServerIndex) {
|
|
542
|
+
return {
|
|
543
|
+
code,
|
|
544
|
+
message,
|
|
545
|
+
...url && { url: redactOAuthUrlForOutput(url) },
|
|
546
|
+
...field && { field },
|
|
547
|
+
...authorizationServerIndex !== void 0 && { authorization_server_index: authorizationServerIndex }
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function fetchFailureFinding(code, result, authorizationServerIndex) {
|
|
551
|
+
return finding(code, result.error ?? `HTTP ${result.status}`, result.url, void 0, authorizationServerIndex);
|
|
552
|
+
}
|
|
553
|
+
function failedFetch(url, error) {
|
|
554
|
+
return { result: { ...emptyProbe(redactOAuthUrlForOutput(url)), error }, bytes: 0 };
|
|
555
|
+
}
|
|
556
|
+
function emptyProbe(url) {
|
|
557
|
+
return { url, status: 0, headers: {}, body: null };
|
|
558
|
+
}
|
|
559
|
+
function safeFetchError(error) {
|
|
560
|
+
if (error instanceof import_ssrf_fetch.SsrfRefusedError) return `SSRF fetch refused (${error.code}).`;
|
|
561
|
+
if (error instanceof Error) {
|
|
562
|
+
if (error.name === "AbortError" || /timeout/i.test(error.message)) return "OAuth metadata fetch timed out.";
|
|
563
|
+
return "OAuth metadata fetch failed.";
|
|
564
|
+
}
|
|
565
|
+
return "OAuth metadata fetch failed.";
|
|
566
|
+
}
|
|
567
|
+
function throwIfCallerAborted(signal) {
|
|
568
|
+
if (!signal?.aborted) return;
|
|
569
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
570
|
+
const error = new Error(signal.reason == null ? "The operation was aborted" : String(signal.reason));
|
|
571
|
+
error.name = "AbortError";
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
function isIssuerUrl(value, trustedDevelopmentOrigin) {
|
|
575
|
+
try {
|
|
576
|
+
const u = new URL(value);
|
|
577
|
+
return (u.protocol === "https:" || isTrustedDevelopmentUrl(value, trustedDevelopmentOrigin)) && !u.username && !u.password && !u.search && !u.hash;
|
|
578
|
+
} catch {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function isAllowedAbsoluteUrl(value, trustedDevelopmentOrigin, rejectCredentialsAndFragment) {
|
|
583
|
+
try {
|
|
584
|
+
const u = new URL(value);
|
|
585
|
+
if (u.protocol !== "https:" && !isTrustedDevelopmentUrl(value, trustedDevelopmentOrigin)) return false;
|
|
586
|
+
if (rejectCredentialsAndFragment && (u.username || u.password || u.hash)) return false;
|
|
587
|
+
return true;
|
|
588
|
+
} catch {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function parseOrigin(value) {
|
|
593
|
+
try {
|
|
594
|
+
return new URL(value).origin;
|
|
595
|
+
} catch {
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function isTrustedDevelopmentUrl(value, trustedDevelopmentOrigin) {
|
|
600
|
+
return trustedDevelopmentOrigin !== void 0 && parseOrigin(value) === trustedDevelopmentOrigin;
|
|
601
|
+
}
|
|
602
|
+
function isRedirectStatus(status) {
|
|
603
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
604
|
+
}
|
|
605
|
+
function sanitizeProtectedResourceResult(result) {
|
|
606
|
+
const body = asObject(result.body);
|
|
607
|
+
let safeBody = result.body;
|
|
608
|
+
if (body) {
|
|
609
|
+
const safeRecord = { ...body };
|
|
610
|
+
if (typeof body.resource === "string") {
|
|
611
|
+
safeRecord.resource = redactOAuthUrlForOutput(body.resource);
|
|
612
|
+
}
|
|
613
|
+
if (Array.isArray(body.authorization_servers)) {
|
|
614
|
+
safeRecord.authorization_servers = body.authorization_servers.map(
|
|
615
|
+
(value) => typeof value === "string" ? redactOAuthUrlForOutput(value) : value
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
safeBody = safeRecord;
|
|
619
|
+
}
|
|
620
|
+
const allowedHeaders = /* @__PURE__ */ new Set(["content-type", "content-length", "content-encoding", "location"]);
|
|
621
|
+
const headers = Object.fromEntries(
|
|
622
|
+
Object.entries(result.headers).filter(([key]) => allowedHeaders.has(key.toLowerCase())).map(([key, value]) => [
|
|
623
|
+
key.toLowerCase(),
|
|
624
|
+
key.toLowerCase() === "location" ? redactOAuthUrlForOutput(value) : value
|
|
625
|
+
])
|
|
626
|
+
);
|
|
627
|
+
return {
|
|
628
|
+
...result,
|
|
629
|
+
url: redactOAuthUrlForOutput(result.url),
|
|
630
|
+
headers,
|
|
631
|
+
body: safeBody
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function isBlockedAddress(address, allowPrivate) {
|
|
635
|
+
return (0, import_address_guards.isAlwaysBlocked)(address) || !allowPrivate && (0, import_address_guards.isPrivateIp)(address);
|
|
636
|
+
}
|
|
637
|
+
function isNonEmptyStringArray(value) {
|
|
638
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0);
|
|
639
|
+
}
|
|
640
|
+
function asObject(value) {
|
|
641
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
642
|
+
}
|
|
643
|
+
function isJsonContentType(value) {
|
|
644
|
+
return typeof value === "string" && /^(application\/json|[^;]+\+json)(?:;|$)/i.test(value.trim());
|
|
645
|
+
}
|
|
646
|
+
function decodeBody(body, contentType) {
|
|
647
|
+
if (body.byteLength === 0) return null;
|
|
648
|
+
const text = Buffer.from(body.buffer, body.byteOffset, body.byteLength).toString("utf8");
|
|
649
|
+
if (isJsonContentType(contentType)) {
|
|
650
|
+
try {
|
|
651
|
+
return JSON.parse(text);
|
|
652
|
+
} catch {
|
|
653
|
+
return text;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return text;
|
|
657
|
+
}
|
|
658
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
659
|
+
0 && (module.exports = {
|
|
660
|
+
buildAuthorizationServerMetadataUrl,
|
|
661
|
+
buildProtectedResourceMetadataUrl,
|
|
662
|
+
gradeOAuthMetadataGraph,
|
|
663
|
+
gradeOAuthMetadataGraphWithTransport,
|
|
664
|
+
normalizeOAuthResourceForComparison,
|
|
665
|
+
redactOAuthUrlForOutput,
|
|
666
|
+
redactOAuthUrlsInText
|
|
667
|
+
});
|
|
668
|
+
//# sourceMappingURL=grader.js.map
|