From a27a24fd5a69d4fde65b019dd5e850fe5b531af6 Mon Sep 17 00:00:00 2001 From: Ruslans Tarasovs Date: Thu, 30 Apr 2026 12:31:08 +0300 Subject: [PATCH] Implemented a fix preventing to write outside of current directory Signed-off-by: Ruslans Tarasovs --- .../src/stages/generate/helpers.test.ts | 47 ++++++++++++++++--- .../src/stages/generate/helpers.ts | 7 ++- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index 4285a26657..770eb0e254 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -581,11 +581,13 @@ describe('helpers', () => { await expect( patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger }), - ).rejects.toThrow(/not allowed to refer to a location outside/i); + ).rejects.toThrow( + /Source path .* is not allowed to refer to a location outside/i, + ); }, ); - it.each(['README.md', 'readme.md'])( + it.each(['README.md', 'readme.md', 'docs/README.md', 'docs/readme.md'])( 'should write %s to a symlink docs directory if docs/index.md does not exist', async fileName => { mockDir.setContent({ @@ -600,25 +602,58 @@ describe('helpers', () => { }); await expect( - fs.readFile(mockDir.resolve('docs/index.md'), 'utf-8'), + fs.readFile(mockDir.resolve('target/docs/index.md'), 'utf-8'), ).resolves.toEqual(`${fileName} content`); }, ); it.each(['README.md', 'readme.md'])( - 'should reject writing %s if targe symlink points outside of the current directory', + 'should reject creating docs dir if target symlink points to non-existing directory outside of the current directory', async fileName => { const anotherMockDir = createMockDirectory(); mockDir.setContent({ - docs: ctx => ctx.symlink(anotherMockDir.path), + docs: ctx => ctx.symlink(anotherMockDir.resolve('docs')), 'information.md': 'information.md content', [fileName]: `${fileName} content`, }); await expect( patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger }), - ).rejects.toThrow(/not allowed to refer to a location outside/i); + ).rejects.toThrow( + /Target path .* is not allowed to refer to a location outside/i, + ); + + await expect(fs.exists(anotherMockDir.resolve('docs'))).resolves.toBe( + false, + ); + }, + ); + + it.each(['README.md', 'readme.md'])( + 'should reject creating docs dir if target symlink points to existing directory outside of the current directory', + async fileName => { + const anotherMockDir = createMockDirectory(); + + mockDir.setContent({ + docs: ctx => ctx.symlink(anotherMockDir.resolve('docs')), + 'information.md': 'information.md content', + [fileName]: `${fileName} content`, + }); + + anotherMockDir.setContent({ + docs: {}, + }); + + await expect( + patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger }), + ).rejects.toThrow( + /Target path .* is not allowed to refer to a location outside/i, + ); + + await expect( + fs.exists(anotherMockDir.resolve('docs/index.md')), + ).resolves.toBe(false); }, ); }); diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index 351fff2f93..7e6b710f96 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -397,11 +397,16 @@ export const patchIndexPreBuild = async ({ path.join(inputDir, 'readme.md'), ]; + if (!isChildPath(inputDir, docsPath)) { + throw new NotAllowedError( + `Target path ${docsPath} is not allowed to refer to a location outside ${inputDir}`, + ); + } await fs.ensureDir(docsPath); for (const filePath of fallbacks) { if (!isChildPath(inputDir, filePath)) { throw new NotAllowedError( - `Path ${filePath} is not allowed to refer to a location outside ${inputDir}`, + `Source path ${filePath} is not allowed to refer to a location outside ${inputDir}`, ); } try {