mirror of
https://github.com/hansjone/dsh-search-mcp.git
synced 2026-10-09 00:00:45 +08:00
Publish dsh-search-mcp fork for newer DSH and Bailian WebSearch MCP.
Based on gxpppp/dsh-search-mcp; includes DSH web settings fixes, Clash fake-IP URL policy, and Bailian default server patch. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
d5c4d2006c
16 changed files with 4821 additions and 0 deletions
158
test/url-policy.test.js
Normal file
158
test/url-policy.test.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
createPinnedLookup,
|
||||
isAllowedEndpointAddress,
|
||||
isPublicAddress,
|
||||
validateHttpEndpoint,
|
||||
} from '../lib/url-policy.js';
|
||||
|
||||
const lookup = (records) => (_hostname, options, callback) => {
|
||||
assert.equal(options.all, true);
|
||||
queueMicrotask(() => callback(null, records));
|
||||
};
|
||||
|
||||
test('URL policy accepts HTTP(S) with public DNS only', async () => {
|
||||
const result = await validateHttpEndpoint('https://search.example/mcp', {
|
||||
lookup: lookup([
|
||||
{ address: '8.8.8.8', family: 4 },
|
||||
{ address: '2606:4700:4700:0:0:0:0:1111', family: 6 },
|
||||
]),
|
||||
});
|
||||
assert.equal(result.url.hostname, 'search.example');
|
||||
assert.deepEqual(result.addresses, [
|
||||
{ address: '8.8.8.8', family: 4 },
|
||||
{ address: '2606:4700:4700:0:0:0:0:1111', family: 6 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('URL policy rejects schemes, userinfo, localhost, and ambiguous IPv4', async () => {
|
||||
for (const input of [
|
||||
'ftp://example.com/mcp',
|
||||
'https://user:pass@example.com/mcp',
|
||||
'http://localhost/mcp',
|
||||
'http://api.localhost/mcp',
|
||||
'http://127.0.0.1/mcp',
|
||||
'http://127.1/mcp',
|
||||
'http://0177.0.0.1/mcp',
|
||||
'http://0x7f000001/mcp',
|
||||
]) {
|
||||
await assert.rejects(() => validateHttpEndpoint(input), { name: 'SearchMcpUrlPolicyError' }, input);
|
||||
}
|
||||
});
|
||||
|
||||
test('URL policy rejects private, reserved, test, mapped, and transition ranges', () => {
|
||||
for (const address of [
|
||||
'0.0.0.0',
|
||||
'10.0.0.1',
|
||||
'100.64.0.1',
|
||||
'127.0.0.1',
|
||||
'169.254.169.254',
|
||||
'172.16.0.1',
|
||||
'192.168.0.1',
|
||||
'192.0.2.1',
|
||||
'198.18.0.1',
|
||||
'198.51.100.1',
|
||||
'203.0.113.1',
|
||||
'224.0.0.1',
|
||||
'255.255.255.255',
|
||||
'::',
|
||||
'::1',
|
||||
'::ffff:127.0.0.1',
|
||||
'::7f00:1',
|
||||
'::a00:1',
|
||||
'::a9fe:a9fe',
|
||||
'::c0a8:101',
|
||||
'::808:808',
|
||||
'64:ff9b::808:808',
|
||||
'2001:db8::1',
|
||||
'2001::1',
|
||||
'2002:0808:0808::1',
|
||||
'fc00::1',
|
||||
'fe80::1',
|
||||
'ff02::1',
|
||||
]) {
|
||||
assert.equal(isPublicAddress(address), false, address);
|
||||
}
|
||||
assert.equal(isPublicAddress('8.8.8.8'), true);
|
||||
assert.equal(isPublicAddress('2606:4700:4700::1111'), true);
|
||||
});
|
||||
|
||||
test('URL policy drops private DNS answers and keeps public ones', async () => {
|
||||
const result = await validateHttpEndpoint('https://search.example/mcp', {
|
||||
lookup: lookup([
|
||||
{ address: '8.8.8.8', family: 4 },
|
||||
{ address: '10.0.0.1', family: 4 },
|
||||
]),
|
||||
});
|
||||
assert.deepEqual(result.addresses, [{ address: '8.8.8.8', family: 4 }]);
|
||||
});
|
||||
|
||||
test('URL policy accepts Clash fake-IP answers with optional public peers', async () => {
|
||||
const mixed = await validateHttpEndpoint('https://dashscope.example/mcp', {
|
||||
lookup: lookup([
|
||||
{ address: '198.18.2.146', family: 4 },
|
||||
{ address: '2408:400a:3e:ef02:12f:bd95:e827:51d', family: 6 },
|
||||
]),
|
||||
});
|
||||
assert.deepEqual(mixed.addresses, [
|
||||
{ address: '198.18.2.146', family: 4 },
|
||||
{ address: '2408:400a:3e:ef02:12f:bd95:e827:51d', family: 6 },
|
||||
]);
|
||||
|
||||
const fakeOnly = await validateHttpEndpoint('https://dashscope.example/mcp', {
|
||||
lookup: lookup([{ address: '198.18.2.146', family: 4 }]),
|
||||
});
|
||||
assert.deepEqual(fakeOnly.addresses, [{ address: '198.18.2.146', family: 4 }]);
|
||||
assert.equal(isAllowedEndpointAddress('198.18.2.146'), true);
|
||||
assert.equal(isAllowedEndpointAddress('10.0.0.1'), false);
|
||||
});
|
||||
|
||||
test('URL policy rejects private-only DNS answers', async () => {
|
||||
await assert.rejects(
|
||||
() => validateHttpEndpoint('https://search.example/mcp', {
|
||||
lookup: lookup([{ address: '10.0.0.1', family: 4 }]),
|
||||
}),
|
||||
/non-public address/,
|
||||
);
|
||||
});
|
||||
|
||||
test('URL policy rejects DNS errors and empty answers without leaking endpoint data', async () => {
|
||||
await assert.rejects(
|
||||
() => validateHttpEndpoint('https://search.example/mcp', {
|
||||
lookup: (_hostname, _options, callback) => callback(new Error('resolver detail')),
|
||||
}),
|
||||
/resolution failed/,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => validateHttpEndpoint('https://search.example/mcp', { lookup: lookup([]) }),
|
||||
/did not resolve/,
|
||||
);
|
||||
});
|
||||
|
||||
test('URL policy aborts a pending DNS lookup', async () => {
|
||||
const controller = new AbortController();
|
||||
const pending = validateHttpEndpoint('https://search.example/mcp', {
|
||||
lookup: () => {},
|
||||
signal: controller.signal,
|
||||
});
|
||||
controller.abort();
|
||||
await assert.rejects(pending, /validation was aborted/);
|
||||
});
|
||||
|
||||
test('pinned lookup serves only validated host and addresses', async () => {
|
||||
const pinned = createPinnedLookup('search.example', [
|
||||
{ address: '8.8.8.8', family: 4 },
|
||||
{ address: '2606:4700:4700:0:0:0:0:1111', family: 6 },
|
||||
]);
|
||||
const all = await new Promise((resolve, reject) => {
|
||||
pinned('search.example', { all: true }, (error, records) => error ? reject(error) : resolve(records));
|
||||
});
|
||||
assert.deepEqual(all, [
|
||||
{ address: '8.8.8.8', family: 4 },
|
||||
{ address: '2606:4700:4700:0:0:0:0:1111', family: 6 },
|
||||
]);
|
||||
await assert.rejects(new Promise((resolve, reject) => {
|
||||
pinned('other.example', {}, (error, address) => error ? reject(error) : resolve(address));
|
||||
}), /unvalidated hostname/);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue