@scalar/json-magic 0.13.4 → 0.14.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/CHANGELOG.md +23 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.d.ts.map +1 -1
- package/dist/bundle/bundle.js +16 -9
- package/dist/bundle/index.d.ts +1 -0
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js +1 -0
- package/dist/bundle/plugins/fetch-urls/fetch-public-url.d.ts +3 -0
- package/dist/bundle/plugins/fetch-urls/fetch-public-url.d.ts.map +1 -0
- package/dist/bundle/plugins/fetch-urls/fetch-public-url.js +42 -0
- package/dist/bundle/plugins/fetch-urls/index.d.ts +1 -0
- package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -1
- package/dist/bundle/plugins/fetch-urls/index.js +12 -35
- package/dist/bundle/plugins/fetch-urls/is-blocked-host.d.ts +3 -0
- package/dist/bundle/plugins/fetch-urls/is-blocked-host.d.ts.map +1 -1
- package/dist/bundle/plugins/fetch-urls/is-blocked-host.js +8 -8
- package/dist/diff/utils.d.ts.map +1 -1
- package/dist/diff/utils.js +2 -1
- package/package.json +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @scalar/json-magic
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10241](https://github.com/scalar/scalar/pull/10241): Expose helpers for indexing locally embedded `$id` and `$anchor` resources and resolving a reference to its local path.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#10257](https://github.com/scalar/scalar/pull/10257): Resolve a relative reference reached through a local pointer during a partial bundle against the document origin; it was resolved against an empty base and failed.
|
|
12
|
+
|
|
13
|
+
## 0.13.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#9817](https://github.com/scalar/scalar/pull/9817): Resolve relative `externalValue` URLs on example objects against the document origin, so external request and response examples load even when referenced with a relative path
|
|
18
|
+
- [#9666](https://github.com/scalar/scalar/pull/9666): Only normalize OpenAPI Reference Objects during bundling, never Schema Objects. `normalizeRefs` used to strip every sibling except `$ref` on any node outside `components/schemas`, which also hit inline schemas. In JSON Schema 2020-12 a `$ref` may legally carry sibling keywords — for example a `$defs`/`$dynamicAnchor` binding that specializes a generic template like `Paginated<T>` — and such schemas appear inline anywhere a schema is allowed (a response's `content.<media>.schema`, an `allOf` branch, …). Dropping those siblings discarded the binding, leaving `$dynamicRef` to resolve to the template's empty fallback and rendering an empty array (for example the `data` array of `GET /planets` in the Scalar Galaxy). Reference Objects are still normalized as before. A new `@scalar/helpers/openapi/is-schema-path` helper detects schema positions.
|
|
19
|
+
- [#10112](https://github.com/scalar/scalar/pull/10112): Pin guarded requests to validated public IP addresses and reject custom transports that bypass the connection checks.
|
|
20
|
+
- [#10140](https://github.com/scalar/scalar/pull/10140): Replace redundant type assertions with compiler-checked annotations, typed accumulators, and existing guards across helpers, API conversion, request handling, and schema rendering.
|
|
21
|
+
|
|
22
|
+
Narrow DOM elements and caught errors before accessing their properties. Correct header lookup to include missing values and handle them during PowerShell snippet generation.
|
|
23
|
+
|
|
24
|
+
Validate release-note provider responses, represent unresolved references and absent groups in helper return types, and require narrowing merged object values. Preserve AsyncAPI broker credentials separately from HTTP authentication schemes.
|
|
25
|
+
|
|
3
26
|
## 0.13.4
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/bundle/bundle.d.ts
CHANGED
|
@@ -124,13 +124,17 @@ export type LoaderPlugin = {
|
|
|
124
124
|
*
|
|
125
125
|
* - `path`: The JSON pointer path (as an array of strings) from the document root to the current node.
|
|
126
126
|
* - `resolutionCache`: A cache for storing promises of resolved references.
|
|
127
|
+
* - `origin`: The origin (URL or file path) of the document this node lives in. Lifecycle plugins can
|
|
128
|
+
* use it to resolve relative references (for example an example's `externalValue`) into absolute URLs.
|
|
127
129
|
*/
|
|
128
130
|
type NodeProcessContext = {
|
|
129
131
|
path: readonly string[];
|
|
132
|
+
referencedFromPath?: readonly string[];
|
|
130
133
|
resolutionCache: Map<string, Promise<Readonly<ResolveResult>>>;
|
|
131
134
|
parentNode: UnknownObject | null;
|
|
132
135
|
rootNode: UnknownObject;
|
|
133
136
|
loaders: LoaderPlugin[];
|
|
137
|
+
origin: string;
|
|
134
138
|
};
|
|
135
139
|
/**
|
|
136
140
|
* A plugin type for lifecycle hooks, allowing custom logic to be injected into the bundler's process.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/bundle/bundle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAU5C;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAA;AA2BpF;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAMhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAqB1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,wBAAwB,GACnC,gBAAgB,OAAO,EACvB,gBAAgB,OAAO,EACvB,eAAe,MAAM,EACrB,iBAAiB,MAAM,EACvB,aAAa,MAAM,EACnB,yBAAuB,EACvB,6BAA0B,SAqD3B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,QAAQ,CAAA;IAEd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IAEpC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAA;CAChD,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/bundle/bundle.ts"],"names":[],"mappings":"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"}
|
package/dist/bundle/bundle.js
CHANGED
|
@@ -349,7 +349,7 @@ export async function bundle(input, config) {
|
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
351
|
};
|
|
352
|
-
const bundler = async (root, origin = defaultOrigin, isChunkParent = false, depth = 0, currentPath = [], parent = null) => {
|
|
352
|
+
const bundler = async (root, origin = defaultOrigin, isChunkParent = false, depth = 0, currentPath = [], parent = null, referencedFromPath) => {
|
|
353
353
|
// If a maximum depth is set in the config, stop bundling when the current depth reaches or exceeds it
|
|
354
354
|
if (config.depth !== undefined && depth > config.depth) {
|
|
355
355
|
return;
|
|
@@ -364,15 +364,19 @@ export async function bundle(input, config) {
|
|
|
364
364
|
}
|
|
365
365
|
// Mark this node as processed before continuing
|
|
366
366
|
processedNodes.add(root);
|
|
367
|
+
// A node with its own `$id` establishes a new base for resolving relative references within it.
|
|
368
|
+
// Fall back to the origin inherited from the parent document otherwise.
|
|
369
|
+
const id = getId(root);
|
|
367
370
|
const context = {
|
|
368
371
|
path: currentPath,
|
|
372
|
+
referencedFromPath,
|
|
369
373
|
resolutionCache: cache,
|
|
370
374
|
parentNode: parent,
|
|
371
375
|
rootNode: documentRoot,
|
|
372
376
|
loaders: loaderPlugins,
|
|
377
|
+
origin: id ?? origin,
|
|
373
378
|
};
|
|
374
379
|
await executeHooks('onBeforeNodeProcess', root, context);
|
|
375
|
-
const id = getId(root);
|
|
376
380
|
if (hasRef(root)) {
|
|
377
381
|
const ref = root['$ref'];
|
|
378
382
|
const isChunk = '$global' in root && typeof root['$global'] === 'boolean' && root['$global'];
|
|
@@ -393,7 +397,12 @@ export async function bundle(input, config) {
|
|
|
393
397
|
// referenced by this local reference to ensure the partial bundle is complete.
|
|
394
398
|
// This includes not just the direct reference but also all its dependencies,
|
|
395
399
|
// creating a complete and self-contained partial bundle.
|
|
396
|
-
|
|
400
|
+
//
|
|
401
|
+
// The pointer addresses the root document, so the target's base is the nearest `$id` on
|
|
402
|
+
// the way to it, else the document origin. `getValueByPath` reports no `$id` as an empty
|
|
403
|
+
// string, and handing that on as the origin left a relative reference under the target
|
|
404
|
+
// (a chunk reference written relative to the document) with nothing to resolve against.
|
|
405
|
+
await bundler(targetValue.value, targetValue.context || defaultOrigin, isChunkParent, depth + 1, segments, parent, referencedFromPath);
|
|
397
406
|
}
|
|
398
407
|
await executeHooks('onAfterNodeProcess', root, context);
|
|
399
408
|
return;
|
|
@@ -436,11 +445,9 @@ export async function bundle(input, config) {
|
|
|
436
445
|
// to handle any nested references it may contain. We pass the resolvedPath as the new origin
|
|
437
446
|
// to ensure any relative references within this content are resolved correctly relative to
|
|
438
447
|
// their new location in the bundled document.
|
|
439
|
-
await bundler(result.data, isChunk ? origin : resolvedPath, isChunk, depth + 1, [
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
documentRoot[config.externalDocumentsMappingsKey],
|
|
443
|
-
]);
|
|
448
|
+
await bundler(result.data, isChunk ? origin : resolvedPath, isChunk, depth + 1, [config.externalDocumentsKey, compressedPath], null,
|
|
449
|
+
// Keep the referring document path across external hops, replacing the storage wrapper.
|
|
450
|
+
referencedFromPath ? [...referencedFromPath, ...currentPath.slice(2)] : currentPath);
|
|
444
451
|
// Store the mapping between hashed keys and original URLs in x-ext-urls
|
|
445
452
|
// This allows tracking which external URLs were bundled and their corresponding locations
|
|
446
453
|
setValueAtPath(documentRoot, `/${config.externalDocumentsMappingsKey}/${escapeJsonPointer(compressedPath)}`, relativePath);
|
|
@@ -478,7 +485,7 @@ export async function bundle(input, config) {
|
|
|
478
485
|
if (key === config.externalDocumentsKey || key === config.externalDocumentsMappingsKey) {
|
|
479
486
|
continue;
|
|
480
487
|
}
|
|
481
|
-
await bundler(root[key], id ?? origin, isChunkParent, depth + 1, [...currentPath, key], root);
|
|
488
|
+
await bundler(root[key], id ?? origin, isChunkParent, depth + 1, [...currentPath, key], root, referencedFromPath);
|
|
482
489
|
}
|
|
483
490
|
await executeHooks('onAfterNodeProcess', root, context);
|
|
484
491
|
};
|
package/dist/bundle/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { resolveReferencePath } from '../helpers/resolve-reference-path.js';
|
|
1
2
|
export type { LifecyclePlugin, LoaderPlugin, Plugin, ResolveResult } from './bundle.js';
|
|
2
3
|
export { bundle, extensions, isLocalRef, prefixInternalRef, prefixInternalRefRecursive, resolveAndCopyReferences, } from './bundle.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bundle/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACpF,OAAO,EACL,MAAM,EACN,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bundle/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;AAEvE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACpF,OAAO,EACL,MAAM,EACN,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,UAAU,CAAA"}
|
package/dist/bundle/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-public-url.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/fetch-public-url.ts"],"names":[],"mappings":"AAIA,yGAAyG;AACzG,eAAO,MAAM,cAAc,GAAU,KAAK,MAAM,EAAE,UAAU,WAAW,KAAG,OAAO,CAAC,QAAQ,CAwCzF,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Agent, fetch } from 'undici';
|
|
2
|
+
import { resolvePublicHost } from './is-blocked-host.js';
|
|
3
|
+
/** Fetches through a connection pinned to validated addresses while preserving the host and TLS name. */
|
|
4
|
+
export const fetchPublicUrl = async (url, headers) => {
|
|
5
|
+
const target = new URL(url);
|
|
6
|
+
if (target.protocol !== 'http:' && target.protocol !== 'https:') {
|
|
7
|
+
throw new Error('Only HTTP and HTTPS URLs are supported');
|
|
8
|
+
}
|
|
9
|
+
const addresses = await resolvePublicHost(target.hostname);
|
|
10
|
+
if (!addresses) {
|
|
11
|
+
throw new Error('Refused to fetch a private or internal address');
|
|
12
|
+
}
|
|
13
|
+
// Never resolve the hostname again when connecting. A later DNS answer could point at a private IP.
|
|
14
|
+
const dispatcher = new Agent({
|
|
15
|
+
connect: {
|
|
16
|
+
lookup: (_hostname, options, callback) => {
|
|
17
|
+
if (options.all) {
|
|
18
|
+
callback(null, addresses);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
callback(null, addresses[0].address, addresses[0].family);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(target, {
|
|
28
|
+
headers: Object.fromEntries(new Headers(headers)),
|
|
29
|
+
redirect: 'error',
|
|
30
|
+
dispatcher,
|
|
31
|
+
});
|
|
32
|
+
const body = !response.ok || [204, 205].includes(response.status) ? null : await response.arrayBuffer();
|
|
33
|
+
return new Response(body, {
|
|
34
|
+
status: response.status,
|
|
35
|
+
statusText: response.statusText,
|
|
36
|
+
headers: Object.fromEntries(response.headers),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await dispatcher.destroy();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -4,6 +4,7 @@ type FetchConfig = Partial<{
|
|
|
4
4
|
headers: HeadersInit;
|
|
5
5
|
domains: string[];
|
|
6
6
|
}[];
|
|
7
|
+
/** Custom transports cannot be combined with blockPrivateNetworks. */
|
|
7
8
|
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
8
9
|
/**
|
|
9
10
|
* When true, refuse to fetch URLs that resolve to a private, loopback, link-local, or otherwise
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAI3D,KAAK,WAAW,GAAG,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;IACtD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1F;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAI3D,KAAK,WAAW,GAAG,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;IACtD,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1F;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAC,CAAA;AAcF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAChD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CA8CxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAAG,YAAY,CAShG"}
|
|
@@ -13,17 +13,6 @@ const getHost = (url) => {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
/**
|
|
17
|
-
* Safely reads the hostname (without port) from a URL, for the private-address check.
|
|
18
|
-
*/
|
|
19
|
-
const getHostname = (url) => {
|
|
20
|
-
try {
|
|
21
|
-
return new URL(url).hostname;
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
16
|
/**
|
|
28
17
|
* Fetches and normalizes data from a remote URL
|
|
29
18
|
* @param url - The URL to fetch data from
|
|
@@ -40,32 +29,20 @@ const getHostname = (url) => {
|
|
|
40
29
|
*/
|
|
41
30
|
export async function fetchUrl(url, limiter, config) {
|
|
42
31
|
try {
|
|
43
|
-
// SSRF guard: optionally refuse to fetch internal or private targets before making the request.
|
|
44
|
-
// Only runs in Node, since the browser build has no DNS access and its own network isolation.
|
|
45
|
-
if (config?.blockPrivateNetworks && typeof window === 'undefined') {
|
|
46
|
-
const hostname = getHostname(url);
|
|
47
|
-
if (hostname) {
|
|
48
|
-
const { isBlockedHost } = await import('./is-blocked-host.js');
|
|
49
|
-
if (await isBlockedHost(hostname)) {
|
|
50
|
-
console.warn(`[WARN] Refused to fetch a private or internal address: ${url}`);
|
|
51
|
-
return {
|
|
52
|
-
ok: false,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
32
|
const host = getHost(url);
|
|
58
|
-
// Get the headers that match the domain
|
|
59
33
|
const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== undefined)?.headers;
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
34
|
+
const guarded = config?.blockPrivateNetworks && typeof window === 'undefined';
|
|
35
|
+
const result = await limiter(async () => {
|
|
36
|
+
if (guarded) {
|
|
37
|
+
// A custom fetch can ignore the pinned connection and resolve the host again.
|
|
38
|
+
if (config?.fetch) {
|
|
39
|
+
throw new Error('Custom fetch cannot be combined with private network blocking');
|
|
40
|
+
}
|
|
41
|
+
const { fetchPublicUrl } = await import('./fetch-public-url.js');
|
|
42
|
+
return fetchPublicUrl(url, headers);
|
|
43
|
+
}
|
|
44
|
+
return (config?.fetch ?? fetch)(url, { headers });
|
|
45
|
+
});
|
|
69
46
|
if (result.ok) {
|
|
70
47
|
const body = await result.text();
|
|
71
48
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LookupAddress } from 'node:dns';
|
|
1
2
|
/**
|
|
2
3
|
* Reports whether a hostname resolves to a private, loopback, link-local, or otherwise internal
|
|
3
4
|
* address that a bundled `$ref` should not be allowed to reach.
|
|
@@ -12,4 +13,6 @@
|
|
|
12
13
|
* @param hostname - The hostname or IP (brackets around an IPv6 literal are tolerated).
|
|
13
14
|
*/
|
|
14
15
|
export declare const isBlockedHost: (hostname: string) => Promise<boolean>;
|
|
16
|
+
/** Resolves a host once and returns only validated public addresses, or null when blocked. */
|
|
17
|
+
export declare const resolvePublicHost: (hostname: string) => Promise<LookupAddress[] | null>;
|
|
15
18
|
//# sourceMappingURL=is-blocked-host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-blocked-host.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/is-blocked-host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"is-blocked-host.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/is-blocked-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAqE7C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CAAiD,CAAA;AAEvH,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,CAWxF,CAAA"}
|
|
@@ -22,6 +22,7 @@ blockList.addSubnet('fe80::', 10, 'ipv6'); // link-local
|
|
|
22
22
|
blockList.addSubnet('fc00::', 7, 'ipv6'); // unique local
|
|
23
23
|
blockList.addSubnet('2002::', 16, 'ipv6'); // 6to4
|
|
24
24
|
blockList.addSubnet('64:ff9b::', 96, 'ipv6'); // NAT64
|
|
25
|
+
blockList.addSubnet('64:ff9b:1::', 48, 'ipv6'); // local NAT64 translation prefixes
|
|
25
26
|
blockList.addSubnet('2001::', 32, 'ipv6'); // Teredo
|
|
26
27
|
/**
|
|
27
28
|
* Checks whether a single IP address falls inside a blocked range.
|
|
@@ -65,17 +66,16 @@ const ipIsBlocked = (ip) => {
|
|
|
65
66
|
*
|
|
66
67
|
* @param hostname - The hostname or IP (brackets around an IPv6 literal are tolerated).
|
|
67
68
|
*/
|
|
68
|
-
export const isBlockedHost = async (hostname) =>
|
|
69
|
+
export const isBlockedHost = async (hostname) => (await resolvePublicHost(hostname)) === null;
|
|
70
|
+
/** Resolves a host once and returns only validated public addresses, or null when blocked. */
|
|
71
|
+
export const resolvePublicHost = async (hostname) => {
|
|
69
72
|
const host = hostname.replace(/^\[/, '').replace(/\]$/, '');
|
|
70
|
-
if (isIP(host)) {
|
|
71
|
-
return ipIsBlocked(host);
|
|
72
|
-
}
|
|
73
73
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
74
|
+
const family = isIP(host);
|
|
75
|
+
const addresses = family ? [{ address: host, family }] : await lookup(host, { all: true });
|
|
76
|
+
return addresses.length === 0 || addresses.some(({ address }) => ipIsBlocked(address)) ? null : addresses;
|
|
76
77
|
}
|
|
77
78
|
catch {
|
|
78
|
-
|
|
79
|
-
return true;
|
|
79
|
+
return null;
|
|
80
80
|
}
|
|
81
81
|
};
|
package/dist/diff/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/diff/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/diff/utils.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,eAAe,GAAI,GAAG,OAAO,EAAE,GAAG,OAAO,KAAG,OAoCxD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAsB3G,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,YAY7C,CAAA"}
|
package/dist/diff/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isObjectLike } from '@scalar/helpers/object/is-object';
|
|
1
2
|
import { isPollutionKey } from '@scalar/helpers/object/prevent-pollution';
|
|
2
3
|
/**
|
|
3
4
|
* Deep check for objects for collisions
|
|
@@ -96,7 +97,7 @@ export const mergeObjects = (a, b) => {
|
|
|
96
97
|
else {
|
|
97
98
|
const aValue = a[key];
|
|
98
99
|
const bValue = b[key];
|
|
99
|
-
if (
|
|
100
|
+
if (isObjectLike(aValue) && isObjectLike(bValue)) {
|
|
100
101
|
a[key] = mergeObjects(aValue, bValue);
|
|
101
102
|
}
|
|
102
103
|
}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"url": "git+https://github.com/scalar/scalar.git",
|
|
11
11
|
"directory": "packages/json-magic"
|
|
12
12
|
},
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.14.0",
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=22"
|
|
16
16
|
},
|
|
@@ -51,6 +51,16 @@
|
|
|
51
51
|
"types": "./dist/helpers/escape-json-pointer.d.ts",
|
|
52
52
|
"default": "./dist/helpers/escape-json-pointer.js"
|
|
53
53
|
},
|
|
54
|
+
"./helpers/convert-to-local-ref": {
|
|
55
|
+
"import": "./dist/helpers/convert-to-local-ref.js",
|
|
56
|
+
"types": "./dist/helpers/convert-to-local-ref.d.ts",
|
|
57
|
+
"default": "./dist/helpers/convert-to-local-ref.js"
|
|
58
|
+
},
|
|
59
|
+
"./helpers/get-schemas": {
|
|
60
|
+
"import": "./dist/helpers/get-schemas.js",
|
|
61
|
+
"types": "./dist/helpers/get-schemas.d.ts",
|
|
62
|
+
"default": "./dist/helpers/get-schemas.js"
|
|
63
|
+
},
|
|
54
64
|
"./helpers/get-segments-from-path": {
|
|
55
65
|
"import": "./dist/helpers/get-segments-from-path.js",
|
|
56
66
|
"types": "./dist/helpers/get-segments-from-path.d.ts",
|
|
@@ -107,9 +117,10 @@
|
|
|
107
117
|
"CHANGELOG.md"
|
|
108
118
|
],
|
|
109
119
|
"dependencies": {
|
|
120
|
+
"@scalar/helpers": "0.12.0",
|
|
110
121
|
"pathe": "^2.0.3",
|
|
111
|
-
"
|
|
112
|
-
"
|
|
122
|
+
"undici": "7.24.4",
|
|
123
|
+
"yaml": "^2.9.0"
|
|
113
124
|
},
|
|
114
125
|
"devDependencies": {
|
|
115
126
|
"fastify": "^5.11.2",
|