Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/arguments/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import {normalizeFdSpecificOptions} from './specific.js';
// Normalize the options object, and sometimes also the file paths and arguments.
// Applies default values, validate allowed options, normalize them.
export const normalizeOptions = (filePath, rawArguments, rawOptions) => {
rawOptions.cwd = normalizeCwd(rawOptions.cwd);
const [processedFile, processedArguments, processedOptions] = handleNodeOption(filePath, rawArguments, rawOptions);
// Prevent prototype pollution by copying only own properties to a null-prototype object
const sanitizedOptions = {__proto__: null, ...rawOptions};
sanitizedOptions.cwd = normalizeCwd(sanitizedOptions.cwd);
const [processedFile, processedArguments, processedOptions] = handleNodeOption(filePath, rawArguments, sanitizedOptions);

const {command: file, args: commandArguments, options: initialOptions} = crossSpawn._parse(processedFile, processedArguments, processedOptions);

Expand All @@ -43,6 +45,7 @@ export const normalizeOptions = (filePath, rawArguments, rawOptions) => {
return {file, commandArguments, options};
};

// Use null prototype to prevent prototype pollution from leaking through
const addDefaultOptions = ({
extendEnv = true,
preferLocal = false,
Expand All @@ -61,6 +64,7 @@ const addDefaultOptions = ({
serialization = 'advanced',
...options
}) => ({
__proto__: null,
...options,
extendEnv,
preferLocal,
Expand Down
8 changes: 5 additions & 3 deletions lib/methods/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import isPlainObject from 'is-plain-obj';
import {FD_SPECIFIC_OPTIONS} from '../arguments/specific.js';

// Deep merge specific options like `env`. Shallow merge the other ones.
// Use spread (which only copies own properties) to safely read from boundOptions without prototype pollution
export const mergeOptions = (boundOptions, options) => {
const newOptions = Object.fromEntries(
const safeBoundOptions = {__proto__: null, ...boundOptions};
const mergedOptions = Object.fromEntries(
Object.entries(options).map(([optionName, optionValue]) => [
optionName,
mergeOption(optionName, boundOptions[optionName], optionValue),
mergeOption(optionName, safeBoundOptions[optionName], optionValue),
]),
);
return {...boundOptions, ...newOptions};
return {...safeBoundOptions, ...mergedOptions};
};

const mergeOption = (optionName, boundOptionValue, optionValue) => {
Expand Down
16 changes: 14 additions & 2 deletions lib/methods/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export const handleNodeOption = (file, commandArguments, {

const normalizedNodePath = safeNormalizeFileUrl(nodePath, 'The "nodePath" option');
const resolvedNodePath = path.resolve(cwd, normalizedNodePath);
// Use spread (which only copies own properties) to safely get shell without reading polluted prototype
const newOptions = {
__proto__: null,
shell: false,
...options,
nodePath: resolvedNodePath,
node: shouldHandleNode,
Expand All @@ -45,7 +48,16 @@ export const handleNodeOption = (file, commandArguments, {

return [
resolvedNodePath,
[...nodeOptions, file, ...commandArguments],
{ipc: true, ...newOptions, shell: false},
[
...nodeOptions,
file,
...commandArguments,
],
{
__proto__: null,
ipc: true,
...newOptions,
shell: false,
},
];
};
3 changes: 2 additions & 1 deletion lib/methods/parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export const normalizeParameters = (rawFile, rawArguments = [], rawOptions = {})
throw new TypeError(`Last argument must be an options object: ${options}`);
}

return [filePath, normalizedArguments, options];
// Prevent prototype pollution by copying only own properties to a null-prototype object
return [filePath, normalizedArguments, {__proto__: null, ...options}];
};