Validate first-party parser boundaries

This commit is contained in:
Codex committed 2026-10-13 21:47:18 +02:00
1 parent c5a92282a9
commit 6599d0a244
4 files changed
+95 -3

No files matched your search

+28
View File
@@ -2,9 +2,11 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const httpModule = require('node:http');
const { createServer } = require('../plugins/worker');
const { parse } = require('../plugins/registry');
const { parseSource } = require('../plugins/client');
const { validate } = require('../plugins/validation');
const { discoverWeebCatalog, discoverWeebChapter } = require('../adapters/weebcentral');
const seriesUrl = 'https://weebcentral.com/series/ABC/title';
@@ -21,6 +23,15 @@ test('first-party registry rejects unknown plugins and malformed input', () => {
assert.equal(parse('weebcentral', 'catalog', { html: listHtml, url: seriesUrl }).chapters.length, 2);
});
test('parser output cannot turn catalog and image links into local or unrelated requests', () => {
assert.throws(() => validate('weebcentral', 'series', { title: 'X', listUrl: 'http://127.0.0.1:8091/api/state' }), /unexpected/);
assert.throws(() => validate('weebcentral', 'catalog', { chapterLinks: 1, chapters: [{ id: 'X', number: '1', url: 'https://other.example/chapters/X' }] }), /unexpected/);
assert.throws(() => validate('weebcentral', 'chapter', { manifestUrl: 'https://weebcentral.com/chapters/X/not-images' }), /manifest/);
assert.throws(() => validate('weebcentral', 'pages', { pages: ['http://127.0.0.1:8080/private'] }), /unsafe/);
assert.throws(() => validate('weebcentral', 'pages', { pages: ['https://localhost/private'] }), /unsafe/);
assert.doesNotThrow(() => validate('weebcentral', 'pages', { pages: ['https://img.example/1.jpg'] }));
});
test('isolated parser preserves WeebCentral catalog and page discovery', async () => {
const server = createServer();
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
@@ -65,3 +76,20 @@ test('configured worker failure does not fall back to local parsing', async () =
else process.env.SOURCE_PLUGIN_URL = previous;
}
});
test('scraper rejects unsafe responses from a configured worker', async () => {
const server = httpModule.createServer((_request, response) => {
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ title: 'X', listUrl: 'http://127.0.0.1:8091/api/state' }));
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const previous = process.env.SOURCE_PLUGIN_URL;
process.env.SOURCE_PLUGIN_URL = `http://127.0.0.1:${server.address().port}`;
try {
await assert.rejects(parseSource('weebcentral', 'series', { html: seriesHtml, url: seriesUrl }), /unexpected/);
} finally {
if (previous === undefined) delete process.env.SOURCE_PLUGIN_URL;
else process.env.SOURCE_PLUGIN_URL = previous;
await new Promise(resolve => server.close(resolve));
}
});