Skip to content

Commit

Permalink
fix(create-docusaurus): potential security issue with command injecti…
Browse files Browse the repository at this point in the history
…on (#7507)
  • Loading branch information
slorber authored May 27, 2022
1 parent cd7cf78 commit dbd161d
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 22 deletions.
1 change: 1 addition & 0 deletions packages/create-docusaurus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "2.0.0-beta.20",
"@docusaurus/utils": "2.0.0-beta.20",
"commander": "^5.1.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
Expand Down
9 changes: 6 additions & 3 deletions packages/create-docusaurus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import logger from '@docusaurus/logger';
import shell from 'shelljs';
import prompts, {type Choice} from 'prompts';
import supportsColor from 'supports-color';
import {escapeShellArg} from '@docusaurus/utils';

type CLIOptions = {
packageManager?: PackageManager;
Expand Down Expand Up @@ -463,9 +464,11 @@ export default async function init(
logger.info('Creating new Docusaurus project...');

if (source.type === 'git') {
logger.info`Cloning Git template url=${source.url}...`;
const command = await getGitCommand(source.strategy);
if (shell.exec(`${command} ${source.url} ${dest}`).code !== 0) {
const gitCommand = await getGitCommand(source.strategy);
const gitCloneCommand = `${gitCommand} ${escapeShellArg(
source.url,
)} ${escapeShellArg(dest)}`;
if (shell.exec(gitCloneCommand).code !== 0) {
logger.error`Cloning Git template failed!`;
process.exit(1);
}
Expand Down
19 changes: 19 additions & 0 deletions packages/docusaurus-utils/src/__tests__/shellUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {escapeShellArg} from '../shellUtils';

describe('shellUtils', () => {
it('escapeShellArg', () => {
expect(escapeShellArg('hello')).toBe("'hello'");
expect(escapeShellArg('*')).toBe("'*'");
expect(escapeShellArg('hello world')).toBe("'hello world'");
expect(escapeShellArg("'hello'")).toBe("\\''hello'\\'");
expect(escapeShellArg('$(pwd)')).toBe("'$(pwd)'");
expect(escapeShellArg('hello$(pwd)')).toBe("'hello$(pwd)'");
});
});
31 changes: 12 additions & 19 deletions packages/docusaurus-utils/src/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,19 @@ export function getFileCommitDate(
);
}

let formatArg = '--format=%ct';
if (includeAuthor) {
formatArg += ',%an';
}

let extraArgs = '--max-count=1';
if (age === 'oldest') {
// --follow is necessary to follow file renames
// --diff-filter=A ensures we only get the commit which (A)dded the file
extraArgs += ' --follow --diff-filter=A';
}
const args = [
`--format=%ct${includeAuthor ? ',%an' : ''}`,
'--max-count=1',
age === 'oldest' ? '--follow --diff-filter=A' : undefined,
]
.filter(Boolean)
.join(' ');

const result = shell.exec(
`git log ${extraArgs} ${formatArg} -- "${path.basename(file)}"`,
{
// Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
cwd: path.dirname(file),
silent: true,
},
);
const result = shell.exec(`git log ${args} -- "${path.basename(file)}"`, {
// Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
cwd: path.dirname(file),
silent: true,
});
if (result.code !== 0) {
throw new Error(
`Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`,
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export {
createAbsoluteFilePathMatcher,
} from './globUtils';
export {getFileLoaderUtils} from './webpackUtils';
export {escapeShellArg} from './shellUtils';
export {
getDataFilePath,
getDataFileData,
Expand Down
18 changes: 18 additions & 0 deletions packages/docusaurus-utils/src/shellUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// TODO move from shelljs to execa later?
// Execa is well maintained and widely used
// Even shelljs recommends execa for security / escaping:
// https://github.com/shelljs/shelljs/wiki/Security-guidelines

// Inspired by https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js
export function escapeShellArg(s: string): string {
let res = `'${s.replace(/'/g, "'\\''")}'`;
res = res.replace(/^(?:'')+/g, '').replace(/\\'''/g, "\\'");
return res;
}
2 changes: 2 additions & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ esbuild
eslintcache
estree
evaluable
execa
Execa
externalwaiting
failfast
fbid
Expand Down

0 comments on commit dbd161d

Please sign in to comment.