qwen_2.5_model / src /core /plugins /plugin-net.spec.ts
Muhammad Noman
Deploy OpenWA to Hugging Face Spaces
46252cd
Raw
History Blame Contribute Delete
7.3 kB
import { effectiveNetAllow, isNetHostAllowed, performPluginFetch } from './plugin-net';
import type { withSafeFetch } from '../../common/security/ssrf-guard';
/** A stand-in for withSafeFetch that hands `use` a canned Response and records the init it was given. */
function fakeSafeFetch(response: Response, sink: { init?: RequestInit }): typeof withSafeFetch {
return (<T>(_url: string, init: RequestInit, use: (r: Response) => Promise<T> | T): Promise<T> => {
sink.init = init;
return Promise.resolve(use(response));
}) as unknown as typeof withSafeFetch;
}
function cannedResponse(body: string, headers: Record<string, string>, status = 200): Response {
const bytes = new TextEncoder().encode(body);
let read = false;
return {
ok: status >= 200 && status < 300,
status,
statusText: status === 200 ? 'OK' : 'ERR',
headers: new Headers(headers),
// Minimal ReadableStream-like body: yields the bytes once, then done.
body: {
getReader: () => ({
read: () =>
Promise.resolve(read ? { done: true, value: undefined } : ((read = true), { done: false, value: bytes })),
cancel: () => Promise.resolve(),
}),
},
} as unknown as Response;
}
describe('isNetHostAllowed', () => {
it('denies by default (no allowlist)', () => {
expect(isNetHostAllowed(undefined, 'https://api.example.com/x')).toBe(false);
expect(isNetHostAllowed([], 'https://api.example.com/x')).toBe(false);
});
it("'*' allows any public host (the SSRF guard still blocks internal IPs at fetch time)", () => {
expect(isNetHostAllowed(['*'], 'https://api.example.com/x')).toBe(true);
expect(isNetHostAllowed(['*'], 'http://other.example.org:8080/y')).toBe(true);
});
it('matches host:port, defaulting the port from the scheme', () => {
expect(isNetHostAllowed(['api.example.com:443'], 'https://api.example.com/x')).toBe(true);
expect(isNetHostAllowed(['api.example.com:80'], 'http://api.example.com/x')).toBe(true);
expect(isNetHostAllowed(['api.example.com:443'], 'https://api.example.com:8443/x')).toBe(false);
});
it('a bare host (no port) allows any port on that host', () => {
expect(isNetHostAllowed(['api.example.com'], 'https://api.example.com:8443/x')).toBe(true);
expect(isNetHostAllowed(['api.example.com'], 'https://other.example.com/x')).toBe(false);
});
it('rejects non-http(s) schemes and unparseable URLs', () => {
expect(isNetHostAllowed(['*'], 'ftp://api.example.com/x')).toBe(false);
expect(isNetHostAllowed(['*'], 'file:///etc/passwd')).toBe(false);
expect(isNetHostAllowed(['*'], 'not a url')).toBe(false);
});
});
describe('performPluginFetch', () => {
it('routes through the safe-fetch guard and serializes the response', async () => {
const sink: { init?: RequestInit } = {};
const fetcher = fakeSafeFetch(cannedResponse('{"hello":"hi"}', { 'content-type': 'application/json' }), sink);
const res = await performPluginFetch(
'https://api.example.com/t',
{ method: 'POST', body: '{}', headers: { 'x-k': 'v' } },
{ fetch: fetcher },
);
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect(JSON.parse(res.body)).toEqual({ hello: 'hi' });
expect(res.headers['content-type']).toBe('application/json');
// method/headers/body are passed to the guarded fetch (which does the SSRF pinning).
expect(sink.init?.method).toBe('POST');
expect(sink.init?.body).toBe('{}');
});
it('coerces a non-numeric timeoutMs to the default instead of throwing a RangeError', async () => {
const sink: { init?: RequestInit } = {};
const fetcher = fakeSafeFetch(cannedResponse('{}', { 'content-type': 'application/json' }), sink);
// A string 'abc' would make AbortSignal.timeout(NaN) throw before the request runs; the coercion
// must fall back to the default so the documented timeout clamp holds and the fetch proceeds.
await expect(
performPluginFetch('https://api.example.com/t', { timeoutMs: 'abc' as unknown as number }, { fetch: fetcher }),
).resolves.toMatchObject({ ok: true });
expect(sink.init?.signal).toBeInstanceOf(AbortSignal);
});
it('rejects a response whose declared content-length exceeds the cap', async () => {
const sink: { init?: RequestInit } = {};
const big = String(11 * 1024 * 1024);
const fetcher = fakeSafeFetch(cannedResponse('x', { 'content-length': big }), sink);
await expect(performPluginFetch('https://api.example.com/t', {}, { fetch: fetcher })).rejects.toThrow(/cap/i);
});
it('rejects once the global concurrent-fetch cap is reached, and recovers after slots free up', async () => {
// Each in-flight fetch buffers up to the body cap host-side, so total buffering must stay bounded.
// Hold all slots open with a gated fetch, prove the next call rejects fast, then drain and recover.
let release!: () => void;
const gate = new Promise<void>(r => (release = r));
const blocking = (<T>(_url: string, _init: RequestInit, use: (r: Response) => Promise<T> | T): Promise<T> =>
gate.then(() => use(cannedResponse('{}', {})))) as unknown as typeof withSafeFetch;
const inflight: Promise<unknown>[] = [];
for (let i = 0; i < 16; i++) {
inflight.push(performPluginFetch('https://api.example.com/t', {}, { fetch: blocking }));
}
// The 17th call reserves no slot — it rejects immediately without awaiting the gate.
await expect(performPluginFetch('https://api.example.com/t', {}, { fetch: blocking })).rejects.toThrow(
/too many concurrent/i,
);
release();
await Promise.all(inflight);
// Slots freed → a fresh fetch succeeds again.
const sink: { init?: RequestInit } = {};
await expect(
performPluginFetch('https://api.example.com/t', {}, { fetch: fakeSafeFetch(cannedResponse('{}', {}), sink) }),
).resolves.toMatchObject({ ok: true });
});
});
describe('effectiveNetAllow', () => {
it('adds the host of each named config URL to the static allowlist', () => {
expect(effectiveNetAllow(['api.static.com'], ['baseUrl'], { baseUrl: 'https://chat.acme.com' })).toEqual([
'api.static.com',
'chat.acme.com',
]);
});
it('ignores missing / non-string / non-https / credentialed config values', () => {
expect(effectiveNetAllow([], ['baseUrl'], {})).toEqual([]);
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 42 })).toEqual([]);
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'not a url' })).toEqual([]);
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'http://x' })).toEqual([]); // https-only
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'https://u:p@x' })).toEqual([]); // no credentials
});
it("never admits the '*' wildcard sentinel from a config value, and preserves an explicit port", () => {
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'https://*' })).toEqual([]); // bare '*' would open all hosts
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'https://%2A' })).toEqual([]); // encoded '*' too
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'https://*:443/x' })).toEqual([]);
expect(effectiveNetAllow([], ['baseUrl'], { baseUrl: 'https://host.com:8443' })).toEqual(['host.com:8443']);
});
});