@dbx-tools/appkit-web-search 0.6.71 → 0.6.73

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 CHANGED
@@ -205,14 +205,16 @@ URL's hostname, and a bare host also matches its subdomains; a path entry
205
205
  design: `web_search` citations are silently filtered to the permitted set (the
206
206
  model never surfaces a source it then can't fetch), while an explicit `web_fetch`
207
207
  of a disallowed URL is refused with an error (a visible, correctable mistake).
208
+ `web_fetch` accepts only HTTPS targets that resolve to public addresses, and
209
+ reapplies both checks before every redirect request.
208
210
 
209
211
  Which of the two modes is in force is named by `urlPolicy`, and the effective
210
212
  mode is in the boot log:
211
213
 
212
- | `urlPolicy` | Effect |
213
- | ---------------- | ------------------------------------------------- |
214
- | `"allowlist"` | Only `allowedUrls` entries are reachable |
215
- | `"unrestricted"` | Any URL is reachable; the open mode, stated aloud |
214
+ | `urlPolicy` | Effect |
215
+ | ---------------- | ---------------------------------------- |
216
+ | `"allowlist"` | Only `allowedUrls` entries are reachable |
217
+ | `"unrestricted"` | Any public HTTPS URL is reachable |
216
218
 
217
219
  Leave `urlPolicy` unset and it follows `allowedUrls`: `"allowlist"` when there
218
220
  are entries, `"unrestricted"` when there are none. Set it to say which one you
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Network policy for page fetches.
3
+ *
4
+ * Every request, including redirects, is HTTPS, matches the configured URL
5
+ * allow-list, and resolves only to public addresses. Resolution happens in the
6
+ * request hook immediately before I/O so a redirect cannot bypass the policy.
7
+ *
8
+ * @module
9
+ */
10
+ import { type UrlAllowList } from "./allowlist.ts";
11
+ /** DNS result shape used by Node and by deterministic tests. */
12
+ export interface ResolvedAddress {
13
+ address: string;
14
+ family: number;
15
+ }
16
+ /** Injectable DNS resolver for fetch-policy tests. */
17
+ export type ResolveAddresses = (hostname: string) => Promise<readonly ResolvedAddress[]>;
18
+ /**
19
+ * Validate one concrete page-fetch target immediately before network I/O.
20
+ *
21
+ * Rejects embedded credentials, non-HTTPS URLs, disallowed hosts/paths, and
22
+ * hostnames whose DNS answers include any private, loopback, link-local, or
23
+ * reserved address.
24
+ */
25
+ export declare function assertFetchUrlAllowed(value: string, allowList: UrlAllowList, resolve?: ResolveAddresses): Promise<void>;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Network policy for page fetches.
3
+ *
4
+ * Every request, including redirects, is HTTPS, matches the configured URL
5
+ * allow-list, and resolves only to public addresses. Resolution happens in the
6
+ * request hook immediately before I/O so a redirect cannot bypass the policy.
7
+ *
8
+ * @module
9
+ */
10
+ import { lookup } from "node:dns/promises";
11
+ import { isIP } from "node:net";
12
+ import { ValidationError } from "@databricks/appkit";
13
+ import { assertUrlAllowed } from "./allowlist.js";
14
+ /** Resolve all addresses for a hostname without changing their family. */
15
+ async function resolveAddresses(hostname) {
16
+ return lookup(hostname, { all: true, verbatim: true });
17
+ }
18
+ /** Whether an IPv4 address is private, local, reserved, or non-unicast. */
19
+ function isBlockedIpv4(address) {
20
+ const [a = -1, b = -1, c = -1] = address.split(".").map(Number);
21
+ return (a === 0 ||
22
+ a === 10 ||
23
+ a === 127 ||
24
+ a >= 224 ||
25
+ (a === 100 && b >= 64 && b <= 127) ||
26
+ (a === 169 && b === 254) ||
27
+ (a === 172 && b >= 16 && b <= 31) ||
28
+ (a === 192 && b === 0 && c === 0) ||
29
+ (a === 192 && b === 0 && c === 2) ||
30
+ (a === 192 && b === 168) ||
31
+ (a === 198 && (b === 18 || b === 19)) ||
32
+ (a === 198 && b === 51 && c === 100) ||
33
+ (a === 203 && b === 0 && c === 113));
34
+ }
35
+ /** Whether an IPv6 address is private, local, reserved, or non-unicast. */
36
+ function isBlockedIpv6(address) {
37
+ const normalized = address.toLowerCase();
38
+ return (normalized === "::" ||
39
+ normalized === "::1" ||
40
+ normalized.startsWith("::ffff:") ||
41
+ normalized.startsWith("100:") ||
42
+ normalized.startsWith("2001:db8:") ||
43
+ normalized.startsWith("fc") ||
44
+ normalized.startsWith("fd") ||
45
+ /^fe[89ab]/.test(normalized) ||
46
+ normalized.startsWith("ff"));
47
+ }
48
+ /** Whether an address is globally routable enough for an outbound page fetch. */
49
+ function isPublicAddress(address) {
50
+ return address.family === 6 ? !isBlockedIpv6(address.address) : !isBlockedIpv4(address.address);
51
+ }
52
+ /**
53
+ * Validate one concrete page-fetch target immediately before network I/O.
54
+ *
55
+ * Rejects embedded credentials, non-HTTPS URLs, disallowed hosts/paths, and
56
+ * hostnames whose DNS answers include any private, loopback, link-local, or
57
+ * reserved address.
58
+ */
59
+ export async function assertFetchUrlAllowed(value, allowList, resolve = resolveAddresses) {
60
+ let url;
61
+ try {
62
+ url = new URL(value);
63
+ }
64
+ catch {
65
+ throw ValidationError.invalidValue("url", value, "an absolute HTTPS URL");
66
+ }
67
+ if (url.protocol !== "https:" || url.username || url.password) {
68
+ throw ValidationError.invalidValue("url", value, "an absolute HTTPS URL without embedded credentials");
69
+ }
70
+ assertUrlAllowed(url.href, allowList);
71
+ const literalFamily = isIP(url.hostname);
72
+ const addresses = literalFamily
73
+ ? [{ address: url.hostname, family: literalFamily }]
74
+ : await resolve(url.hostname);
75
+ if (addresses.length === 0 || addresses.some((address) => !isPublicAddress(address))) {
76
+ throw ValidationError.invalidValue("url", value, "a URL that resolves only to public addresses");
77
+ }
78
+ }
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2ZldGNoLXVybC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9fZmV0Y2gtdXJsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDaEMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRXJELE9BQU8sRUFBRSxnQkFBZ0IsRUFBcUIsTUFBTSxnQkFBZ0IsQ0FBQztBQVdyRSwwRUFBMEU7QUFDMUUsS0FBSyxVQUFVLGdCQUFnQixDQUFDLFFBQWdCO0lBQzlDLE9BQU8sTUFBTSxDQUFDLFFBQVEsRUFBRSxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7QUFDekQsQ0FBQztBQUVELDJFQUEyRTtBQUMzRSxTQUFTLGFBQWEsQ0FBQyxPQUFlO0lBQ3BDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hFLE9BQU8sQ0FDTCxDQUFDLEtBQUssQ0FBQztRQUNQLENBQUMsS0FBSyxFQUFFO1FBQ1IsQ0FBQyxLQUFLLEdBQUc7UUFDVCxDQUFDLElBQUksR0FBRztRQUNSLENBQUMsQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUM7UUFDbEMsQ0FBQyxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxHQUFHLENBQUM7UUFDeEIsQ0FBQyxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLElB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Q0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUMsZUFBZSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNyRixNQUFNLGVBQWUsQ0FBQyxZQUFZLENBQ2hDLEtBQUssRUFDTCxLQUFLLEVBQ0wsOENBQThDLENBQy9DLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTmV0d29yayBwb2xpY3kgZm9yIHBhZ2UgZmV0Y2hlcy5cbiAqXG4gKiBFdmVyeSByZXF1ZXN0LCBpbmNsdWRpbmcgcmVkaXJlY3RzLCBpcyBIVFRQUywgbWF0Y2hlcyB0aGUgY29uZmlndXJlZCBVUkxcbiAqIGFsbG93LWxpc3QsIGFuZCByZXNvbHZlcyBvbmx5IHRvIHB1YmxpYyBhZGRyZXNzZXMuIFJlc29sdXRpb24gaGFwcGVucyBpbiB0aGVcbiAqIHJlcXVlc3QgaG9vayBpbW1lZGlhdGVseSBiZWZvcmUgSS9PIHNvIGEgcmVkaXJlY3QgY2Fubm90IGJ5cGFzcyB0aGUgcG9saWN5LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBsb29rdXAgfSBmcm9tIFwibm9kZTpkbnMvcHJvbWlzZXNcIjtcbmltcG9ydCB7IGlzSVAgfSBmcm9tIFwibm9kZTpuZXRcIjtcbmltcG9ydCB7IFZhbGlkYXRpb25FcnJvciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcblxuaW1wb3J0IHsgYXNzZXJ0VXJsQWxsb3dlZCwgdHlwZSBVcmxBbGxvd0xpc3QgfSBmcm9tIFwiLi9hbGxvd2xpc3QudHNcIjtcblxuLyoqIEROUyByZXN1bHQgc2hhcGUgdXNlZCBieSBOb2RlIGFuZCBieSBkZXRlcm1pbmlzdGljIHRlc3RzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXNvbHZlZEFkZHJlc3Mge1xuICBhZGRyZXNzOiBzdHJpbmc7XG4gIGZhbWlseTogbnVtYmVyO1xufVxuXG4vKiogSW5qZWN0YWJsZSBETlMgcmVzb2x2ZXIgZm9yIGZldGNoLXBvbGljeSB0ZXN0cy4gKi9cbmV4cG9ydCB0eXBlIFJlc29sdmVBZGRyZXNzZXMgPSAoaG9zdG5hbWU6IHN0cmluZykgPT4gUHJvbWlzZTxyZWFkb25seSBSZXNvbHZlZEFkZHJlc3NbXT47XG5cbi8qKiBSZXNvbHZlIGFsbCBhZGRyZXNzZXMgZm9yIGEgaG9zdG5hbWUgd2l0aG91dCBjaGFuZ2luZyB0aGVpciBmYW1pbHkuICovXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlQWRkcmVzc2VzKGhvc3RuYW1lOiBzdHJpbmcpOiBQcm9taXNlPHJlYWRvbmx5IFJlc29sdmVkQWRkcmVzc1tdPiB7XG4gIHJldHVybiBsb29rdXAoaG9zdG5hbWUsIHsgYWxsOiB0cnVlLCB2ZXJiYXRpbTogdHJ1ZSB9KTtcbn1cblxuLyoqIFdoZXRoZXIgYW4gSVB2NCBhZGRyZXNzIGlzIHByaXZhdGUsIGxvY2FsLCByZXNlcnZlZCwgb3Igbm9uLXVuaWNhc3QuICovXG5mdW5jdGlvbiBpc0Jsb2NrZWRJcHY0KGFkZHJlc3M6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBbYSA9IC0xLCBiID0gLTEsIGMgPSAtMV0gPSBhZGRyZXNzLnNwbGl0KFwiLlwiKS5tYXAoTnVtYmVyKTtcbiAgcmV0dXJuIChcbiAgICBhID09PSAwIHx8XG4gICAgYSA9PT0gMTAgfHxcbiAgICBhID09PSAxMjcgfHxcbiAgICBhID49IDIyNCB8fFxuICAgIChhID09PSAxMDAgJiYgYiA+PSA2NCAmJiBiIDw9IDEyNykgfHxcbiAgICAoYSA9PT0gMTY5ICYmIGIgPT09IDI1NCkgfHxcbiAgICAoYSA9PT0gMTcyICYmIGIgPj0gMTYgJiYgYiA8PSAzMSkgfHxcbiAgICAoYSA9PT0gMTkyICYmIGIgPT09IDAgJiYgYyA9PT0gMCkgfHxcbiAgICAoYSA9PT0gMTkyICYmIGIgPT09IDAgJiYgYyA9PT0gMikgfHxcbiAgICAoYSA9PT0gMTkyICYmIGIgPT09IDE2OCkgfHxcbiAgICAoYSA9PT0gMTk4ICYmIChiID09PSAxOCB8fCBiID09PSAxOSkpIHx8XG4gICAgKGEgPT09IDE5OCAmJiBiID09PSA1MSAmJiBjID09PSAxMDApIHx8XG4gICAgKGEgPT09IDIwMyAmJiBiID09PSAwICYmIGMgPT09IDExMylcbiAgKTtcbn1cblxuLyoqIFdoZXRoZXIgYW4gSVB2NiBhZGRyZXNzIGlzIHByaXZhdGUsIGxvY2FsLCByZXNlcnZlZCwgb3Igbm9uLXVuaWNhc3QuICovXG5mdW5jdGlvbiBpc0Jsb2NrZWRJcHY2KGFkZHJlc3M6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBub3JtYWxpemVkID0gYWRkcmVzcy50b0xvd2VyQ2FzZSgpO1xuICByZXR1cm4gKFxuICAgIG5vcm1hbGl6ZWQgPT09IFwiOjpcIiB8fFxuICAgIG5vcm1hbGl6ZWQgPT09IFwiOjoxXCIgfHxcbiAgICBub3JtYWxpemVkLnN0YXJ0c1dpdGgoXCI6OmZmZmY6XCIpIHx8XG4gICAgbm9ybWFsaXplZC5zdGFydHNXaXRoKFwiMTAwOlwiKSB8fFxuICAgIG5vcm1hbGl6ZWQuc3RhcnRzV2l0aChcIjIwMDE6ZGI4OlwiKSB8fFxuICAgIG5vcm1hbGl6ZWQuc3RhcnRzV2l0aChcImZjXCIpIHx8XG4gICAgbm9ybWFsaXplZC5zdGFydHNXaXRoKFwiZmRcIikgfHxcbiAgICAvXmZlWzg5YWJdLy50ZXN0KG5vcm1hbGl6ZWQpIHx8XG4gICAgbm9ybWFsaXplZC5zdGFydHNXaXRoKFwiZmZcIilcbiAgKTtcbn1cblxuLyoqIFdoZXRoZXIgYW4gYWRkcmVzcyBpcyBnbG9iYWxseSByb3V0YWJsZSBlbm91Z2ggZm9yIGFuIG91dGJvdW5kIHBhZ2UgZmV0Y2guICovXG5mdW5jdGlvbiBpc1B1YmxpY0FkZHJlc3MoYWRkcmVzczogUmVzb2x2ZWRBZGRyZXNzKTogYm9vbGVhbiB7XG4gIHJldHVybiBhZGRyZXNzLmZhbWlseSA9PT0gNiA/ICFpc0Jsb2NrZWRJcHY2KGFkZHJlc3MuYWRkcmVzcykgOiAhaXNCbG9ja2VkSXB2NChhZGRyZXNzLmFkZHJlc3MpO1xufVxuXG4vKipcbiAqIFZhbGlkYXRlIG9uZSBjb25jcmV0ZSBwYWdlLWZldGNoIHRhcmdldCBpbW1lZGlhdGVseSBiZWZvcmUgbmV0d29yayBJL08uXG4gKlxuICogUmVqZWN0cyBlbWJlZGRlZCBjcmVkZW50aWFscywgbm9uLUhUVFBTIFVSTHMsIGRpc2FsbG93ZWQgaG9zdHMvcGF0aHMsIGFuZFxuICogaG9zdG5hbWVzIHdob3NlIEROUyBhbnN3ZXJzIGluY2x1ZGUgYW55IHByaXZhdGUsIGxvb3BiYWNrLCBsaW5rLWxvY2FsLCBvclxuICogcmVzZXJ2ZWQgYWRkcmVzcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGFzc2VydEZldGNoVXJsQWxsb3dlZChcbiAgdmFsdWU6IHN0cmluZyxcbiAgYWxsb3dMaXN0OiBVcmxBbGxvd0xpc3QsXG4gIHJlc29sdmU6IFJlc29sdmVBZGRyZXNzZXMgPSByZXNvbHZlQWRkcmVzc2VzLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIGxldCB1cmw6IFVSTDtcbiAgdHJ5IHtcbiAgICB1cmwgPSBuZXcgVVJMKHZhbHVlKTtcbiAgfSBjYXRjaCB7XG4gICAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLmludmFsaWRWYWx1ZShcInVybFwiLCB2YWx1ZSwgXCJhbiBhYnNvbHV0ZSBIVFRQUyBVUkxcIik7XG4gIH1cbiAgaWYgKHVybC5wcm90b2NvbCAhPT0gXCJodHRwczpcIiB8fCB1cmwudXNlcm5hbWUgfHwgdXJsLnBhc3N3b3JkKSB7XG4gICAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLmludmFsaWRWYWx1ZShcbiAgICAgIFwidXJsXCIsXG4gICAgICB2YWx1ZSxcbiAgICAgIFwiYW4gYWJzb2x1dGUgSFRUUFMgVVJMIHdpdGhvdXQgZW1iZWRkZWQgY3JlZGVudGlhbHNcIixcbiAgICApO1xuICB9XG4gIGFzc2VydFVybEFsbG93ZWQodXJsLmhyZWYsIGFsbG93TGlzdCk7XG5cbiAgY29uc3QgbGl0ZXJhbEZhbWlseSA9IGlzSVAodXJsLmhvc3RuYW1lKTtcbiAgY29uc3QgYWRkcmVzc2VzID0gbGl0ZXJhbEZhbWlseVxuICAgID8gW3sgYWRkcmVzczogdXJsLmhvc3RuYW1lLCBmYW1pbHk6IGxpdGVyYWxGYW1pbHkgfV1cbiAgICA6IGF3YWl0IHJlc29sdmUodXJsLmhvc3RuYW1lKTtcbiAgaWYgKGFkZHJlc3Nlcy5sZW5ndGggPT09IDAgfHwgYWRkcmVzc2VzLnNvbWUoKGFkZHJlc3MpID0+ICFpc1B1YmxpY0FkZHJlc3MoYWRkcmVzcykpKSB7XG4gICAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLmludmFsaWRWYWx1ZShcbiAgICAgIFwidXJsXCIsXG4gICAgICB2YWx1ZSxcbiAgICAgIFwiYSBVUkwgdGhhdCByZXNvbHZlcyBvbmx5IHRvIHB1YmxpYyBhZGRyZXNzZXNcIixcbiAgICApO1xuICB9XG59XG4iXX0=
package/lib/src/fetch.js CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { log } from "@dbx-tools/shared-core";
18
18
  import { gotScraping } from "got-scraping";
19
- import { assertUrlAllowed } from "./allowlist.js";
19
+ import { assertFetchUrlAllowed } from "./_fetch-url.js";
20
20
  import { toCallSettings, webFetchExecuteDefaults } from "./defaults.js";
21
21
  import { decodeHtmlEntities, htmlToText } from "./html-text.js";
22
22
  import { executeRead } from "./runtime.js";
@@ -42,11 +42,16 @@ function truncate(text, max) {
42
42
  * `signal` cancels the in-flight fetch.
43
43
  */
44
44
  export async function runWebFetch(request, config, signal) {
45
- assertUrlAllowed(request.url, config.allowList);
45
+ await assertFetchUrlAllowed(request.url, config.allowList);
46
46
  const cap = Math.min(request.maxLength ?? config.fetchMaxLength, config.fetchMaxLength);
47
47
  // The response is cached before the format reduction, so the same page read
48
48
  // as text and as html shares one network round trip.
49
- const page = await executeRead("page-fetch", toCallSettings(webFetchExecuteDefaults, config.timeoutMs, ["web-search", "fetch", request.url]), async (executeSignal) => {
49
+ const page = await executeRead("page-fetch", toCallSettings(webFetchExecuteDefaults, config.timeoutMs, [
50
+ "web-search",
51
+ "fetch",
52
+ request.url,
53
+ ...config.allowList.patterns,
54
+ ]), async (executeSignal) => {
50
55
  const response = await gotScraping({
51
56
  url: request.url,
52
57
  // got's own timeout aborts the socket and reports which phase timed
@@ -54,6 +59,13 @@ export async function runWebFetch(request, config, signal) {
54
59
  timeout: { request: config.timeoutMs },
55
60
  throwHttpErrors: false,
56
61
  followRedirect: true,
62
+ hooks: {
63
+ beforeRequest: [
64
+ async (options) => {
65
+ await assertFetchUrlAllowed(String(options.url), config.allowList);
66
+ },
67
+ ],
68
+ },
57
69
  ...(executeSignal ? { signal: executeSignal } : {}),
58
70
  });
59
71
  return {
@@ -63,6 +75,7 @@ export async function runWebFetch(request, config, signal) {
63
75
  body: typeof response.body === "string" ? response.body : String(response.body ?? ""),
64
76
  };
65
77
  }, signal);
78
+ await assertFetchUrlAllowed(page.url, config.allowList);
66
79
  const isHtml = !page.contentType || /html|xml/i.test(page.contentType);
67
80
  const rawContent = request.format === "html" || !isHtml ? page.body : htmlToText(page.body);
68
81
  const { content, truncated } = truncate(rawContent, cap);
@@ -83,4 +96,4 @@ export async function runWebFetch(request, config, signal) {
83
96
  truncated,
84
97
  };
85
98
  }
86
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmV0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZmV0Y2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDM0MsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFbEQsT0FBTyxFQUFFLGNBQWMsRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN4RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUczQyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUFVOUMsbUVBQW1FO0FBQ25FLFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDaEMsTUFBTSxLQUFLLEdBQUcsa0NBQWtDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzVELE1BQU0sS0FBSyxHQUFHLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ3BFLE9BQU8sS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzlDLENBQUM7QUFFRCxvRUFBb0U7QUFDcEUsU0FBUyxRQUFRLENBQUMsSUFBWSxFQUFFLEdBQVc7SUFDekMsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLEdBQUc7UUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLElB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QVMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQzFDLENBQUMsQ0FBQztJQUVILE9BQU87UUFDTCxHQUFHLEVBQUUsSUFBSSxDQUFDLEdBQUc7UUFDYixNQUFNLEVBQUUsSUFBSSxDQUFDLFVBQVU7UUFDdkIsR0FBRyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsV0FBVyxFQUFFLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzlELEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUMzQixPQUFPO1FBQ1AsU0FBUztLQUNWLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBQYWdlIGZldGNoaW5nIG92ZXIge0BsaW5rIGh0dHBzOi8vd3d3Lm5wbWpzLmNvbS9wYWNrYWdlL2dvdC1zY3JhcGluZyB8IGdvdC1zY3JhcGluZ31cbiAqIC0gYSBgZ290YCB3cmFwcGVyIHRoYXQgZ2VuZXJhdGVzIGJyb3dzZXItbGlrZSBUTFMgKyBoZWFkZXIgZmluZ2VycHJpbnRzLCBzb1xuICogZmV0Y2hlcyBzdXJ2aXZlIHRoZSBib3Qgd2FsbHMgYSBwbGFpbiBgZmV0Y2hgIHRyaXBzLiB7QGxpbmsgcnVuV2ViRmV0Y2h9XG4gKiBlbmZvcmNlcyB0aGUgVVJMIGFsbG93LWxpc3QgKGFuIGV4cGxpY2l0IGZldGNoIG9mIGEgZGlzYWxsb3dlZCBVUkwgaXNcbiAqIHJlZnVzZWQsIG5vdCBzaWxlbnRseSBlbXB0aWVkKSwgZmV0Y2hlcyB3aXRoIHRoZSBwbHVnaW4ncyB0aW1lb3V0LCBhbmRcbiAqIHJldHVybnMgZWl0aGVyIHRoZSByYXcgSFRNTCBvciBhIHJlYWRhYmxlIHBsYWluLXRleHQgcmVkdWN0aW9uLCBjYXBwZWQgYXRcbiAqIHRoZSBjb25maWd1cmVkIGxlbmd0aC5cbiAqXG4gKiBUaGUgSFRNTC10by10ZXh0IHJlZHVjdGlvbiBpcyBkZWxpYmVyYXRlbHkgZGVwZW5kZW5jeS1mcmVlIChzdHJpcFxuICogc2NyaXB0L3N0eWxlLCB1bndyYXAgdGFncywgZGVjb2RlIGEgaGFuZGZ1bCBvZiBlbnRpdGllcywgY29sbGFwc2VcbiAqIHdoaXRlc3BhY2UpOiBnb29kIGVub3VnaCB0byBmZWVkIGEgbW9kZWwsIGFuZCBpdCBrZWVwcyB0aGUgYWRkLW9uJ3NcbiAqIGRlcGVuZGVuY3kgc3VyZmFjZSB0byB0aGUgdHdvIGxpYnJhcmllcyB0aGUgdGFzayBjYWxsZWQgZm9yLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgZ290U2NyYXBpbmcgfSBmcm9tIFwiZ290LXNjcmFwaW5nXCI7XG5pbXBvcnQgeyBhc3NlcnRVcmxBbGxvd2VkIH0gZnJvbSBcIi4vYWxsb3dsaXN0LnRzXCI7XG5pbXBvcnQgdHlwZSB7IFJlc29sdmVkV2ViU2VhcmNoQ29uZmlnIH0gZnJvbSBcIi4vY29uZmlnLnRzXCI7XG5pbXBvcnQgeyB0b0NhbGxTZXR0aW5ncywgd2ViRmV0Y2hFeGVjdXRlRGVmYXVsdHMgfSBmcm9tIFwiLi9kZWZhdWx0cy50c1wiO1xuaW1wb3J0IHsgZGVjb2RlSHRtbEVudGl0aWVzLCBodG1sVG9UZXh0IH0gZnJvbSBcIi4vaHRtbC10ZXh0LnRzXCI7XG5pbXBvcnQgeyBleGVjdXRlUmVhZCB9IGZyb20gXCIuL3J1bnRpbWUudHNcIjtcbmltcG9ydCB0eXBlIHsgV2ViRmV0Y2hSZXF1ZXN0LCBXZWJGZXRjaFJlc3VsdCB9IGZyb20gXCIuL3NjaGVtYS50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwid2ViLXNlYXJjaC9mZXRjaFwiKTtcblxuLyoqIFRoZSBzbGljZSBvZiBhIGdvdC1zY3JhcGluZyByZXNwb25zZSB0aGlzIG1vZHVsZSByZWFkcy4gKi9cbmludGVyZmFjZSBGZXRjaGVkUGFnZSB7XG4gIHVybDogc3RyaW5nO1xuICBzdGF0dXNDb2RlOiBudW1iZXI7XG4gIGNvbnRlbnRUeXBlOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gIGJvZHk6IHN0cmluZztcbn1cblxuLyoqIFB1bGwgdGhlIDx0aXRsZT4gdGV4dCBvdXQgb2YgYW4gSFRNTCBkb2N1bWVudCwgd2hlbiBwcmVzZW50LiAqL1xuZnVuY3Rpb24gZXh0cmFjdFRpdGxlKGh0bWw6IHN0cmluZyk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IG1hdGNoID0gLzx0aXRsZVtePl0qPihbXFxzXFxTXSo/KTxcXC90aXRsZT4vaS5leGVjKGh0bWwpO1xuICBjb25zdCB0aXRsZSA9IG1hdGNoPy5bMV0gPyBkZWNvZGVIdG1sRW50aXRpZXMobWF0Y2hbMV0pLnRyaW0oKSA6IFwiXCI7XG4gIHJldHVybiB0aXRsZS5sZW5ndGggPiAwID8gdGl0bGUgOiB1bmRlZmluZWQ7XG59XG5cbi8qKiBUcnVuY2F0ZSBgdGV4dGAgdG8gYG1heGAgY2hhcnMsIHJlcG9ydGluZyB3aGV0aGVyIGl0IHdhcyBjdXQuICovXG5mdW5jdGlvbiB0cnVuY2F0ZSh0ZXh0OiBzdHJpbmcsIG1heDogbnVtYmVyKTogeyBjb250ZW50OiBzdHJpbmc7IHRydW5jYXRlZDogYm9vbGVhbiB9IHtcbiAgaWYgKHRleHQubGVuZ3RoIDw9IG1heCkgcmV0dXJuIHsgY29udGVudDogdGV4dCwgdHJ1bmNhdGVkOiBmYWxzZSB9O1xuICByZXR1cm4geyBjb250ZW50OiB0ZXh0LnNsaWNlKDAsIG1heCksIHRydW5jYXRlZDogdHJ1ZSB9O1xufVxuXG4vKipcbiAqIEZldGNoIGEgc2luZ2xlIFVSTCBhbmQgcmV0dXJuIGl0cyBjb250ZW50IGluIHRoZSByZXF1ZXN0ZWQgZm9ybWF0LlxuICpcbiAqIFRocm93cyB3aGVuIHRoZSBVUkwgaXMgbm90IHBlcm1pdHRlZCBieSB0aGUgYWxsb3ctbGlzdCAodGhlIHZpc2libGUsXG4gKiBjb3JyZWN0YWJsZSBmYWlsdXJlIHRoZSBkZXNpZ24gY2FsbHMgZm9yIG9uIHRoZSBmZXRjaCBwYXRoKS4gVGhlIHJlcXVlc3Qnc1xuICogYG1heExlbmd0aGAgbmFycm93cyAobmV2ZXIgd2lkZW5zKSB0aGUgcGx1Z2luJ3MgYGZldGNoTWF4TGVuZ3RoYCBjYXAuXG4gKiBgc2lnbmFsYCBjYW5jZWxzIHRoZSBpbi1mbGlnaHQgZmV0Y2guXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBydW5XZWJGZXRjaChcbiAgcmVxdWVzdDogV2ViRmV0Y2hSZXF1ZXN0LFxuICBjb25maWc6IFJlc29sdmVkV2ViU2VhcmNoQ29uZmlnLFxuICBzaWduYWw/OiBBYm9ydFNpZ25hbCxcbik6IFByb21pc2U8V2ViRmV0Y2hSZXN1bHQ+IHtcbiAgYXNzZXJ0VXJsQWxsb3dlZChyZXF1ZXN0LnVybCwgY29uZmlnLmFsbG93TGlzdCk7XG4gIGNvbnN0IGNhcCA9IE1hdGgubWluKHJlcXVlc3QubWF4TGVuZ3RoID8/IGNvbmZpZy5mZXRjaE1heExlbmd0aCwgY29uZmlnLmZldGNoTWF4TGVuZ3RoKTtcblxuICAvLyBUaGUgcmVzcG9uc2UgaXMgY2FjaGVkIGJlZm9yZSB0aGUgZm9ybWF0IHJlZHVjdGlvbiwgc28gdGhlIHNhbWUgcGFnZSByZWFkXG4gIC8vIGFzIHRleHQgYW5kIGFzIGh0bWwgc2hhcmVzIG9uZSBuZXR3b3JrIHJvdW5kIHRyaXAuXG4gIGNvbnN0IHBhZ2UgPSBhd2FpdCBleGVjdXRlUmVhZChcbiAgICBcInBhZ2UtZmV0Y2hcIixcbiAgICB0b0NhbGxTZXR0aW5ncyh3ZWJGZXRjaEV4ZWN1dGVEZWZhdWx0cywgY29uZmlnLnRpbWVvdXRNcywgW1wid2ViLXNlYXJjaFwiLCBcImZldGNoXCIsIHJlcXVlc3QudXJsXSksXG4gICAgYXN5bmMgKGV4ZWN1dGVTaWduYWwpOiBQcm9taXNlPEZldGNoZWRQYWdlPiA9PiB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGdvdFNjcmFwaW5nKHtcbiAgICAgICAgdXJsOiByZXF1ZXN0LnVybCxcbiAgICAgICAgLy8gZ290J3Mgb3duIHRpbWVvdXQgYWJvcnRzIHRoZSBzb2NrZXQgYW5kIHJlcG9ydHMgd2hpY2ggcGhhc2UgdGltZWRcbiAgICAgICAgLy8gb3V0OyB0aGUgaW50ZXJjZXB0b3IgdGltZW91dCBib3VuZHMgdGhlIHdob2xlIGF0dGVtcHQgYXJvdW5kIGl0LlxuICAgICAgICB0aW1lb3V0OiB7IHJlcXVlc3Q6IGNvbmZpZy50aW1lb3V0TXMgfSxcbiAgICAgICAgdGhyb3dIdHRwRXJyb3JzOiBmYWxzZSxcbiAgICAgICAgZm9sbG93UmVkaXJlY3Q6IHRydWUsXG4gICAgICAgIC4uLihleGVjdXRlU2lnbmFsID8geyBzaWduYWw6IGV4ZWN1dGVTaWduYWwgfSA6IHt9KSxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgdXJsOiByZXNwb25zZS51cmwgPz8gcmVxdWVzdC51cmwsXG4gICAgICAgIHN0YXR1c0NvZGU6IHJlc3BvbnNlLnN0YXR1c0NvZGUsXG4gICAgICAgIGNvbnRlbnRUeXBlOiByZXNwb25zZS5oZWFkZXJzW1wiY29udGVudC10eXBlXCJdLFxuICAgICAgICBib2R5OiB0eXBlb2YgcmVzcG9uc2UuYm9keSA9PT0gXCJzdHJpbmdcIiA/IHJlc3BvbnNlLmJvZHkgOiBTdHJpbmcocmVzcG9uc2UuYm9keSA/PyBcIlwiKSxcbiAgICAgIH07XG4gICAgfSxcbiAgICBzaWduYWwsXG4gICk7XG5cbiAgY29uc3QgaXNIdG1sID0gIXBhZ2UuY29udGVudFR5cGUgfHwgL2h0bWx8eG1sL2kudGVzdChwYWdlLmNvbnRlbnRUeXBlKTtcbiAgY29uc3QgcmF3Q29udGVudCA9IHJlcXVlc3QuZm9ybWF0ID09PSBcImh0bWxcIiB8fCAhaXNIdG1sID8gcGFnZS5ib2R5IDogaHRtbFRvVGV4dChwYWdlLmJvZHkpO1xuICBjb25zdCB7IGNvbnRlbnQsIHRydW5jYXRlZCB9ID0gdHJ1bmNhdGUocmF3Q29udGVudCwgY2FwKTtcbiAgY29uc3QgdGl0bGUgPSBpc0h0bWwgPyBleHRyYWN0VGl0bGUocGFnZS5ib2R5KSA6IHVuZGVmaW5lZDtcblxuICBsb2dnZXIuZGVidWcoXCJmZXRjaGVkXCIsIHtcbiAgICB1cmw6IHBhZ2UudXJsLFxuICAgIHN0YXR1czogcGFnZS5zdGF0dXNDb2RlLFxuICAgIGJ5dGVzOiBwYWdlLmJvZHkubGVuZ3RoLFxuICAgIHJldHVybmVkOiBjb250ZW50Lmxlbmd0aCxcbiAgICAuLi4odHJ1bmNhdGVkID8geyB0cnVuY2F0ZWQ6IHRydWUgfSA6IHt9KSxcbiAgfSk7XG5cbiAgcmV0dXJuIHtcbiAgICB1cmw6IHBhZ2UudXJsLFxuICAgIHN0YXR1czogcGFnZS5zdGF0dXNDb2RlLFxuICAgIC4uLihwYWdlLmNvbnRlbnRUeXBlID8geyBjb250ZW50VHlwZTogcGFnZS5jb250ZW50VHlwZSB9IDoge30pLFxuICAgIC4uLih0aXRsZSA/IHsgdGl0bGUgfSA6IHt9KSxcbiAgICBjb250ZW50LFxuICAgIHRydW5jYXRlZCxcbiAgfTtcbn1cbiJdfQ==
99
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmV0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZmV0Y2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDM0MsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFeEQsT0FBTyxFQUFFLGNBQWMsRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN4RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUczQyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUFVOUMsbUVBQW1FO0FBQ25FLFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDaEMsTUFBTSxLQUFLLEdBQUcsa0NBQWtDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzVELE1BQU0sS0FBSyxHQUFHLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ3BFLE9BQU8sS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzlDLENBQUM7QUFFRCxvRUFBb0U7QUFDcEUsU0FBUyxRQUFRLENBQUMsSUFBWSxFQUFFLEdBQVc7SUFDekMsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLEdBQUc7UUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLElB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LENBQUMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFFM0QsTUFBTSxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUU7UUFDdEIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsTUFBTSxFQUFFLElBQUksQ0FBQyxVQUFVO1FBQ3ZCLEtBQUssRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU07UUFDdkIsUUFBUSxFQUFFLE9BQU8sQ0FBQyxNQUFNO1FBQ3hCLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7S0FDMUMsQ0FBQyxDQUFDO0lBRUgsT0FBTztRQUNMLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLE1BQU0sRUFBRSxJQUFJLENBQUMsVUFBVTtRQUN2QixHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsRUFBRSxXQUFXLEVBQUUsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDOUQsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzNCLE9BQU87UUFDUCxTQUFTO0tBQ1YsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFBhZ2UgZmV0Y2hpbmcgb3ZlciB7QGxpbmsgaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvZ290LXNjcmFwaW5nIHwgZ290LXNjcmFwaW5nfVxuICogLSBhIGBnb3RgIHdyYXBwZXIgdGhhdCBnZW5lcmF0ZXMgYnJvd3Nlci1saWtlIFRMUyArIGhlYWRlciBmaW5nZXJwcmludHMsIHNvXG4gKiBmZXRjaGVzIHN1cnZpdmUgdGhlIGJvdCB3YWxscyBhIHBsYWluIGBmZXRjaGAgdHJpcHMuIHtAbGluayBydW5XZWJGZXRjaH1cbiAqIGVuZm9yY2VzIHRoZSBVUkwgYWxsb3ctbGlzdCAoYW4gZXhwbGljaXQgZmV0Y2ggb2YgYSBkaXNhbGxvd2VkIFVSTCBpc1xuICogcmVmdXNlZCwgbm90IHNpbGVudGx5IGVtcHRpZWQpLCBmZXRjaGVzIHdpdGggdGhlIHBsdWdpbidzIHRpbWVvdXQsIGFuZFxuICogcmV0dXJucyBlaXRoZXIgdGhlIHJhdyBIVE1MIG9yIGEgcmVhZGFibGUgcGxhaW4tdGV4dCByZWR1Y3Rpb24sIGNhcHBlZCBhdFxuICogdGhlIGNvbmZpZ3VyZWQgbGVuZ3RoLlxuICpcbiAqIFRoZSBIVE1MLXRvLXRleHQgcmVkdWN0aW9uIGlzIGRlbGliZXJhdGVseSBkZXBlbmRlbmN5LWZyZWUgKHN0cmlwXG4gKiBzY3JpcHQvc3R5bGUsIHVud3JhcCB0YWdzLCBkZWNvZGUgYSBoYW5kZnVsIG9mIGVudGl0aWVzLCBjb2xsYXBzZVxuICogd2hpdGVzcGFjZSk6IGdvb2QgZW5vdWdoIHRvIGZlZWQgYSBtb2RlbCwgYW5kIGl0IGtlZXBzIHRoZSBhZGQtb24nc1xuICogZGVwZW5kZW5jeSBzdXJmYWNlIHRvIHRoZSB0d28gbGlicmFyaWVzIHRoZSB0YXNrIGNhbGxlZCBmb3IuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGxvZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgeyBnb3RTY3JhcGluZyB9IGZyb20gXCJnb3Qtc2NyYXBpbmdcIjtcbmltcG9ydCB7IGFzc2VydEZldGNoVXJsQWxsb3dlZCB9IGZyb20gXCIuL19mZXRjaC11cmwudHNcIjtcbmltcG9ydCB0eXBlIHsgUmVzb2x2ZWRXZWJTZWFyY2hDb25maWcgfSBmcm9tIFwiLi9jb25maWcudHNcIjtcbmltcG9ydCB7IHRvQ2FsbFNldHRpbmdzLCB3ZWJGZXRjaEV4ZWN1dGVEZWZhdWx0cyB9IGZyb20gXCIuL2RlZmF1bHRzLnRzXCI7XG5pbXBvcnQgeyBkZWNvZGVIdG1sRW50aXRpZXMsIGh0bWxUb1RleHQgfSBmcm9tIFwiLi9odG1sLXRleHQudHNcIjtcbmltcG9ydCB7IGV4ZWN1dGVSZWFkIH0gZnJvbSBcIi4vcnVudGltZS50c1wiO1xuaW1wb3J0IHR5cGUgeyBXZWJGZXRjaFJlcXVlc3QsIFdlYkZldGNoUmVzdWx0IH0gZnJvbSBcIi4vc2NoZW1hLnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ3ZWItc2VhcmNoL2ZldGNoXCIpO1xuXG4vKiogVGhlIHNsaWNlIG9mIGEgZ290LXNjcmFwaW5nIHJlc3BvbnNlIHRoaXMgbW9kdWxlIHJlYWRzLiAqL1xuaW50ZXJmYWNlIEZldGNoZWRQYWdlIHtcbiAgdXJsOiBzdHJpbmc7XG4gIHN0YXR1c0NvZGU6IG51bWJlcjtcbiAgY29udGVudFR5cGU6IHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgYm9keTogc3RyaW5nO1xufVxuXG4vKiogUHVsbCB0aGUgPHRpdGxlPiB0ZXh0IG91dCBvZiBhbiBIVE1MIGRvY3VtZW50LCB3aGVuIHByZXNlbnQuICovXG5mdW5jdGlvbiBleHRyYWN0VGl0bGUoaHRtbDogc3RyaW5nKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgbWF0Y2ggPSAvPHRpdGxlW14+XSo+KFtcXHNcXFNdKj8pPFxcL3RpdGxlPi9pLmV4ZWMoaHRtbCk7XG4gIGNvbnN0IHRpdGxlID0gbWF0Y2g/LlsxXSA/IGRlY29kZUh0bWxFbnRpdGllcyhtYXRjaFsxXSkudHJpbSgpIDogXCJcIjtcbiAgcmV0dXJuIHRpdGxlLmxlbmd0aCA+IDAgPyB0aXRsZSA6IHVuZGVmaW5lZDtcbn1cblxuLyoqIFRydW5jYXRlIGB0ZXh0YCB0byBgbWF4YCBjaGFycywgcmVwb3J0aW5nIHdoZXRoZXIgaXQgd2FzIGN1dC4gKi9cbmZ1bmN0aW9uIHRydW5jYXRlKHRleHQ6IHN0cmluZywgbWF4OiBudW1iZXIpOiB7IGNvbnRlbnQ6IHN0cmluZzsgdHJ1bmNhdGVkOiBib29sZWFuIH0ge1xuICBpZiAodGV4dC5sZW5ndGggPD0gbWF4KSByZXR1cm4geyBjb250ZW50OiB0ZXh0LCB0cnVuY2F0ZWQ6IGZhbHNlIH07XG4gIHJldHVybiB7IGNvbnRlbnQ6IHRleHQuc2xpY2UoMCwgbWF4KSwgdHJ1bmNhdGVkOiB0cnVlIH07XG59XG5cbi8qKlxuICogRmV0Y2ggYSBzaW5nbGUgVVJMIGFuZCByZXR1cm4gaXRzIGNvbnRlbnQgaW4gdGhlIHJlcXVlc3RlZCBmb3JtYXQuXG4gKlxuICogVGhyb3dzIHdoZW4gdGhlIFVSTCBpcyBub3QgcGVybWl0dGVkIGJ5IHRoZSBhbGxvdy1saXN0ICh0aGUgdmlzaWJsZSxcbiAqIGNvcnJlY3RhYmxlIGZhaWx1cmUgdGhlIGRlc2lnbiBjYWxscyBmb3Igb24gdGhlIGZldGNoIHBhdGgpLiBUaGUgcmVxdWVzdCdzXG4gKiBgbWF4TGVuZ3RoYCBuYXJyb3dzIChuZXZlciB3aWRlbnMpIHRoZSBwbHVnaW4ncyBgZmV0Y2hNYXhMZW5ndGhgIGNhcC5cbiAqIGBzaWduYWxgIGNhbmNlbHMgdGhlIGluLWZsaWdodCBmZXRjaC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bldlYkZldGNoKFxuICByZXF1ZXN0OiBXZWJGZXRjaFJlcXVlc3QsXG4gIGNvbmZpZzogUmVzb2x2ZWRXZWJTZWFyY2hDb25maWcsXG4gIHNpZ25hbD86IEFib3J0U2lnbmFsLFxuKTogUHJvbWlzZTxXZWJGZXRjaFJlc3VsdD4ge1xuICBhd2FpdCBhc3NlcnRGZXRjaFVybEFsbG93ZWQocmVxdWVzdC51cmwsIGNvbmZpZy5hbGxvd0xpc3QpO1xuICBjb25zdCBjYXAgPSBNYXRoLm1pbihyZXF1ZXN0Lm1heExlbmd0aCA/PyBjb25maWcuZmV0Y2hNYXhMZW5ndGgsIGNvbmZpZy5mZXRjaE1heExlbmd0aCk7XG5cbiAgLy8gVGhlIHJlc3BvbnNlIGlzIGNhY2hlZCBiZWZvcmUgdGhlIGZvcm1hdCByZWR1Y3Rpb24sIHNvIHRoZSBzYW1lIHBhZ2UgcmVhZFxuICAvLyBhcyB0ZXh0IGFuZCBhcyBodG1sIHNoYXJlcyBvbmUgbmV0d29yayByb3VuZCB0cmlwLlxuICBjb25zdCBwYWdlID0gYXdhaXQgZXhlY3V0ZVJlYWQoXG4gICAgXCJwYWdlLWZldGNoXCIsXG4gICAgdG9DYWxsU2V0dGluZ3Mod2ViRmV0Y2hFeGVjdXRlRGVmYXVsdHMsIGNvbmZpZy50aW1lb3V0TXMsIFtcbiAgICAgIFwid2ViLXNlYXJjaFwiLFxuICAgICAgXCJmZXRjaFwiLFxuICAgICAgcmVxdWVzdC51cmwsXG4gICAgICAuLi5jb25maWcuYWxsb3dMaXN0LnBhdHRlcm5zLFxuICAgIF0pLFxuICAgIGFzeW5jIChleGVjdXRlU2lnbmFsKTogUHJvbWlzZTxGZXRjaGVkUGFnZT4gPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBnb3RTY3JhcGluZyh7XG4gICAgICAgIHVybDogcmVxdWVzdC51cmwsXG4gICAgICAgIC8vIGdvdCdzIG93biB0aW1lb3V0IGFib3J0cyB0aGUgc29ja2V0IGFuZCByZXBvcnRzIHdoaWNoIHBoYXNlIHRpbWVkXG4gICAgICAgIC8vIG91dDsgdGhlIGludGVyY2VwdG9yIHRpbWVvdXQgYm91bmRzIHRoZSB3aG9sZSBhdHRlbXB0IGFyb3VuZCBpdC5cbiAgICAgICAgdGltZW91dDogeyByZXF1ZXN0OiBjb25maWcudGltZW91dE1zIH0sXG4gICAgICAgIHRocm93SHR0cEVycm9yczogZmFsc2UsXG4gICAgICAgIGZvbGxvd1JlZGlyZWN0OiB0cnVlLFxuICAgICAgICBob29rczoge1xuICAgICAgICAgIGJlZm9yZVJlcXVlc3Q6IFtcbiAgICAgICAgICAgIGFzeW5jIChvcHRpb25zKSA9PiB7XG4gICAgICAgICAgICAgIGF3YWl0IGFzc2VydEZldGNoVXJsQWxsb3dlZChTdHJpbmcob3B0aW9ucy51cmwpLCBjb25maWcuYWxsb3dMaXN0KTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgXSxcbiAgICAgICAgfSxcbiAgICAgICAgLi4uKGV4ZWN1dGVTaWduYWwgPyB7IHNpZ25hbDogZXhlY3V0ZVNpZ25hbCB9IDoge30pLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICB1cmw6IHJlc3BvbnNlLnVybCA/PyByZXF1ZXN0LnVybCxcbiAgICAgICAgc3RhdHVzQ29kZTogcmVzcG9uc2Uuc3RhdHVzQ29kZSxcbiAgICAgICAgY29udGVudFR5cGU6IHJlc3BvbnNlLmhlYWRlcnNbXCJjb250ZW50LXR5cGVcIl0sXG4gICAgICAgIGJvZHk6IHR5cGVvZiByZXNwb25zZS5ib2R5ID09PSBcInN0cmluZ1wiID8gcmVzcG9uc2UuYm9keSA6IFN0cmluZyhyZXNwb25zZS5ib2R5ID8/IFwiXCIpLFxuICAgICAgfTtcbiAgICB9LFxuICAgIHNpZ25hbCxcbiAgKTtcbiAgYXdhaXQgYXNzZXJ0RmV0Y2hVcmxBbGxvd2VkKHBhZ2UudXJsLCBjb25maWcuYWxsb3dMaXN0KTtcblxuICBjb25zdCBpc0h0bWwgPSAhcGFnZS5jb250ZW50VHlwZSB8fCAvaHRtbHx4bWwvaS50ZXN0KHBhZ2UuY29udGVudFR5cGUpO1xuICBjb25zdCByYXdDb250ZW50ID0gcmVxdWVzdC5mb3JtYXQgPT09IFwiaHRtbFwiIHx8ICFpc0h0bWwgPyBwYWdlLmJvZHkgOiBodG1sVG9UZXh0KHBhZ2UuYm9keSk7XG4gIGNvbnN0IHsgY29udGVudCwgdHJ1bmNhdGVkIH0gPSB0cnVuY2F0ZShyYXdDb250ZW50LCBjYXApO1xuICBjb25zdCB0aXRsZSA9IGlzSHRtbCA/IGV4dHJhY3RUaXRsZShwYWdlLmJvZHkpIDogdW5kZWZpbmVkO1xuXG4gIGxvZ2dlci5kZWJ1ZyhcImZldGNoZWRcIiwge1xuICAgIHVybDogcGFnZS51cmwsXG4gICAgc3RhdHVzOiBwYWdlLnN0YXR1c0NvZGUsXG4gICAgYnl0ZXM6IHBhZ2UuYm9keS5sZW5ndGgsXG4gICAgcmV0dXJuZWQ6IGNvbnRlbnQubGVuZ3RoLFxuICAgIC4uLih0cnVuY2F0ZWQgPyB7IHRydW5jYXRlZDogdHJ1ZSB9IDoge30pLFxuICB9KTtcblxuICByZXR1cm4ge1xuICAgIHVybDogcGFnZS51cmwsXG4gICAgc3RhdHVzOiBwYWdlLnN0YXR1c0NvZGUsXG4gICAgLi4uKHBhZ2UuY29udGVudFR5cGUgPyB7IGNvbnRlbnRUeXBlOiBwYWdlLmNvbnRlbnRUeXBlIH0gOiB7fSksXG4gICAgLi4uKHRpdGxlID8geyB0aXRsZSB9IDoge30pLFxuICAgIGNvbnRlbnQsXG4gICAgdHJ1bmNhdGVkLFxuICB9O1xufVxuIl19
@@ -1 +1 @@
1
- {"root":["../src/allowlist.ts","../src/config.ts","../src/defaults.ts","../src/fetch.ts","../src/html-text.ts","../src/plugin.ts","../src/provider.ts","../src/runtime.ts","../src/schema.ts","../src/scrape.ts","../src/search.ts","../src/tool.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/_fetch-url.ts","../src/allowlist.ts","../src/config.ts","../src/defaults.ts","../src/fetch.ts","../src/html-text.ts","../src/plugin.ts","../src/provider.ts","../src/runtime.ts","../src/schema.ts","../src/scrape.ts","../src/search.ts","../src/tool.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -26,10 +26,10 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@databricks/appkit": "^0.43.0",
29
- "@dbx-tools/model": "0.6.71",
30
- "@dbx-tools/path": "0.6.71",
31
- "@dbx-tools/shared-core": "0.6.71",
32
- "@dbx-tools/shared-model": "0.6.71",
29
+ "@dbx-tools/model": "0.6.73",
30
+ "@dbx-tools/path": "0.6.73",
31
+ "@dbx-tools/shared-core": "0.6.73",
32
+ "@dbx-tools/shared-model": "0.6.73",
33
33
  "@mastra/core": "^1.47.0",
34
34
  "got-scraping": "^4.2.1",
35
35
  "zod": "4.3.6"
@@ -48,7 +48,7 @@
48
48
  "./package.json": "./package.json"
49
49
  }
50
50
  },
51
- "version": "0.6.71",
51
+ "version": "0.6.73",
52
52
  "types": "./lib/index.d.ts",
53
53
  "type": "module",
54
54
  "exports": {
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Network policy for page fetches.
3
+ *
4
+ * Every request, including redirects, is HTTPS, matches the configured URL
5
+ * allow-list, and resolves only to public addresses. Resolution happens in the
6
+ * request hook immediately before I/O so a redirect cannot bypass the policy.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import { lookup } from "node:dns/promises";
12
+ import { isIP } from "node:net";
13
+ import { ValidationError } from "@databricks/appkit";
14
+
15
+ import { assertUrlAllowed, type UrlAllowList } from "./allowlist.ts";
16
+
17
+ /** DNS result shape used by Node and by deterministic tests. */
18
+ export interface ResolvedAddress {
19
+ address: string;
20
+ family: number;
21
+ }
22
+
23
+ /** Injectable DNS resolver for fetch-policy tests. */
24
+ export type ResolveAddresses = (hostname: string) => Promise<readonly ResolvedAddress[]>;
25
+
26
+ /** Resolve all addresses for a hostname without changing their family. */
27
+ async function resolveAddresses(hostname: string): Promise<readonly ResolvedAddress[]> {
28
+ return lookup(hostname, { all: true, verbatim: true });
29
+ }
30
+
31
+ /** Whether an IPv4 address is private, local, reserved, or non-unicast. */
32
+ function isBlockedIpv4(address: string): boolean {
33
+ const [a = -1, b = -1, c = -1] = address.split(".").map(Number);
34
+ return (
35
+ a === 0 ||
36
+ a === 10 ||
37
+ a === 127 ||
38
+ a >= 224 ||
39
+ (a === 100 && b >= 64 && b <= 127) ||
40
+ (a === 169 && b === 254) ||
41
+ (a === 172 && b >= 16 && b <= 31) ||
42
+ (a === 192 && b === 0 && c === 0) ||
43
+ (a === 192 && b === 0 && c === 2) ||
44
+ (a === 192 && b === 168) ||
45
+ (a === 198 && (b === 18 || b === 19)) ||
46
+ (a === 198 && b === 51 && c === 100) ||
47
+ (a === 203 && b === 0 && c === 113)
48
+ );
49
+ }
50
+
51
+ /** Whether an IPv6 address is private, local, reserved, or non-unicast. */
52
+ function isBlockedIpv6(address: string): boolean {
53
+ const normalized = address.toLowerCase();
54
+ return (
55
+ normalized === "::" ||
56
+ normalized === "::1" ||
57
+ normalized.startsWith("::ffff:") ||
58
+ normalized.startsWith("100:") ||
59
+ normalized.startsWith("2001:db8:") ||
60
+ normalized.startsWith("fc") ||
61
+ normalized.startsWith("fd") ||
62
+ /^fe[89ab]/.test(normalized) ||
63
+ normalized.startsWith("ff")
64
+ );
65
+ }
66
+
67
+ /** Whether an address is globally routable enough for an outbound page fetch. */
68
+ function isPublicAddress(address: ResolvedAddress): boolean {
69
+ return address.family === 6 ? !isBlockedIpv6(address.address) : !isBlockedIpv4(address.address);
70
+ }
71
+
72
+ /**
73
+ * Validate one concrete page-fetch target immediately before network I/O.
74
+ *
75
+ * Rejects embedded credentials, non-HTTPS URLs, disallowed hosts/paths, and
76
+ * hostnames whose DNS answers include any private, loopback, link-local, or
77
+ * reserved address.
78
+ */
79
+ export async function assertFetchUrlAllowed(
80
+ value: string,
81
+ allowList: UrlAllowList,
82
+ resolve: ResolveAddresses = resolveAddresses,
83
+ ): Promise<void> {
84
+ let url: URL;
85
+ try {
86
+ url = new URL(value);
87
+ } catch {
88
+ throw ValidationError.invalidValue("url", value, "an absolute HTTPS URL");
89
+ }
90
+ if (url.protocol !== "https:" || url.username || url.password) {
91
+ throw ValidationError.invalidValue(
92
+ "url",
93
+ value,
94
+ "an absolute HTTPS URL without embedded credentials",
95
+ );
96
+ }
97
+ assertUrlAllowed(url.href, allowList);
98
+
99
+ const literalFamily = isIP(url.hostname);
100
+ const addresses = literalFamily
101
+ ? [{ address: url.hostname, family: literalFamily }]
102
+ : await resolve(url.hostname);
103
+ if (addresses.length === 0 || addresses.some((address) => !isPublicAddress(address))) {
104
+ throw ValidationError.invalidValue(
105
+ "url",
106
+ value,
107
+ "a URL that resolves only to public addresses",
108
+ );
109
+ }
110
+ }
package/src/fetch.ts CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { log } from "@dbx-tools/shared-core";
19
19
  import { gotScraping } from "got-scraping";
20
- import { assertUrlAllowed } from "./allowlist.ts";
20
+ import { assertFetchUrlAllowed } from "./_fetch-url.ts";
21
21
  import type { ResolvedWebSearchConfig } from "./config.ts";
22
22
  import { toCallSettings, webFetchExecuteDefaults } from "./defaults.ts";
23
23
  import { decodeHtmlEntities, htmlToText } from "./html-text.ts";
@@ -60,14 +60,19 @@ export async function runWebFetch(
60
60
  config: ResolvedWebSearchConfig,
61
61
  signal?: AbortSignal,
62
62
  ): Promise<WebFetchResult> {
63
- assertUrlAllowed(request.url, config.allowList);
63
+ await assertFetchUrlAllowed(request.url, config.allowList);
64
64
  const cap = Math.min(request.maxLength ?? config.fetchMaxLength, config.fetchMaxLength);
65
65
 
66
66
  // The response is cached before the format reduction, so the same page read
67
67
  // as text and as html shares one network round trip.
68
68
  const page = await executeRead(
69
69
  "page-fetch",
70
- toCallSettings(webFetchExecuteDefaults, config.timeoutMs, ["web-search", "fetch", request.url]),
70
+ toCallSettings(webFetchExecuteDefaults, config.timeoutMs, [
71
+ "web-search",
72
+ "fetch",
73
+ request.url,
74
+ ...config.allowList.patterns,
75
+ ]),
71
76
  async (executeSignal): Promise<FetchedPage> => {
72
77
  const response = await gotScraping({
73
78
  url: request.url,
@@ -76,6 +81,13 @@ export async function runWebFetch(
76
81
  timeout: { request: config.timeoutMs },
77
82
  throwHttpErrors: false,
78
83
  followRedirect: true,
84
+ hooks: {
85
+ beforeRequest: [
86
+ async (options) => {
87
+ await assertFetchUrlAllowed(String(options.url), config.allowList);
88
+ },
89
+ ],
90
+ },
79
91
  ...(executeSignal ? { signal: executeSignal } : {}),
80
92
  });
81
93
  return {
@@ -87,6 +99,7 @@ export async function runWebFetch(
87
99
  },
88
100
  signal,
89
101
  );
102
+ await assertFetchUrlAllowed(page.url, config.allowList);
90
103
 
91
104
  const isHtml = !page.contentType || /html|xml/i.test(page.contentType);
92
105
  const rawContent = request.format === "html" || !isHtml ? page.body : htmlToText(page.body);