fix: reject traversal in migration paths
This commit is contained in:
parent
745554e2fb
commit
6791b9b96c
2 changed files with 20 additions and 2 deletions
|
|
@ -5,6 +5,10 @@ import { join } from 'node:path';
|
||||||
const SOURCE_COUNT = 29;
|
const SOURCE_COUNT = 29;
|
||||||
const DOCS_ROOT = 'src/content/docs/';
|
const DOCS_ROOT = 'src/content/docs/';
|
||||||
|
|
||||||
|
function hasParentTraversal(path) {
|
||||||
|
return path.split('/').includes('..');
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadManifest(path) {
|
export async function loadManifest(path) {
|
||||||
return JSON.parse(await readFile(path, 'utf8'));
|
return JSON.parse(await readFile(path, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
@ -36,10 +40,10 @@ export function validateManifest(manifest) {
|
||||||
if (source.startsWith('pages/wiki/')) {
|
if (source.startsWith('pages/wiki/')) {
|
||||||
throw new Error(`Generic DokuWiki source is excluded: ${source}`);
|
throw new Error(`Generic DokuWiki source is excluded: ${source}`);
|
||||||
}
|
}
|
||||||
if (!(source === 'pages/start.txt' || source === 'pages/study.txt' || source.startsWith('pages/study/'))) {
|
if (hasParentTraversal(source) || !(source === 'pages/start.txt' || source === 'pages/study.txt' || source.startsWith('pages/study/'))) {
|
||||||
throw new Error(`Source is outside the selected migration scope: ${source}`);
|
throw new Error(`Source is outside the selected migration scope: ${source}`);
|
||||||
}
|
}
|
||||||
if (!destination.startsWith(DOCS_ROOT)) {
|
if (hasParentTraversal(destination) || !destination.startsWith(DOCS_ROOT)) {
|
||||||
throw new Error(`Destination is outside ${DOCS_ROOT}: ${destination}`);
|
throw new Error(`Destination is outside ${DOCS_ROOT}: ${destination}`);
|
||||||
}
|
}
|
||||||
if (sources.has(source)) {
|
if (sources.has(source)) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@ test('generic DokuWiki pages are excluded', async () => {
|
||||||
assert.equal(manifest.entries.some(({ source }) => source.startsWith('pages/wiki/')), false);
|
assert.equal(manifest.entries.some(({ source }) => source.startsWith('pages/wiki/')), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('validation rejects source traversal outside the selected scope', async () => {
|
||||||
|
const manifest = await loadManifest('docs/migration-manifest.json');
|
||||||
|
manifest.entries[2].source = 'pages/study/../../wiki/x.txt';
|
||||||
|
|
||||||
|
assert.throws(() => validateManifest(manifest), /outside the selected migration scope/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validation rejects destination traversal outside the docs root', async () => {
|
||||||
|
const manifest = await loadManifest('docs/migration-manifest.json');
|
||||||
|
manifest.entries[2].destination = 'src/content/docs/../../../outside.md';
|
||||||
|
|
||||||
|
assert.throws(() => validateManifest(manifest), /outside src\/content\/docs/);
|
||||||
|
});
|
||||||
|
|
||||||
test('validation rejects duplicate destinations unless the later entry is a merge', async () => {
|
test('validation rejects duplicate destinations unless the later entry is a merge', async () => {
|
||||||
const manifest = await loadManifest('docs/migration-manifest.json');
|
const manifest = await loadManifest('docs/migration-manifest.json');
|
||||||
manifest.entries[1].mode = 'page';
|
manifest.entries[1].mode = 'page';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue