webveil 0.0.0 → 0.1.1
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/LICENSE +661 -0
- package/README.md +326 -0
- package/dist/cli.d.ts +58 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/backends/custom.d.ts +15 -0
- package/dist/core/backends/custom.d.ts.map +1 -0
- package/dist/core/backends/custom.js +106 -0
- package/dist/core/backends/custom.js.map +1 -0
- package/dist/core/backends/registry.d.ts +13 -0
- package/dist/core/backends/registry.d.ts.map +1 -0
- package/dist/core/backends/registry.js +31 -0
- package/dist/core/backends/registry.js.map +1 -0
- package/dist/core/backends/searxng.d.ts +8 -0
- package/dist/core/backends/searxng.d.ts.map +1 -0
- package/dist/core/backends/searxng.js +43 -0
- package/dist/core/backends/searxng.js.map +1 -0
- package/dist/core/backends/tavily-compat.d.ts +10 -0
- package/dist/core/backends/tavily-compat.d.ts.map +1 -0
- package/dist/core/backends/tavily-compat.js +85 -0
- package/dist/core/backends/tavily-compat.js.map +1 -0
- package/dist/core/backends/types.d.ts +48 -0
- package/dist/core/backends/types.d.ts.map +1 -0
- package/dist/core/backends/types.js +5 -0
- package/dist/core/backends/types.js.map +1 -0
- package/dist/core/baseurl.d.ts +42 -0
- package/dist/core/baseurl.d.ts.map +1 -0
- package/dist/core/baseurl.js +79 -0
- package/dist/core/baseurl.js.map +1 -0
- package/dist/core/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +72 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/egress.d.ts +46 -0
- package/dist/core/egress.d.ts.map +1 -0
- package/dist/core/egress.js +113 -0
- package/dist/core/egress.js.map +1 -0
- package/dist/core/extract.d.ts +45 -0
- package/dist/core/extract.d.ts.map +1 -0
- package/dist/core/extract.js +36 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/fetch.d.ts +42 -0
- package/dist/core/fetch.d.ts.map +1 -0
- package/dist/core/fetch.js +76 -0
- package/dist/core/fetch.js.map +1 -0
- package/dist/core/http.d.ts +8 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +49 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/search.d.ts +34 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +92 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/security.d.ts +35 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +141 -0
- package/dist/core/security.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -2
- package/src/cli.ts +106 -0
- package/src/core/backends/custom.ts +159 -0
- package/src/core/backends/registry.ts +41 -0
- package/src/core/backends/searxng.ts +70 -0
- package/src/core/backends/tavily-compat.ts +156 -0
- package/src/core/backends/types.ts +61 -0
- package/src/core/baseurl.ts +104 -0
- package/src/core/config.ts +106 -0
- package/src/core/egress.ts +134 -0
- package/src/core/extract.ts +82 -0
- package/src/core/fetch.ts +132 -0
- package/src/core/http.ts +62 -0
- package/src/core/search.ts +140 -0
- package/src/core/security.ts +141 -0
- package/src/index.ts +82 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// egress seam — how outbound HTTP leaves the machine. Yields TWO artifacts off
|
|
2
|
+
// the SAME undici dispatcher: the proxied `http` helper (see http.ts, handed to
|
|
3
|
+
// backends) and an egress-bound WHATWG `fetch` (injected into distilly/fetch).
|
|
4
|
+
//
|
|
5
|
+
// CRITICAL anonymity invariant (docs/adr/0001): egress is fail-loud. A
|
|
6
|
+
// configured proxy that cannot be built MUST throw — it must NEVER silently
|
|
7
|
+
// fall back to un-proxied (direct) transport.
|
|
8
|
+
import { Agent, ProxyAgent, fetch as undiciFetch } from 'undici';
|
|
9
|
+
import { socksDispatcher } from 'fetch-socks';
|
|
10
|
+
import { isUnixBaseUrl } from './baseurl.js';
|
|
11
|
+
/** Thrown when a configured egress proxy cannot be built. Never swallowed. */
|
|
12
|
+
export class EgressError extends Error {
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = 'EgressError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fail-loud guard for the false-confidence combo: a `unix:` (local-socket)
|
|
20
|
+
* backend `baseUrl` configured with a NON-direct egress (`http`/`socks5`). A
|
|
21
|
+
* Unix socket is inherently local, so proxying that hop is the same fake-
|
|
22
|
+
* anonymity footgun as proxying a loopback TCP baseUrl: webveil would route a
|
|
23
|
+
* pointless local call through the proxy while the backend (SearXNG) crawls the
|
|
24
|
+
* public web from the real IP, OUTSIDE webveil's egress. Refuse it and point at
|
|
25
|
+
* the real fix.
|
|
26
|
+
*
|
|
27
|
+
* OVERLAP SEAM (recorded): this is the loopback false-confidence family. The
|
|
28
|
+
* sibling task `fail-loud-on-proxied-loopback-backend` adds the broader guard
|
|
29
|
+
* for loopback TCP baseUrls (127.0.0.0/8, ::1, localhost). When it lands, fold
|
|
30
|
+
* THIS `unix:`-is-loopback-equivalent case into that single guard instead of
|
|
31
|
+
* keeping a parallel check here.
|
|
32
|
+
*/
|
|
33
|
+
export function assertEgressAllowsBaseUrl(cfg) {
|
|
34
|
+
if (cfg.egress.mode === 'direct')
|
|
35
|
+
return;
|
|
36
|
+
if (isUnixBaseUrl(cfg.baseUrl))
|
|
37
|
+
throw new EgressError(`egress ${cfg.egress.mode}: a unix: (local socket) baseUrl cannot be ` +
|
|
38
|
+
`proxied — it is inherently local, so proxying it gives fake ` +
|
|
39
|
+
`anonymity (SearXNG still crawls the web from your real IP). Set ` +
|
|
40
|
+
`egress=direct and proxy the backend itself (SearXNG's ` +
|
|
41
|
+
`outgoing.proxies), or use a remote backend.`);
|
|
42
|
+
}
|
|
43
|
+
function socksFromUrl(raw) {
|
|
44
|
+
const url = new URL(raw); // throws on a malformed proxy URL → fail loud
|
|
45
|
+
const protocol = url.protocol.replace(':', '');
|
|
46
|
+
if (protocol !== 'socks5' && protocol !== 'socks' && protocol !== 'socks5h')
|
|
47
|
+
throw new EgressError(`egress socks5: expected a socks5:// proxy url, got ${raw}`);
|
|
48
|
+
const port = Number(url.port);
|
|
49
|
+
if (!url.hostname || !Number.isInteger(port) || port <= 0)
|
|
50
|
+
throw new EgressError(`egress socks5: invalid host/port in ${raw}`);
|
|
51
|
+
return socksDispatcher({
|
|
52
|
+
type: 5,
|
|
53
|
+
host: url.hostname,
|
|
54
|
+
port,
|
|
55
|
+
userId: url.username || undefined,
|
|
56
|
+
password: url.password || undefined,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build the undici Dispatcher for the config's egress mode:
|
|
61
|
+
* - direct → undefined (undici uses its default, un-proxied transport)
|
|
62
|
+
* - http → ProxyAgent
|
|
63
|
+
* - socks5 → socks dispatcher (undici Agent over a socks connector)
|
|
64
|
+
*
|
|
65
|
+
* Throws (fail loud) if a configured http/socks5 proxy cannot be built. It
|
|
66
|
+
* NEVER returns `undefined` (direct) as a fallback for a broken proxy.
|
|
67
|
+
*/
|
|
68
|
+
export function buildDispatcher(cfg) {
|
|
69
|
+
const egress = cfg.egress;
|
|
70
|
+
switch (egress.mode) {
|
|
71
|
+
case 'direct':
|
|
72
|
+
return undefined;
|
|
73
|
+
case 'http':
|
|
74
|
+
try {
|
|
75
|
+
if (!egress.url)
|
|
76
|
+
throw new Error('missing proxy url');
|
|
77
|
+
return new ProxyAgent(egress.url);
|
|
78
|
+
}
|
|
79
|
+
catch (cause) {
|
|
80
|
+
throw new EgressError(`egress http: could not build proxy for ${egress.url}`, { cause });
|
|
81
|
+
}
|
|
82
|
+
case 'socks5':
|
|
83
|
+
try {
|
|
84
|
+
if (!egress.url)
|
|
85
|
+
throw new Error('missing proxy url');
|
|
86
|
+
return socksFromUrl(egress.url);
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
if (cause instanceof EgressError)
|
|
90
|
+
throw cause;
|
|
91
|
+
throw new EgressError(`egress socks5: could not build proxy for ${egress.url}`, { cause });
|
|
92
|
+
}
|
|
93
|
+
default: {
|
|
94
|
+
const exhaustive = egress;
|
|
95
|
+
throw new EgressError(`egress: unknown mode ${JSON.stringify(exhaustive)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build an egress-bound WHATWG `fetch`: undici's `fetch` closed over the
|
|
101
|
+
* dispatcher from buildDispatcher(cfg). This is the `fetch` injected into
|
|
102
|
+
* distilly/fetch so distilly never has egress of its own. Same fail-loud
|
|
103
|
+
* guarantee: a broken proxy throws HERE (before any I/O), never goes un-proxied.
|
|
104
|
+
*/
|
|
105
|
+
export function createEgressFetch(cfg) {
|
|
106
|
+
const dispatcher = buildDispatcher(cfg);
|
|
107
|
+
return ((input, init) => undiciFetch(input, {
|
|
108
|
+
...(init ?? {}),
|
|
109
|
+
dispatcher,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
export { Agent, ProxyAgent };
|
|
113
|
+
//# sourceMappingURL=egress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egress.js","sourceRoot":"","sources":["../../src/core/egress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Config, FetchSize } from './config.js';
|
|
2
|
+
import { type EgressFetch } from './egress.js';
|
|
3
|
+
import type { FetchResult } from './backends/types.js';
|
|
4
|
+
/** The shape distilly's `urlToMarkdown` returns (the bits we surface). */
|
|
5
|
+
interface UrlToMarkdownResult {
|
|
6
|
+
markdown: string;
|
|
7
|
+
truncated: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** distilly's networked entrypoint, narrowed to what the seam injects/uses. */
|
|
10
|
+
type UrlToMarkdown = (url: string | URL, options: {
|
|
11
|
+
fetch: EgressFetch;
|
|
12
|
+
size?: FetchSize;
|
|
13
|
+
}) => Promise<UrlToMarkdownResult>;
|
|
14
|
+
/** Per-call extractor options. */
|
|
15
|
+
export interface ExtractOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Page-size budget for THIS call. Overrides the config's `fetchSize` when
|
|
18
|
+
* given. webveil's `s`/`m`/`l`/`f` preset maps STRAIGHT to distilly's `size`
|
|
19
|
+
* (the two enums are identical), so this is passed through verbatim.
|
|
20
|
+
*/
|
|
21
|
+
size?: FetchSize;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Seams the extractor's collaborators so it is testable WITHOUT real network or
|
|
25
|
+
* undici: tests inject a spy `urlToMarkdown` and/or a spy egress fetch to assert
|
|
26
|
+
* distilly is called with the egress fetch (never a global). Defaults wire the
|
|
27
|
+
* real `distilly/fetch` + `createEgressFetch`.
|
|
28
|
+
*/
|
|
29
|
+
export interface ExtractDeps {
|
|
30
|
+
/** distilly's networked `urlToMarkdown` (default: the real `distilly/fetch`). */
|
|
31
|
+
urlToMarkdown?: UrlToMarkdown;
|
|
32
|
+
/** Builds the egress-bound fetch from config (default: createEgressFetch). */
|
|
33
|
+
createEgressFetch?: (config: Config) => EgressFetch;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Extract a URL to clean, budget-bounded markdown via distilly over webveil's
|
|
37
|
+
* egress. Builds the egress-bound `fetch` (fail-loud on an unbuildable proxy),
|
|
38
|
+
* injects it into distilly's `urlToMarkdown`, maps the `s`/`m`/`l`/`f` preset to
|
|
39
|
+
* distilly's `size`, and surfaces distilly's `truncated`.
|
|
40
|
+
*
|
|
41
|
+
* @returns `{ url, markdown, truncated }` (a `FetchResult` without a `title`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function extract(url: string, config: Config, options?: ExtractOptions, deps?: ExtractDeps): Promise<FetchResult>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/core/extract.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,MAAM,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AACnD,OAAO,EAAoB,KAAK,WAAW,EAAC,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErD,0EAA0E;AAC1E,UAAU,mBAAmB;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,+EAA+E;AAC/E,KAAK,aAAa,GAAG,CACpB,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAC,KAC3C,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,iFAAiF;IACjF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,EAC5B,IAAI,GAAE,WAAgB,GACpB,OAAO,CAAC,WAAW,CAAC,CAYtB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Extractor seam — turn a URL into clean, size-bounded markdown by calling
|
|
2
|
+
// distilly's NETWORKED `urlToMarkdown` (the `distilly/fetch` entrypoint),
|
|
3
|
+
// INJECTING webveil's egress-bound `fetch` as the only transport. distilly's
|
|
4
|
+
// network Rules (github/mdn/react.dev/vuejs.org) rewrite a matching URL to its
|
|
5
|
+
// raw `.md`/API source and fetch THAT over our egress — shorter, cleaner output;
|
|
6
|
+
// non-matching URLs run through distilly's pure core. See docs/adr/0001.
|
|
7
|
+
//
|
|
8
|
+
// THE HARD INVARIANT (load-bearing for anonymity): webveil ALWAYS injects its
|
|
9
|
+
// egress-bound `fetch` here and NEVER lets distilly use a global/default fetch.
|
|
10
|
+
// distilly throws if none is injected — the desired fail-loud. And the egress
|
|
11
|
+
// fetch itself throws (before any I/O) when a configured proxy is unbuildable
|
|
12
|
+
// (egress.ts), so a broken proxy can never become an un-proxied request.
|
|
13
|
+
//
|
|
14
|
+
// This is the DEFAULT extractor; a backend's own `/extract` (tavily-compat) may
|
|
15
|
+
// override it (wired in the core-fetch task).
|
|
16
|
+
import { urlToMarkdown as distillyUrlToMarkdown } from 'distilly/fetch';
|
|
17
|
+
import { createEgressFetch } from './egress.js';
|
|
18
|
+
/**
|
|
19
|
+
* Extract a URL to clean, budget-bounded markdown via distilly over webveil's
|
|
20
|
+
* egress. Builds the egress-bound `fetch` (fail-loud on an unbuildable proxy),
|
|
21
|
+
* injects it into distilly's `urlToMarkdown`, maps the `s`/`m`/`l`/`f` preset to
|
|
22
|
+
* distilly's `size`, and surfaces distilly's `truncated`.
|
|
23
|
+
*
|
|
24
|
+
* @returns `{ url, markdown, truncated }` (a `FetchResult` without a `title`).
|
|
25
|
+
*/
|
|
26
|
+
export async function extract(url, config, options = {}, deps = {}) {
|
|
27
|
+
const urlToMarkdown = deps.urlToMarkdown ?? distillyUrlToMarkdown;
|
|
28
|
+
const buildFetch = deps.createEgressFetch ?? createEgressFetch;
|
|
29
|
+
// Build the egress-bound fetch FIRST: a configured-but-unbuildable proxy
|
|
30
|
+
// throws here, before any network access (never an un-proxied request).
|
|
31
|
+
const fetch = buildFetch(config);
|
|
32
|
+
const size = options.size ?? config.fetchSize;
|
|
33
|
+
const { markdown, truncated } = await urlToMarkdown(url, { fetch, size });
|
|
34
|
+
return { url, markdown, truncated };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/core/extract.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,+EAA+E;AAC/E,iFAAiF;AACjF,yEAAyE;AACzE,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,8EAA8E;AAC9E,yEAAyE;AACzE,EAAE;AACF,gFAAgF;AAChF,8CAA8C;AAE9C,OAAO,EAAC,aAAa,IAAI,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EAAC,iBAAiB,EAAmB,MAAM,aAAa,CAAC;AAsChE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC5B,GAAW,EACX,MAAc,EACd,UAA0B,EAAE,EAC5B,OAAoB,EAAE;IAEtB,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,qBAAqB,CAAC;IAClE,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;IAE/D,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAEjC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC;IAE9C,MAAM,EAAC,QAAQ,EAAE,SAAS,EAAC,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;IACtE,OAAO,EAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Config, ResolveOptions } from './config.js';
|
|
2
|
+
import type { EgressFetch } from './egress.js';
|
|
3
|
+
import type { Dispatcher } from './egress.js';
|
|
4
|
+
import type { ExtractDeps } from './extract.js';
|
|
5
|
+
import type { Backend, FetchOptions, FetchResult, Http } from './backends/types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Collaborators, seamed so the core is testable WITHOUT real config files,
|
|
8
|
+
* undici, network, or distilly: a test injects fakes to assert the
|
|
9
|
+
* backend-`/extract`-vs-distilly branch, the list path, and that the guarded
|
|
10
|
+
* egress fetch (never a global) is what reaches distilly. Defaults wire the real
|
|
11
|
+
* modules.
|
|
12
|
+
*/
|
|
13
|
+
export interface FetchDeps {
|
|
14
|
+
resolveConfig?: (options?: ResolveOptions) => Config;
|
|
15
|
+
getBackend?: (name: string, config: Config) => Backend;
|
|
16
|
+
buildDispatcher?: (config: Config) => Dispatcher | undefined;
|
|
17
|
+
createHttp?: (dispatcher: Dispatcher | undefined) => Http;
|
|
18
|
+
createEgressFetch?: (config: Config) => EgressFetch;
|
|
19
|
+
guardEgressFetch?: (fetch: EgressFetch, config: Config) => EgressFetch;
|
|
20
|
+
extract?: (url: string, config: Config, options: {
|
|
21
|
+
size?: Config['fetchSize'];
|
|
22
|
+
}, deps: ExtractDeps) => Promise<FetchResult>;
|
|
23
|
+
}
|
|
24
|
+
/** Per-call fetch options plus the config-resolution knobs (cwd/env/global). */
|
|
25
|
+
export interface FetchCoreOptions extends FetchOptions, ResolveOptions {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch a LIST of urls to clean, size-bounded markdown, in order. This is the
|
|
29
|
+
* list-ready internal (story 12): the single-URL `fetch()` below is a thin
|
|
30
|
+
* wrapper over it, so a future `web_batch_fetch` reuses this directly.
|
|
31
|
+
*
|
|
32
|
+
* Each url goes through the SAME content-source choice: a backend's own
|
|
33
|
+
* `/extract` (if the configured backend implements `fetch`) OR the default
|
|
34
|
+
* distilly Extractor with the GUARDED egress fetch injected.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fetchAll(urls: string[], options?: FetchCoreOptions, deps?: FetchDeps): Promise<FetchResult[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch ONE url to clean, size-bounded markdown (`{ markdown, truncated, … }`).
|
|
39
|
+
* A thin single-URL wrapper over the list-ready `fetchAll` (story 12).
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetch(url: string, options?: FetchCoreOptions, deps?: FetchDeps): Promise<FetchResult>;
|
|
42
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,MAAM,EAAE,cAAc,EAAC,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAI7C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EACX,OAAO,EACP,YAAY,EACZ,WAAW,EACX,IAAI,EACJ,MAAM,qBAAqB,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACzB,aAAa,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IACrD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1D,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;IACpD,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;KAAC,EACrC,IAAI,EAAE,WAAW,KACb,OAAO,CAAC,WAAW,CAAC,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,gBAAiB,SAAQ,YAAY,EAAE,cAAc;CAAG;AAEzE;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,gBAAqB,EAC9B,IAAI,GAAE,SAAc,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC,CAsCxB;AAYD;;;GAGG;AACH,wBAAsB,KAAK,CAC1B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,gBAAqB,EAC9B,IAAI,GAAE,SAAc,GAClB,OAAO,CAAC,WAAW,CAAC,CAGtB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// core fetch: the plain, framework-agnostic `fetch()` BOTH frontends (the incur
|
|
2
|
+
// CLI/MCP and the pi extension) call. Returns clean, size-bounded markdown with
|
|
3
|
+
// distilly's `truncated` flag.
|
|
4
|
+
//
|
|
5
|
+
// Flow (per URL): pick the content source (a backend's own `/extract`
|
|
6
|
+
// (tavily-compat) when the configured backend provides one, OTHERWISE the
|
|
7
|
+
// default distilly Extractor seam, urlToMarkdown over webveil's egress). The
|
|
8
|
+
// SSRF guard lives INSIDE the egress-bound fetch injected into distilly, so it
|
|
9
|
+
// covers distilly's rule-rewritten requests too (docs/adr/0001).
|
|
10
|
+
//
|
|
11
|
+
// LIST-READY INTERNALS (story 12): the work happens in `fetchAll(urls, …)`, a
|
|
12
|
+
// list-processing internal, so a future `web_batch_fetch` tool is a trivial add
|
|
13
|
+
// with no redesign. The public `fetch()` is a thin single-URL wrapper over it.
|
|
14
|
+
import { resolveConfig as defaultResolveConfig } from './config.js';
|
|
15
|
+
import { createEgressFetch as defaultCreateEgressFetch } from './egress.js';
|
|
16
|
+
import { guardEgressFetch as defaultGuardEgressFetch } from './security.js';
|
|
17
|
+
import { createHttp as defaultCreateHttp } from './http.js';
|
|
18
|
+
import { buildDispatcher as defaultBuildDispatcher } from './egress.js';
|
|
19
|
+
import { extract as defaultExtract } from './extract.js';
|
|
20
|
+
import { getBackend as defaultGetBackend } from './backends/registry.js';
|
|
21
|
+
/**
|
|
22
|
+
* Fetch a LIST of urls to clean, size-bounded markdown, in order. This is the
|
|
23
|
+
* list-ready internal (story 12): the single-URL `fetch()` below is a thin
|
|
24
|
+
* wrapper over it, so a future `web_batch_fetch` reuses this directly.
|
|
25
|
+
*
|
|
26
|
+
* Each url goes through the SAME content-source choice: a backend's own
|
|
27
|
+
* `/extract` (if the configured backend implements `fetch`) OR the default
|
|
28
|
+
* distilly Extractor with the GUARDED egress fetch injected.
|
|
29
|
+
*/
|
|
30
|
+
export async function fetchAll(urls, options = {}, deps = {}) {
|
|
31
|
+
const resolveConfig = deps.resolveConfig ?? defaultResolveConfig;
|
|
32
|
+
const getBackend = deps.getBackend ?? defaultGetBackend;
|
|
33
|
+
const buildDispatcher = deps.buildDispatcher ?? defaultBuildDispatcher;
|
|
34
|
+
const createHttp = deps.createHttp ?? defaultCreateHttp;
|
|
35
|
+
const createEgressFetch = deps.createEgressFetch ?? defaultCreateEgressFetch;
|
|
36
|
+
const guardEgressFetch = deps.guardEgressFetch ?? defaultGuardEgressFetch;
|
|
37
|
+
const extract = deps.extract ?? defaultExtract;
|
|
38
|
+
const config = resolveConfig({
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
env: options.env,
|
|
41
|
+
globalPath: options.globalPath,
|
|
42
|
+
});
|
|
43
|
+
const backend = getBackend(config.backend, config);
|
|
44
|
+
// A backend that provides its own `/extract` (tavily-compat) OVERRIDES the
|
|
45
|
+
// distilly Extractor; it is handed only the proxied http helper (built from
|
|
46
|
+
// the SAME dispatcher as the egress fetch), so it cannot bypass egress.
|
|
47
|
+
if (backend.fetch) {
|
|
48
|
+
const http = createHttp(buildDispatcher(config));
|
|
49
|
+
const backendFetch = backend.fetch.bind(backend);
|
|
50
|
+
return runAll(urls, (url) => backendFetch(url, http, { size: options.size, signal: options.signal }));
|
|
51
|
+
}
|
|
52
|
+
// Default path: distilly Extractor over webveil's egress. Build the
|
|
53
|
+
// egress-bound fetch ONCE, wrap it with the SSRF guard, and inject THAT into
|
|
54
|
+
// distilly (never a global fetch). The guard covers distilly's rule-rewritten
|
|
55
|
+
// requests too. A configured-but-unbuildable proxy throws at build time
|
|
56
|
+
// (fail-loud), before any I/O.
|
|
57
|
+
const guardedFetch = guardEgressFetch(createEgressFetch(config), config);
|
|
58
|
+
const extractDeps = { createEgressFetch: () => guardedFetch };
|
|
59
|
+
return runAll(urls, (url) => extract(url, config, { size: options.size }, extractDeps));
|
|
60
|
+
}
|
|
61
|
+
/** Run a per-url worker over the list in order, collecting the results. */
|
|
62
|
+
async function runAll(urls, work) {
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const url of urls)
|
|
65
|
+
out.push(await work(url));
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Fetch ONE url to clean, size-bounded markdown (`{ markdown, truncated, … }`).
|
|
70
|
+
* A thin single-URL wrapper over the list-ready `fetchAll` (story 12).
|
|
71
|
+
*/
|
|
72
|
+
export async function fetch(url, options = {}, deps = {}) {
|
|
73
|
+
const [result] = await fetchAll([url], options, deps);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Dispatcher } from 'undici';
|
|
2
|
+
import type { Http } from './backends/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build the proxied http helper over a given dispatcher. Both methods throw on a
|
|
5
|
+
* non-2xx response so a backend never silently consumes an error body.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createHttp(dispatcher: Dispatcher | undefined): Http;
|
|
8
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,KAAK,UAAU,EAAuB,MAAM,QAAQ,CAAC;AAC7D,OAAO,KAAK,EAAC,IAAI,EAAqB,MAAM,qBAAqB,CAAC;AA8BlE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAqBnE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// http helper — the proxied `http` handed to backends. fetchJson / fetchText
|
|
2
|
+
// apply the egress dispatcher + a per-request timeout + abort. Distinct from the
|
|
3
|
+
// egress-bound WHATWG `fetch` (egress.ts), but bound to the SAME dispatcher, so
|
|
4
|
+
// a backend physically cannot bypass the configured egress.
|
|
5
|
+
import { fetch as undiciFetch } from 'undici';
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
|
+
async function request(dispatcher, url, options = {}) {
|
|
8
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
if (options.signal)
|
|
12
|
+
options.signal.addEventListener('abort', () => controller.abort(), {
|
|
13
|
+
once: true,
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const res = await undiciFetch(url, {
|
|
17
|
+
method: options.method,
|
|
18
|
+
headers: options.headers,
|
|
19
|
+
body: options.body,
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
dispatcher,
|
|
22
|
+
});
|
|
23
|
+
return res;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the proxied http helper over a given dispatcher. Both methods throw on a
|
|
31
|
+
* non-2xx response so a backend never silently consumes an error body.
|
|
32
|
+
*/
|
|
33
|
+
export function createHttp(dispatcher) {
|
|
34
|
+
return {
|
|
35
|
+
async fetchJson(url, options) {
|
|
36
|
+
const res = await request(dispatcher, url, options);
|
|
37
|
+
if (!res.ok)
|
|
38
|
+
throw new Error(`http ${res.status} ${res.statusText} for ${url}`);
|
|
39
|
+
return (await res.json());
|
|
40
|
+
},
|
|
41
|
+
async fetchText(url, options) {
|
|
42
|
+
const res = await request(dispatcher, url, options);
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
throw new Error(`http ${res.status} ${res.statusText} for ${url}`);
|
|
45
|
+
return await res.text();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,iFAAiF;AACjF,gFAAgF;AAChF,4DAA4D;AAE5D,OAAO,EAAkB,KAAK,IAAI,WAAW,EAAC,MAAM,QAAQ,CAAC;AAG7D,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,KAAK,UAAU,OAAO,CACrB,UAAkC,EAClC,GAAW,EACX,UAA8B,EAAE;IAEhC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM;QACjB,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE;YAClE,IAAI,EAAE,IAAI;SACV,CAAC,CAAC;IACJ,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE;YAClC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,UAAU;SACD,CAAC,CAAC;QACZ,OAAO,GAA0B,CAAC;IACnC,CAAC;YAAS,CAAC;QACV,YAAY,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkC;IAC5D,OAAO;QACN,KAAK,CAAC,SAAS,CACd,GAAW,EACX,OAA4B;YAE5B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC,GAAG,CAAC,EAAE;gBACV,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,QAAQ,GAAG,EAAE,CAAC,CAAC;YACpE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;QAChC,CAAC;QACD,KAAK,CAAC,SAAS,CACd,GAAW,EACX,OAA4B;YAE5B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC,GAAG,CAAC,EAAE;gBACV,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,QAAQ,GAAG,EAAE,CAAC,CAAC;YACpE,OAAO,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Config, ResolveOptions } from './config.js';
|
|
2
|
+
import type { Dispatcher } from './egress.js';
|
|
3
|
+
import type { BackendTransport } from './baseurl.js';
|
|
4
|
+
import type { Http, SearchOptions, SearchResult } from './backends/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Collaborators, seamed so the core is testable WITHOUT real config files,
|
|
7
|
+
* undici, or network: a test injects a fake `getBackend`/`createHttp` to assert
|
|
8
|
+
* the backend is handed only the proxied helper, and a fake backend returning
|
|
9
|
+
* duplicate/over-limit hits to assert dedup + clamp. Defaults wire the real
|
|
10
|
+
* config/egress/http/registry modules.
|
|
11
|
+
*/
|
|
12
|
+
export interface SearchDeps {
|
|
13
|
+
resolveConfig?: (options?: ResolveOptions) => Config;
|
|
14
|
+
buildDispatcher?: (config: Config) => Dispatcher | undefined;
|
|
15
|
+
assertEgressAllowsBaseUrl?: (config: Config) => void;
|
|
16
|
+
resolveBackendTransport?: (baseUrl: string) => BackendTransport;
|
|
17
|
+
createHttp?: (dispatcher: Dispatcher | undefined) => Http;
|
|
18
|
+
getBackend?: (name: string, config: Config) => {
|
|
19
|
+
search: (query: string, http: Http, options?: SearchOptions) => Promise<SearchResult[]>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Per-call search options plus the config-resolution knobs (cwd/env/global). */
|
|
23
|
+
export interface SearchCoreOptions extends SearchOptions, ResolveOptions {
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Search the configured backend over the configured egress and return
|
|
27
|
+
* normalized `SearchResult[]` (deduped by url, then clamped to `maxResults`).
|
|
28
|
+
*
|
|
29
|
+
* Dedup runs BEFORE the clamp so the caller gets up to `maxResults` UNIQUE hits,
|
|
30
|
+
* not a window that duplicates eat into; for the same reason the backend is NOT
|
|
31
|
+
* asked to pre-clamp (only the abort signal is forwarded).
|
|
32
|
+
*/
|
|
33
|
+
export declare function search(query: string, options?: SearchCoreOptions, deps?: SearchDeps): Promise<SearchResult[]>;
|
|
34
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,MAAM,EAAE,cAAc,EAAC,MAAM,aAAa,CAAC;AAKxD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAGnD,OAAO,KAAK,EAAC,IAAI,EAAE,aAAa,EAAE,YAAY,EAAC,MAAM,qBAAqB,CAAC;AAU3E;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IAC1B,aAAa,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IAC7D,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,uBAAuB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC;IAChE,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1D,UAAU,CAAC,EAAE,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,KACV;QACJ,MAAM,EAAE,CACP,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,KACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC7B,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,WAAW,iBAAkB,SAAQ,aAAa,EAAE,cAAc;CAAG;AAc3E;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,iBAAsB,EAC/B,IAAI,GAAE,UAAe,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// core search — the plain, framework-agnostic `search()` BOTH frontends (the
|
|
2
|
+
// incur CLI/MCP and the pi extension) call. It owns the wiring and the
|
|
3
|
+
// caller-facing post-processing; the per-source parsing lives in the backend.
|
|
4
|
+
//
|
|
5
|
+
// Flow: resolve config → build the egress dispatcher → bind the proxied `http`
|
|
6
|
+
// helper to it → select the backend from the registry → call the backend with
|
|
7
|
+
// ONLY that proxied helper → normalize (dedup + clamp) the SearchResult[].
|
|
8
|
+
//
|
|
9
|
+
// The egress invariant (docs/adr/0001): the backend is handed only the
|
|
10
|
+
// dispatcher-bound `http` helper, so it physically cannot reach a global fetch
|
|
11
|
+
// and bypass the configured egress. A configured-but-unbuildable proxy throws at
|
|
12
|
+
// buildDispatcher (fail-loud), never silently un-proxied.
|
|
13
|
+
import { resolveConfig as defaultResolveConfig } from './config.js';
|
|
14
|
+
import { buildDispatcher as defaultBuildDispatcher, assertEgressAllowsBaseUrl as defaultAssertEgressAllowsBaseUrl, } from './egress.js';
|
|
15
|
+
import { resolveBackendTransport as defaultResolveBackendTransport } from './baseurl.js';
|
|
16
|
+
import { createHttp as defaultCreateHttp } from './http.js';
|
|
17
|
+
import { getBackend as defaultGetBackend } from './backends/registry.js';
|
|
18
|
+
/**
|
|
19
|
+
* Default cap on returned results when the caller does not pass `maxResults`.
|
|
20
|
+
* Keeps an agent's context small by default; a caller can raise/lower it per
|
|
21
|
+
* call. (Recorded decision: there is no configured default, so the core sets
|
|
22
|
+
* one; see the task's Decisions block.)
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_MAX_RESULTS = 10;
|
|
25
|
+
/** Dedup by url (the hit's identity), preserving first-seen order. */
|
|
26
|
+
function dedup(results) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const r of results) {
|
|
30
|
+
if (seen.has(r.url))
|
|
31
|
+
continue;
|
|
32
|
+
seen.add(r.url);
|
|
33
|
+
out.push(r);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Search the configured backend over the configured egress and return
|
|
39
|
+
* normalized `SearchResult[]` (deduped by url, then clamped to `maxResults`).
|
|
40
|
+
*
|
|
41
|
+
* Dedup runs BEFORE the clamp so the caller gets up to `maxResults` UNIQUE hits,
|
|
42
|
+
* not a window that duplicates eat into; for the same reason the backend is NOT
|
|
43
|
+
* asked to pre-clamp (only the abort signal is forwarded).
|
|
44
|
+
*/
|
|
45
|
+
export async function search(query, options = {}, deps = {}) {
|
|
46
|
+
const resolveConfig = deps.resolveConfig ?? defaultResolveConfig;
|
|
47
|
+
const buildDispatcher = deps.buildDispatcher ?? defaultBuildDispatcher;
|
|
48
|
+
const assertEgressAllowsBaseUrl = deps.assertEgressAllowsBaseUrl ?? defaultAssertEgressAllowsBaseUrl;
|
|
49
|
+
const resolveBackendTransport = deps.resolveBackendTransport ?? defaultResolveBackendTransport;
|
|
50
|
+
const createHttp = deps.createHttp ?? defaultCreateHttp;
|
|
51
|
+
const getBackend = deps.getBackend ?? defaultGetBackend;
|
|
52
|
+
const config = resolveConfig({
|
|
53
|
+
cwd: options.cwd,
|
|
54
|
+
env: options.env,
|
|
55
|
+
globalPath: options.globalPath,
|
|
56
|
+
});
|
|
57
|
+
// Fail loud on the false-confidence combo (a local `unix:` socket baseUrl
|
|
58
|
+
// behind a proxy egress) BEFORE any transport is built.
|
|
59
|
+
assertEgressAllowsBaseUrl(config);
|
|
60
|
+
// Resolve the BACKEND-hop transport. For a normal TCP baseUrl this is a no-op
|
|
61
|
+
// (no per-hop dispatcher); for a `unix:` baseUrl it yields a socket-bound
|
|
62
|
+
// `Agent` and a synthetic `http://localhost…` base the backend builds on. The
|
|
63
|
+
// socket transport is scoped to THIS hop only and is NEVER bound into the
|
|
64
|
+
// shared config-wide egress dispatcher, so `web_fetch` egress is unaffected.
|
|
65
|
+
const transport = resolveBackendTransport(config.baseUrl);
|
|
66
|
+
// Build the egress dispatcher FIRST: a configured-but-unbuildable proxy throws
|
|
67
|
+
// here, before any network access (never an un-proxied request). For a socket
|
|
68
|
+
// baseUrl the per-hop socket dispatcher overrides the (direct/undefined) one.
|
|
69
|
+
const dispatcher = transport.dispatcher ?? buildDispatcher(config);
|
|
70
|
+
const http = createHttp(dispatcher);
|
|
71
|
+
// The backend stays transport-unaware: it receives a config whose baseUrl is
|
|
72
|
+
// always a real `http(s):` base (the `unix:` form is rewritten away here).
|
|
73
|
+
const backendConfig = transport.baseUrl === config.baseUrl
|
|
74
|
+
? config
|
|
75
|
+
: { ...config, baseUrl: transport.baseUrl };
|
|
76
|
+
const backend = getBackend(backendConfig.backend, backendConfig);
|
|
77
|
+
// Hand the backend ONLY the proxied helper (no maxResults: dedup happens
|
|
78
|
+
// here, over the full set, so the clamp below is over UNIQUE results).
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = await backend.search(query, http, { signal: options.signal });
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
// Best-effort close of the per-hop socket Agent (the shared egress
|
|
85
|
+
// dispatcher, owned by config, is NOT touched here).
|
|
86
|
+
if (transport.dispatcher)
|
|
87
|
+
void transport.dispatcher.close();
|
|
88
|
+
}
|
|
89
|
+
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
90
|
+
return dedup(raw).slice(0, maxResults);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
import type { EgressFetch } from './egress.js';
|
|
3
|
+
/** Thrown when the SSRF guard refuses a request to a private/blocked address. */
|
|
4
|
+
export declare class SsrfError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Is this LITERAL IP private / non-public? Covers the ranges that must never be
|
|
9
|
+
* reachable from a direct-egress web fetch:
|
|
10
|
+
* IPv4: 0.0.0.0/8, 10/8 (RFC1918), 127/8 (loopback), 169.254/16 (link-local,
|
|
11
|
+
* incl. the 169.254.169.254 cloud metadata endpoint), 172.16/12 (RFC1918),
|
|
12
|
+
* 192.168/16 (RFC1918), 100.64/10 (CGNAT), 192.0.0/24, 192.0.2/24,
|
|
13
|
+
* 198.18/15, 198.51.100/24, 203.0.113/24, 224/4 (multicast), 240/4
|
|
14
|
+
* (reserved).
|
|
15
|
+
* IPv6: ::1 (loopback), :: (unspecified), fc00::/7 (ULA), fe80::/10
|
|
16
|
+
* (link-local), ff00::/8 (multicast), plus IPv4-mapped (::ffff:a.b.c.d,
|
|
17
|
+
* re-checked as IPv4). Default-deny: anything outside global unicast
|
|
18
|
+
* (2000::/3) is treated as non-public.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Assert a URL is safe to fetch under THIS config's egress. Under a proxy egress
|
|
23
|
+
* it always passes (the proxy owns egress + DNS). Under direct egress it rejects
|
|
24
|
+
* a literal private IP and, for a hostname, resolves it locally and rejects if it
|
|
25
|
+
* maps to a private IP (so a name pointing at 127.0.0.1 / metadata is caught).
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertPublicUrl(url: string, config: Config): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Wrap an egress-bound `fetch` with the SSRF guard. The returned fetch checks
|
|
30
|
+
* EVERY request URL (so it covers distilly's rule-rewritten requests too, not
|
|
31
|
+
* only webveil's own GET) before delegating to the underlying egress fetch.
|
|
32
|
+
* This is what `core.fetch()` injects into distilly. See docs/adr/0001.
|
|
33
|
+
*/
|
|
34
|
+
export declare function guardEgressFetch(fetch: EgressFetch, config: Config): EgressFetch;
|
|
35
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/core/security.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAE7C,iFAAiF;AACjF,qBAAa,SAAU,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI3B;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK/C;AAuCD;;;;;GAKG;AACH,wBAAsB,eAAe,CACpC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GACZ,WAAW,CAWb"}
|