diff --git a/.gitignore b/.gitignore index cdfee2d3e..dc279dc7e 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,6 @@ typings/ # DynamoDB Local files .dynamodb/ + +# Built artifacts +dist/ \ No newline at end of file diff --git a/README.md b/README.md index 6b2c3e8e0..656de5c89 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ See [action.yml](action.yml) # Set always-auth option in npmrc file. # Default: '' always-auth: '' + + # Set ignore-scripts in npmrc file to prevent pre and postinstall scripts from running as they are a potential security problem. + # Default: false + ignore-scripts: false ``` diff --git a/__tests__/ignore-scripts.test.ts b/__tests__/ignore-scripts.test.ts new file mode 100644 index 000000000..980be6152 --- /dev/null +++ b/__tests__/ignore-scripts.test.ts @@ -0,0 +1,46 @@ +import path from 'path'; +import fs from 'fs'; +import * as ignorescripts from '../src/ignore-scripts'; +import {getNpmrcLocation} from '../src/util'; + +let rcFile: string; + +describe('ignore-scripts tests', () => { + const runnerDir = path.join(__dirname, 'runner'); + + beforeEach(async () => { + rcFile = getNpmrcLocation(); + }, 5000); + + afterEach(async () => { + fs.unlinkSync(rcFile); + rcFile = getNpmrcLocation(); + }, 10000); + + it('sets the value to true according to input', async () => { + ignorescripts.ignoreScriptsInNpmConfig('true'); + const rcContents = fs.readFileSync(rcFile).toString(); + expect(rcContents).toMatch('\nignore-scripts=true\n'); + }); + + it('sets the value to false according to input', async () => { + ignorescripts.ignoreScriptsInNpmConfig('false'); + const rcContents = fs.readFileSync(rcFile).toString(); + expect(rcContents).toMatch('\nignore-scripts=false\n'); + }); + + it('defaults to false on empty input', async () => { + ignorescripts.ignoreScriptsInNpmConfig(''); + const rcContents = fs.readFileSync(rcFile).toString(); + expect(rcContents).toMatch('\nignore-scripts=false\n'); + }); + + it('preserves existing npmrc file contents', async () => { + fs.writeFileSync(getNpmrcLocation(), 'something\nwhatever\nstuff'); + ignorescripts.ignoreScriptsInNpmConfig('true'); + const rcContents = fs.readFileSync(rcFile).toString(); + expect(rcContents).toMatch( + 'something\nwhatever\nstuff\nignore-scripts=true\n' + ); + }); +}); diff --git a/action.yml b/action.yml index 99db5869f..c8588f96b 100644 --- a/action.yml +++ b/action.yml @@ -25,6 +25,9 @@ inputs: description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.' cache-dependency-path: description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.' + ignore-scripts: + description: 'Set ignore-scripts in npmrc to prevent pre and postinstall scripts from running as they are a potential security problem.' + default: 'false' # TODO: add input to control forcing to pull from cloud or dist. # escape valve for someone having issues or needing the absolute latest which isn't cached yet outputs: diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index 35d549498..72d492aa7 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -83333,7 +83333,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; +exports.defaultIfEmpty = exports.getNpmrcLocation = exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const fs_1 = __importDefault(__nccwpck_require__(7147)); @@ -83429,6 +83429,12 @@ const unique = () => { }; }; exports.unique = unique; +const getNpmrcLocation = () => { + return path_1.default.resolve(process.env['RUNNER_TEMP'] || process.cwd(), '.npmrc'); +}; +exports.getNpmrcLocation = getNpmrcLocation; +const defaultIfEmpty = (input, defaultValue) => input.length === 0 ? defaultValue : input; +exports.defaultIfEmpty = defaultIfEmpty; /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index c0eade6da..3dbdc889e 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -93605,6 +93605,29 @@ class CanaryBuild extends base_distribution_prerelease_1.default { exports["default"] = CanaryBuild; +/***/ }), + +/***/ 6572: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ignoreScriptsInNpmConfig = void 0; +const fs_1 = __nccwpck_require__(7147); +const util_1 = __nccwpck_require__(2629); +const ignoreScriptsInNpmConfig = (ignore) => { + const nonEmptyInput = (0, util_1.defaultIfEmpty)(ignore, 'false'); + const ignored = JSON.parse(nonEmptyInput); + appendToNpmrc(ignored); +}; +exports.ignoreScriptsInNpmConfig = ignoreScriptsInNpmConfig; +const appendToNpmrc = (ignoreScripts) => { + const npmrc = (0, util_1.getNpmrcLocation)(); + (0, fs_1.writeFileSync)(npmrc, `\nignore-scripts=${ignoreScripts}\n`, { flag: 'a' }); +}; + + /***/ }), /***/ 399: @@ -93658,6 +93681,7 @@ const cache_utils_1 = __nccwpck_require__(1678); const installer_factory_1 = __nccwpck_require__(5617); const util_1 = __nccwpck_require__(2629); const constants_1 = __nccwpck_require__(9042); +const ignore_scripts_1 = __nccwpck_require__(6572); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -93697,6 +93721,8 @@ function run() { if (registryUrl) { auth.configAuthentication(registryUrl, alwaysAuth); } + const ignoreScripts = core.getInput('ignore-scripts'); + (0, ignore_scripts_1.ignoreScriptsInNpmConfig)(ignoreScripts); if (cache && (0, cache_utils_1.isCacheFeatureAvailable)()) { core.saveState(constants_1.State.CachePackageManager, cache); const cacheDependencyPath = core.getInput('cache-dependency-path'); @@ -93780,7 +93806,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; +exports.defaultIfEmpty = exports.getNpmrcLocation = exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const fs_1 = __importDefault(__nccwpck_require__(7147)); @@ -93876,6 +93902,12 @@ const unique = () => { }; }; exports.unique = unique; +const getNpmrcLocation = () => { + return path_1.default.resolve(process.env['RUNNER_TEMP'] || process.cwd(), '.npmrc'); +}; +exports.getNpmrcLocation = getNpmrcLocation; +const defaultIfEmpty = (input, defaultValue) => input.length === 0 ? defaultValue : input; +exports.defaultIfEmpty = defaultIfEmpty; /***/ }), diff --git a/src/ignore-scripts.ts b/src/ignore-scripts.ts new file mode 100644 index 000000000..62a689423 --- /dev/null +++ b/src/ignore-scripts.ts @@ -0,0 +1,13 @@ +import {writeFileSync} from 'fs'; +import {defaultIfEmpty, getNpmrcLocation} from './util'; + +export const ignoreScriptsInNpmConfig = (ignore: string): void => { + const nonEmptyInput: string = defaultIfEmpty(ignore, 'false'); + const ignored: boolean = JSON.parse(nonEmptyInput); + appendToNpmrc(ignored); +}; + +const appendToNpmrc = (ignoreScripts: boolean): void => { + const npmrc = getNpmrcLocation(); + writeFileSync(npmrc, `\nignore-scripts=${ignoreScripts}\n`, {flag: 'a'}); +}; diff --git a/src/main.ts b/src/main.ts index c55c3b005..d61f005e8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import {isCacheFeatureAvailable} from './cache-utils'; import {getNodejsDistribution} from './distributions/installer-factory'; import {getNodeVersionFromFile, printEnvDetailsAndSetOutput} from './util'; import {State} from './constants'; +import {ignoreScriptsInNpmConfig} from './ignore-scripts'; export async function run() { try { @@ -59,6 +60,9 @@ export async function run() { auth.configAuthentication(registryUrl, alwaysAuth); } + const ignoreScripts: string = core.getInput('ignore-scripts'); + ignoreScriptsInNpmConfig(ignoreScripts); + if (cache && isCacheFeatureAvailable()) { core.saveState(State.CachePackageManager, cache); const cacheDependencyPath = core.getInput('cache-dependency-path'); diff --git a/src/util.ts b/src/util.ts index cc6ac3107..3343fcf01 100644 --- a/src/util.ts +++ b/src/util.ts @@ -105,3 +105,10 @@ export const unique = () => { return true; }; }; + +export const getNpmrcLocation: () => string = () => { + return path.resolve(process.env['RUNNER_TEMP'] || process.cwd(), '.npmrc'); +}; + +export const defaultIfEmpty = (input: string, defaultValue: string): string => + input.length === 0 ? defaultValue : input;