From 7e27d1272996ead317ab6448e672f4787a3d882b Mon Sep 17 00:00:00 2001 From: nmccready Date: Fri, 23 Aug 2024 21:59:39 -0400 Subject: [PATCH 1/2] fix: cve globby issue resolved via glob --- README.md | 2 +- index.js | 6 +++--- package.json | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ef7753e..262d00a 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ Only applicable if **type** is `api`: - **parameters** (optional): Parameters passed to **action** (e.g. `{ StackName: "MyStack" }`) - **region** (optional): Either `AWS_DEFAULT_REGION` or this parameter have to be set which specifies the region where the API call is made. You can also use a plain string if you want the default behavior, which is simply including a JSON file. -- **isGlob** (optional): Forces the usage of [globby](https://www.npmjs.com/package/globby) to spit out an array of includes +- **isGlob** (optional): Forces the usage of [glob](https://www.npmjs.com/package/glob) to spit out an array of includes - **inject** (optional): Pass in localized env / options to be injected into a template ### Examples diff --git a/index.js b/index.js index 233e324..ad954e4 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ const url = require('url'); const path = require('path'); const { readFile } = require('fs/promises'); const _ = require('lodash'); -const globby = require('globby'); +const { globSync } = require('glob'); const Promise = require('bluebird'); const sortObject = require('@znemz/sort-object'); const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3'); @@ -262,7 +262,7 @@ async function recurse({ base, scope, cft, ...opts }) { const absolute = location.relative ? path.join(path.dirname(base.path), location.host, location.path || '') : [location.host, location.path].join(''); - const globs = globby.sync(absolute); + const globs = globSync(absolute).sort(); if (json.omitExtension) { return globs.map((f) => path.basename(f, path.extname(f))); } @@ -590,7 +590,7 @@ async function fnInclude({ base, scope, cft, ...opts }) { handleInjectSetup(); if (isGlob(cft, absolute)) { - const paths = globby.sync(absolute); + const paths = globSync(absolute).sort(); const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n')); return recurse({ base, scope, cft: template, ...opts }); } diff --git a/package.json b/package.json index c051099..48216aa 100644 --- a/package.json +++ b/package.json @@ -41,23 +41,25 @@ "test:run": "sleep 1 && mocha --timeout 20000 --bail t/include.js t/cli.js t/replaceEnv.js" }, "dependencies": { - "@aws-sdk/client-cloudformation": "^3", - "@aws-sdk/client-s3": "^3", - "@znemz/cft-utils": "0.1.0", + "@aws-sdk/client-cloudformation": "^3.637.0", + "@aws-sdk/client-s3": "^3.637.0", + "@znemz/cft-utils": "0.1.1", "@znemz/sort-object": "^3.0.4", "aws-sdk-v3-proxy": "2.1.2", "bluebird": "^3.7.2", "deepmerge": "^4.2.2", - "globby": "^11.1.0", + "glob": "^11.0.0", "jmespath": "^0.16.0", "js-yaml": "^3.14.0", "jsonminify": "^0.4.1", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "path-parse": "~1.0.7", "proxy-agent": "6.3.1", "yargs": "17" }, "devDependencies": { + "@commitlint/cli": "^19", + "@commitlint/config-conventional": "^19", "better-npm-audit": "3.7.3", "eslint": "8", "eslint-config-prettier": "9", @@ -68,7 +70,7 @@ "npm-run-all": "4.1.5", "prettier": "3", "serve": "14.2.1", - "sort-package-json": "2.6.0" + "sort-package-json": "2.10.1" }, "engines": { "node": ">=8" From 5ec4c022787cdf9c7515681e43094f9b7ad7e754 Mon Sep 17 00:00:00 2001 From: nmccready Date: Fri, 23 Aug 2024 22:33:00 -0400 Subject: [PATCH 2/2] fix: make eval opt in for Fn::Eval, Fn::IfEval off by default --- README.md | 10 +++++++++- bin/cli.js | 16 +++++++++++----- index.js | 5 +++-- t/cli.js | 1 + t/include.js | 1 + t/tests/ifeval.js | 4 ++++ 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 262d00a..2f34673 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Options: * `--version` print version and exit * `--context` template full path. only utilized for stdin when the template is piped to this script example: `cat examples/base.template | ./bin/cli.js --context examples/base.template` -* `--enable` different options / toggles: ['env'] [string] [choices: "env"] +* `--enable` different options / toggles: ['env','eval'] [string] [choices: 'env','eval','env.eval' etc...] * `env` pre-process env vars and inject into templates as they are processed looks for $KEY or ${KEY} matches * `-i, --inject` JSON string payload to use for template injection. (Takes precedence over process.env (if enabled) injection and will be merged on top of process.env) * `--doLog` console log out include options in recurse step. @@ -1015,6 +1015,10 @@ In summary falsy values are omitted from an object except `false` and `0`. ## Fn::Eval +Opt in to use `eval` in your templates. This is disabled by default. + +`--enable eval` is required to turn on options.doEval in the include function. + ```yaml Fn::Eval: state: [1, 2, 3] @@ -1030,6 +1034,10 @@ Fn::Eval: ## Fn::IfEval +Opt in to use `eval` in your templates. This is disabled by default. + +`--enable eval` is required to turn on options.doEval in the include function. + ```yaml Fn::IfEval: inject: diff --git a/bin/cli.js b/bin/cli.js index b7eec83..3ee9d93 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -70,8 +70,9 @@ const opts = yargs }, enable: { string: true, - desc: `enable different options: ['env']`, - choices: ['env'], + desc: `enable different options: ['env','eval'] or a combination of both via comma.`, + choices: ['', 'env', 'env,eval', 'eval,env', 'eval'], // '' hack + default: '', }, inject: { alias: 'i', @@ -96,6 +97,9 @@ const opts = yargs }) .parse(); +// make enable an array +opts.enable = opts.enable.split(','); + let promise; if (opts.path) { let location; @@ -105,7 +109,8 @@ if (opts.path) { else location = `file://${path.join(process.cwd(), opts.path)}`; promise = include({ url: location, - doEnv: opts.enable === 'env', + doEnv: opts.enable.includes('env'), + doEval: opts.enable.includes('eval'), inject: opts.inject, doLog: opts.doLog, }); @@ -126,12 +131,13 @@ if (opts.path) { ? path.resolve(opts.context) : path.join(process.cwd(), 'template.yml'); - template = opts.enable === 'env' ? replaceEnv(template) : template; + template = opts.enable.includes('env') ? replaceEnv(template) : template; return include({ template: yaml.load(template), url: `file://${location}`, - doEnv: opts.enable === 'env', + doEnv: opts.enable.includes('env'), + doEval: opts.enable.includes('eval'), inject: opts.inject, doLog: opts.doLog, }).catch((err) => console.error(err)); diff --git a/index.js b/index.js index ad954e4..800bea4 100644 --- a/index.js +++ b/index.js @@ -44,6 +44,7 @@ const { isOurExplicitFunction } = require('./lib/schema'); * doEnv: opts.enable === 'env', * inject: opts.inject, * doLog: opts.doLog, + * doEval: opts.doEval, -- allow Fn::Eval to be used * }) */ module.exports = async function (options) { @@ -231,7 +232,7 @@ async function recurse({ base, scope, cft, ...opts }) { } ); } - if (cft['Fn::Eval']) { + if (cft['Fn::Eval'] && opts.doEval) { return recurse({ base, scope, cft: cft['Fn::Eval'], ...opts }).then(function (json) { // **WARNING** you have now enabled god mode // eslint-disable-next-line no-unused-vars, prefer-const @@ -386,7 +387,7 @@ async function recurse({ base, scope, cft, ...opts }) { return isString ? seq.map((i) => String.fromCharCode(i)) : seq; } - if (cft['Fn::IfEval']) { + if (cft['Fn::IfEval'] && opts.doEval) { return recurse({ base, scope, cft: cft['Fn::IfEval'], ...opts }).then(function (json) { // eslint-disable-next-line prefer-const let { truthy, falsy, evalCond, inject, doLog } = json; diff --git a/t/cli.js b/t/cli.js index 4bd2fc5..9c3cc95 100644 --- a/t/cli.js +++ b/t/cli.js @@ -26,6 +26,7 @@ const extendEnv = require('./tests/extendEnv'); return done(); } // console.log({out: out.toString()}) + out = out || '{}'; // fix for empty output to see failed test const json = JSON.parse(out.toString()); delete json.Metadata; assert.deepEqual(json, test.output); diff --git a/t/include.js b/t/include.js index c4f6331..804de0d 100644 --- a/t/include.js +++ b/t/include.js @@ -54,6 +54,7 @@ tests.forEach(function (file) { // eslint-disable-next-line n/no-path-concat url: `file://${__dirname}/template.json`, doEnv: !!test.doEnv || false, + doEval: !!test.doEval || false, }; if (test.inject) { opts.inject = test.inject; diff --git a/t/tests/ifeval.js b/t/tests/ifeval.js index c3fc954..9ff5fd4 100644 --- a/t/tests/ifeval.js +++ b/t/tests/ifeval.js @@ -2,6 +2,7 @@ module.exports = { ifEval: [ { name: 'truthy', + doEval: true, template: { 'Fn::IfEval': { inject: { @@ -25,6 +26,7 @@ module.exports = { }, { name: 'falsy', + doEval: true, template: { 'Fn::IfEval': { inject: { @@ -48,6 +50,7 @@ module.exports = { }, { name: 'no falsy', + doEval: true, template: { 'Fn::IfEval': { inject: { @@ -64,6 +67,7 @@ module.exports = { }, { name: 'evalCond required', + doEval: true, template: { 'Fn::IfEval': { inject: {