diff --git a/.depcheckrc.json b/.depcheckrc.json index 1d3a983..d9563e8 100644 --- a/.depcheckrc.json +++ b/.depcheckrc.json @@ -10,6 +10,7 @@ "@vitest/coverage-istanbul", "@vitest/eslint-plugin", "@yarnpkg/types", + "@yarnpkg/shell", "eslint-config-*", "eslint-import-resolver-typescript", "eslint-plugin-*", diff --git a/.oxfmtrc.json b/.oxfmtrc.json index a8a63ac..eaf60bd 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,6 +1,6 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": [".yarnrc.yml"], + "ignorePatterns": [".yarnrc.yml", "lavamoat"], "printWidth": 80, "quoteProps": "as-needed", "singleQuote": true, diff --git a/.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs b/.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs deleted file mode 100644 index 05671f9..0000000 --- a/.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-allow-scripts", -factory: function (require) { -var plugin=(()=>{var a=Object.create,l=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var p=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty;var u=e=>l(e,"__esModule",{value:!0});var f=e=>{if(typeof require!="undefined")return require(e);throw new Error('Dynamic require of "'+e+'" is not supported')};var g=(e,o)=>{for(var r in o)l(e,r,{get:o[r],enumerable:!0})},m=(e,o,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of s(o))!c.call(e,t)&&t!=="default"&&l(e,t,{get:()=>o[t],enumerable:!(r=i(o,t))||r.enumerable});return e},x=e=>m(u(l(e!=null?a(p(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var k={};g(k,{default:()=>d});var n=x(f("@yarnpkg/shell")),y={hooks:{afterAllInstalled:async()=>{let e=await(0,n.execute)("yarn run allow-scripts");e!==0&&process.exit(e)}}},d=y;return k;})(); -return plugin; -} -}; diff --git a/.yarnrc.yml b/.yarnrc.yml index b901501..6659c0c 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -7,10 +7,20 @@ compressionLevel: mixed enableGlobalCache: false +# Enable hardened mode to validate lockfile content against remote registries. +enableHardenedMode: true + +# Disable Yarn's mirror feature to avoid writing to the global cache. +enableMirror: false + enableScripts: false enableTelemetry: false +# Despite `enableGlobalCache` and `enableMirror` being `false`, Yarn is still +# writing to the global folder. +globalFolder: .yarn/global + logFilters: - code: YN0004 level: discard @@ -28,6 +38,7 @@ npmPreapprovedPackages: - '@metamask-previews/*' - '@lavamoat/*' +# Protect the runtime of calls to "yarn run" scripts using a local plugin. plugins: - - path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs - spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js' + - path: ./lavamoat/plugin-allow-scripts.js + - path: ./lavamoat/.runner-plugin.js diff --git a/eslint.config.mjs b/eslint.config.mjs index ac19e12..35f47ed 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ import vitest from '@metamask/eslint-config-vitest'; const config = createConfig([ { - ignores: ['dist/', 'docs/', '.yarn/'], + ignores: ['dist/', 'docs/', '.yarn/', 'lavamoat/'], }, { diff --git a/lavamoat/.env.ban.json b/lavamoat/.env.ban.json new file mode 100644 index 0000000..524f5af --- /dev/null +++ b/lavamoat/.env.ban.json @@ -0,0 +1 @@ +["SESSION", "SSH", "KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"] diff --git a/lavamoat/.runner-plugin.js b/lavamoat/.runner-plugin.js new file mode 100755 index 0000000..2e41219 --- /dev/null +++ b/lavamoat/.runner-plugin.js @@ -0,0 +1,370 @@ +/** + * Yarn 4 plugin for script environment hardening. + * + * @module + */ + +/** @typedef {NonNullable} WrapScriptExecutionHook */ + +/* global makeRunScriptWrapper */ + +/** + * Yarn 4 plugin factory for wrapScriptExecution hook. + * + * @param {any} require - Yarn's require function + * @returns {{ hooks: { wrapScriptExecution: Function } }} + */ +module.exports = { + name: '@yarnpkg/plugin-runner', + factory: function (/** @type {NodeJS.Require} */ require) { + const { tmpdir } = require('node:os') + return { + hooks: { + /** @type {WrapScriptExecutionHook} */ + wrapScriptExecution: async ( + executor, + project, + locator, + scriptName, + extra + ) => { + const path = require('node:path') + const fs = require('node:fs') + const workspace = project.tryWorkspaceByLocator(locator) + + if (!workspace) { + // a script is being executed outside of a workspace context, so we + // can't apply any custom logic. + // This is the case when a postinstal is running. + + // "Do nothing" - return the original executor immediately + // without running any custom plugin logic or reading manifests. + // TODO: implement wrapping these scripts with reasonable defaults + return executor + } + + const pkgJson = workspace.manifest.raw + const binFolder = + extra.env.BERRY_BIN_FOLDER || `node_modules${path.sep}.bin` + + const wrapper = makeRunScriptWrapper( + { + scriptName, + scriptPayload: extra.script, + projectRoot: extra.cwd, + pathBinMatcher: (fragment) => { + return fragment.endsWith(binFolder) + }, + customizePermissionsConfig: addMandatoryReads, + readScriptsConfig: () => { + return pkgJson.scriptsConfig + }, + }, + { + readFileSync: fs.readFileSync, + pathJoin: path.join, + pathDelimiter: path.delimiter, + tmpdir, + realpathSync: fs.realpathSync, + } + ) + + // extra.env is a reference to the mutable object, but a different variable + // containing that reference is used within execute, so we must amend not + // replace it. + const newEnv = wrapper.processEnv(extra.env) + for (const key of Object.keys(extra.env)) { + delete extra.env[key] + } + Object.assign(extra.env, newEnv) + return executor + }, + }, + } + }, +} + +/** + * @param {Record} configOptions + * @param {NodeJS.ProcessEnv} _env + */ +function addMandatoryReads(configOptions, _env) { + if (!configOptions['--permission']) { + return + } + configOptions['--allow-fs-tmp'] = true // yarn always uses tmp dirs. +} + +;; +/// + +/** + * @param {MakeRunScriptWrapperOptions} param0 + * @param {MakeRunScriptWrapperIO} param1 + * @returns {MakeRunScriptWrapper} + */ +function makeRunScriptWrapper( + { + scriptName, + scriptPayload: _scriptPayload, // might be useful to read in the future + projectRoot, + pathBinMatcher, + customizePermissionsConfig, + readScriptsConfig, + }, + { readFileSync, pathJoin, pathDelimiter, tmpdir, realpathSync } +) { + const DEFAULT_PERMISSION_KEY = '#default' + + /** @param {string} filePath */ + function readJsonFile(filePath) { + return JSON.parse(readFileSync(filePath, 'utf8')) + } + + /** + * @param {object} opts + * @param {Record | undefined} opts.scriptsConfig + * @param {string} [opts.scriptName] + * @param {string} opts.projectRoot + */ + function readConfig({ + scriptsConfig, + scriptName = DEFAULT_PERMISSION_KEY, + projectRoot, + }) { + if (!scriptsConfig) { + return {} + } + const configName = + scriptsConfig[scriptName] || scriptsConfig[DEFAULT_PERMISSION_KEY] + + // config needs to be optional, because it's opt-in first and specifying a default turns it opt-out. + if (!configName) { + return {} + } + const configPath = pathJoin(projectRoot, configName) + let conf + try { + conf = readJsonFile(configPath) + if (typeof conf !== 'object' || conf === null) { + throw Error(`Expected an object, got ${typeof conf}`) + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + throw Error( + `[LavaMoat] Error loading script config file "${configPath}": ${message}`, + { cause: err } + ) + } + return conf + } + + /** + * Adds features we'd want in Node.js permissions model and intend to + * eventually upstream + * + * @param {ConfigOptions} configOptions + * @param {NodeJS.ProcessEnv} env + * @returns {void} + */ + function permissionsModelCompatibilityExtensions(configOptions, env) { + // 1. support env variables as values in allow-fs-* + /** + * @param {string} value + * @returns {string} + */ + const replaceEnvVar = (value) => { + const envVarMatch = value.match(/^\$([A-Z_][A-Z0-9_]*)$/i) + if (envVarMatch) { + const envVarName = envVarMatch[1] + if (env[envVarName] !== undefined) { + return env[envVarName] + } else { + console.error( + `[LavaMoat] Environment variable "${envVarName}" referenced in config but not found in environment` + ) + } + } + return value + } + + for (const key of ['--allow-fs-read', '--allow-fs-write']) { + if (Array.isArray(configOptions[key])) { + configOptions[key] = configOptions[key].map(replaceEnvVar) + } + } + + // 2. tmp write - crossplatform + if (configOptions['--allow-fs-tmp'] === true) { + delete configOptions['--allow-fs-tmp'] + if (configOptions['--allow-fs-write']) { + if (typeof configOptions['--allow-fs-write'] === 'string') { + configOptions['--allow-fs-write'] = [ + configOptions['--allow-fs-write'], + ] + } + if (configOptions['--allow-fs-write'] === true) { + return // none of this matters + } + } else { + // do this for both undefined and false + configOptions['--allow-fs-write'] = [] + } + const tmp = tmpdir() + configOptions['--allow-fs-write'].push(tmp) + // because macos is being weird + const tmpRealPath = realpathSync(tmp) + if (tmpRealPath !== tmp) { + configOptions['--allow-fs-write'].push(tmpRealPath) + } + } + } + + /** @param {ConfigOptions} configOptions */ + function makeFlagsFromConfig(configOptions) { + return Object.entries(configOptions) + .map(([arg, value]) => { + if (typeof value === 'boolean') { + return value ? arg : '' + } else if (Array.isArray(value)) { + return value.map((v) => `${arg}="${v}"`).join(' ') + } else { + return `${arg}="${value}"` + } + }) + .filter(Boolean) + .join(' ') + } + + /** + * Checks the config obtained from package.json and puts it in as NODE_OPTIONS + * + * @param {string | undefined} existingOptions + * @param {ConfigOptions} configOptions + * @param {NodeJS.ProcessEnv} env + */ + function installNodeOptions(existingOptions, configOptions, env) { + if (!configOptions) { + return existingOptions || '' + } + + customizePermissionsConfig(configOptions, env) + + permissionsModelCompatibilityExtensions(configOptions, env) + + const confOption = makeFlagsFromConfig(configOptions) + + return confOption.trim() + } + + /** + * Filter environment variables based on ban keywords + * + * @param {NodeJS.ProcessEnv} env + * @param {string} lavamoatDir + * @returns {NodeJS.ProcessEnv} + */ + function filterEnv(env, lavamoatDir) { + const banFilePath = pathJoin(lavamoatDir, '.env.ban.json') + let banConfig + + try { + banConfig = readJsonFile(banFilePath) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + console.error( + `[LavaMoat] Warning: Failed to read .env.ban.json: ${message}.` + ) + return env + } + let banKeywords = [] + try { + if (Array.isArray(banConfig)) { + banKeywords.push(...banConfig) + } else { + throw Error(`Expected .env.ban.json to contain an array of keywords`) + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + console.error( + `[LavaMoat] Warning: Failed to read .env.ban.json: ${message}.` + ) + return env + } + + /** @type {NodeJS.ProcessEnv} */ + const filteredEnv = {} + const bannedEnv = [] + banKeywords = banKeywords.map((keyword) => keyword.toLowerCase()) + for (const [key, value] of Object.entries(env)) { + if ( + !key.toLowerCase().startsWith('npm_config') && + banKeywords.some((keyword) => key.toLowerCase().includes(keyword)) + ) { + bannedEnv.push(key) + } else { + filteredEnv[key] = value + } + } + if (bannedEnv.length > 0) { + console.error( + `[LavaMoat] Warning: The following environment variables were banned: ${bannedEnv.join(', ')}` + ) + } + return filteredEnv + } + + /** @param {string} PATH */ + function envPathOpinions(PATH) { + const pathFragments = PATH.split(pathDelimiter) + // This is to eliminate bin confusion attacks. + // Find node_modules/.bin and remove it, put it on the end that gets looked up last when looking for a name in the path. + + /** @type {string[]} */ + const filteredFragments = [] + /** @type {string[]} */ + const nodeModulesBinFragments = [] + + for (const fragment of pathFragments) { + if (pathBinMatcher(fragment)) { + nodeModulesBinFragments.push(fragment) + } else { + filteredFragments.push(fragment) + } + } + // Why would there be multiple bin fragments? In a npm workspace, local bin and workspace root bin is added + filteredFragments.push(...nodeModulesBinFragments) + return filteredFragments.join(pathDelimiter) + } + + return { + processEnv: (existingEnv) => { + const scriptsConfig = readScriptsConfig(projectRoot) + const config = readConfig({ + scriptsConfig, + scriptName, + projectRoot, + }) + + // Smell: Windows environment variables are case-insensitive, but Node's process.env + // might expose it as 'Path' instead of 'PATH'. Checking both ensures it doesn't get wiped out. + + const existingPath = existingEnv.PATH || existingEnv.Path || '' + + const lavamoatDir = pathJoin(projectRoot, 'lavamoat') + + const fixedEnv = { + ...filterEnv(existingEnv, lavamoatDir), + PATH: envPathOpinions(existingPath), + NODE_OPTIONS: installNodeOptions( + existingEnv.NODE_OPTIONS, + config.nodeOptions, + existingEnv + ), + } + return fixedEnv + }, + } +} + + diff --git a/lavamoat/plugin-allow-scripts.js b/lavamoat/plugin-allow-scripts.js new file mode 100644 index 0000000..fbd0f01 --- /dev/null +++ b/lavamoat/plugin-allow-scripts.js @@ -0,0 +1,17 @@ +//prettier-ignore +module.exports = { +name: "@yarnpkg/plugin-allow-scripts", +factory: function (/** @type {(arg0: string) => { execute: any; }} */ require) { + const { execute } = require(`@yarnpkg/shell`); + return { + hooks: { + afterAllInstalled: async () => { + const exitCode = await execute('yarn run allow-scripts') + if (exitCode !== 0) { + process.exit(exitCode) + } + }, + }, + } + } +}; diff --git a/lavamoat/scripts.lint.json b/lavamoat/scripts.lint.json new file mode 100644 index 0000000..13d43fc --- /dev/null +++ b/lavamoat/scripts.lint.json @@ -0,0 +1,15 @@ +{ + "notes": "Denies all powerful IO capabilities that a linter script should not need. If a script needs more permissions, copy this file and edit. Do not allow more for all other linter scripts.", + "nodeOptions": { + "--disable-warning": "SecurityWarning", + "--permission": true, + "--allow-fs-read": ["./"], + "--allow-fs-write": ["./"], + "--allow-child-process": true, + "--allow-net": false, + "--allow-worker": false, + "--allow-addons": true, + "--allow-wasi": false, + "--allow-inspector": false + } +} diff --git a/lavamoat/scripts.loose.json b/lavamoat/scripts.loose.json new file mode 100644 index 0000000..35609b1 --- /dev/null +++ b/lavamoat/scripts.loose.json @@ -0,0 +1,15 @@ +{ + "notes": "Should prevent basic malicious behavior of writing to locations outside the project and sending network requests, but not break any of the advanced packages in use. Everything below the net permission can be used to bypass the restrictions by a more sophisticated attacker.", + "nodeOptions": { + "--disable-warning": "SecurityWarning", + "--permission": true, + "--allow-fs-read": ["/"], + "--allow-fs-write": ["./"], + "--allow-net": false, + "--allow-child-process": true, + "--allow-worker": true, + "--allow-addons": true, + "--allow-wasi": true, + "--allow-inspector": false + } +} diff --git a/lavamoat/scripts.strict.json b/lavamoat/scripts.strict.json new file mode 100644 index 0000000..3e381f4 --- /dev/null +++ b/lavamoat/scripts.strict.json @@ -0,0 +1,15 @@ +{ + "notes": "Denies all powerful IO capabilities. This provides a strict default and if you need a script to have more permissions, choose another scripts.*.json file instead of editing this one.", + "nodeOptions": { + "--disable-warning": "SecurityWarning", + "--permission": true, + "--allow-fs-read": ["./"], + "--allow-fs-write": ["./"], + "--allow-child-process": false, + "--allow-net": false, + "--allow-worker": false, + "--allow-addons": false, + "--allow-wasi": false, + "--allow-inspector": false + } +} diff --git a/lavamoat/scripts.test.json b/lavamoat/scripts.test.json new file mode 100644 index 0000000..52e20e5 --- /dev/null +++ b/lavamoat/scripts.test.json @@ -0,0 +1,15 @@ +{ + "notes": "Should prevent basic malicious behavior of writing to locations outside the project and sending network requests, but not break any of the advanced packages in use. Everything below the net permission can be used to bypass the restrictions by a more sophisticated attacker.", + "nodeOptions": { + "--disable-warning": "SecurityWarning", + "--permission": true, + "--allow-fs-read": ["/"], + "--allow-fs-write": ["./", "$GITHUB_STEP_SUMMARY"], + "--allow-net": false, + "--allow-child-process": true, + "--allow-worker": false, + "--allow-addons": true, + "--allow-wasi": false, + "--allow-inspector": false + } +} diff --git a/lavamoat/scripts.yarn.json b/lavamoat/scripts.yarn.json new file mode 100644 index 0000000..9ac9e44 --- /dev/null +++ b/lavamoat/scripts.yarn.json @@ -0,0 +1,15 @@ +{ + "notes": "Use this one ONLY for scripts that are only creating nested Yarn processes, e.g., a composition of multiple other scripts via `yarn something && yarn something-else`.", + "nodeOptions": { + "--disable-warning": "SecurityWarning", + "--permission": true, + "--allow-fs-read": ["/"], + "--allow-fs-write": ["./"], + "--allow-child-process": true, + "--allow-net": false, + "--allow-worker": false, + "--allow-addons": false, + "--allow-wasi": false, + "--allow-inspector": false + } +} diff --git a/package.json b/package.json index 305f7d6..32a7c7e 100644 --- a/package.json +++ b/package.json @@ -40,20 +40,24 @@ "lint": "yarn lint:eslint && yarn lint:constraints && yarn lint:misc --check && yarn lint:dependencies --check && yarn lint:changelog", "lint:changelog": "auto-changelog validate --formatter oxfmt", "lint:constraints": "yarn constraints", - "lint:dependencies": "depcheck && yarn dedupe --check", - "lint:dependencies:fix": "depcheck && yarn dedupe", + "lint:dependencies": "yarn lint:dependencies:depcheck && yarn lint:dependencies:dedupe --check", + "lint:dependencies:depcheck": "depcheck", + "lint:dependencies:dedupe": "yarn dedupe", + "lint:dependencies:fix": "yarn lint:dependencies:depcheck && yarn lint:dependencies:dedupe", "lint:eslint": "eslint .", "lint:fix": "yarn lint:eslint --fix && yarn lint:constraints --fix && yarn lint:misc --write && yarn lint:dependencies:fix && yarn lint:changelog --fix", "lint:misc": "oxfmt --ignore-path .gitignore", "prepack": "./scripts/prepack.sh", - "test": "vitest && attw --pack", - "test:watch": "vitest --watch" + "test": "yarn test:vitest && yarn test:package", + "test:vitest": "vitest", + "test:watch": "yarn test:vitest --watch", + "test:package": "yarn pack --out .yarn/package.tgz && attw .yarn/package.tgz" }, "devDependencies": { "@arethetypeswrong/cli": "^0.15.3", "@lavamoat/allow-scripts": "^5.1.0", "@lavamoat/preinstall-always-fail": "^2.0.0", - "@metamask/auto-changelog": "^6.1.0", + "@metamask/auto-changelog": "^6.2.1", "@metamask/eslint-config": "^15.0.0", "@metamask/eslint-config-nodejs": "^15.0.0", "@metamask/eslint-config-typescript": "^15.0.0", @@ -84,6 +88,13 @@ "resolutions": { "fflate": "0.8.2" }, + "devEngines": { + "packageManager": { + "name": "yarn", + "version": ">=4.16.0+sha256.ba05224324578801b9cc98170d64aa50b9a36733b440fb0942306da3fbbdc7d1", + "onFail": "error" + } + }, "engines": { "node": "^22 || ^24 || >=26" }, @@ -93,5 +104,22 @@ "@lavamoat/preinstall-always-fail": false, "eslint-plugin-import-x>unrs-resolver": false } + }, + "scriptsConfig": { + "#default": "lavamoat/scripts.strict.json", + "lint": "lavamoat/scripts.yarn.json", + "lint:changelog": "lavamoat/scripts.lint.json", + "lint:constraints": "lavamoat/scripts.yarn.json", + "lint:dependencies": "lavamoat/scripts.yarn.json", + "lint:dependencies:fix": "lavamoat/scripts.yarn.json", + "lint:dependencies:dedupe": "lavamoat/scripts.yarn.json", + "lint:eslint": "lavamoat/scripts.lint.json", + "lint:fix": "lavamoat/scripts.yarn.json", + "lint:misc": "lavamoat/scripts.lint.json", + "prepack": "lavamoat/scripts.yarn.json", + "test": "lavamoat/scripts.yarn.json", + "test:vitest": "lavamoat/scripts.test.json", + "test:package": "lavamoat/scripts.test.json", + "test:watch": "lavamoat/scripts.test.json" } } diff --git a/yarn.lock b/yarn.lock index a403e4f..0dec19c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -519,9 +519,9 @@ __metadata: languageName: node linkType: hard -"@metamask/auto-changelog@npm:^6.1.0": - version: 6.1.0 - resolution: "@metamask/auto-changelog@npm:6.1.0" +"@metamask/auto-changelog@npm:^6.2.1": + version: 6.2.1 + resolution: "@metamask/auto-changelog@npm:6.2.1" dependencies: "@octokit/rest": "npm:^20.0.0" diff: "npm:^5.0.0" @@ -538,7 +538,7 @@ __metadata: optional: true bin: auto-changelog: dist/cli.mjs - checksum: 10/d4b086ea609f1395e111d8d124d74ac94e31a872f26eba5b65906f6e634f61e328264c940e7a603cb823d58a7140f8eeafec4521b85ee6584917e2c5ac90b684 + checksum: 10/88f5bc63ef5003b3e4f88ebf018229ce5e701283cadee1dcbd37f869541a1102ba6a8a96fd54993863e3578c8b79f0c6e0242e4afd08c58d5db529bc75dbaf2c languageName: node linkType: hard @@ -612,7 +612,7 @@ __metadata: "@arethetypeswrong/cli": "npm:^0.15.3" "@lavamoat/allow-scripts": "npm:^5.1.0" "@lavamoat/preinstall-always-fail": "npm:^2.0.0" - "@metamask/auto-changelog": "npm:^6.1.0" + "@metamask/auto-changelog": "npm:^6.2.1" "@metamask/eslint-config": "npm:^15.0.0" "@metamask/eslint-config-nodejs": "npm:^15.0.0" "@metamask/eslint-config-typescript": "npm:^15.0.0" @@ -2538,9 +2538,9 @@ __metadata: linkType: hard "diff@npm:^5.0.0": - version: 5.0.0 - resolution: "diff@npm:5.0.0" - checksum: 10/4a179a75b17cbb420eb9145be913f9ddb34b47cb2ba4301e80ae745122826a468f02ca8f5e56945958de26ace594899c8381acb6659c88e7803ef078b53d690c + version: 5.2.2 + resolution: "diff@npm:5.2.2" + checksum: 10/8a885b38113d96138d87f6cb474ee959b7e9ab33c0c4cb1b07dcf019ec544945a2309d53d721532af020de4b3a58fb89f1026f64f42f9421aa9c3ae48a36998b languageName: node linkType: hard @@ -5418,15 +5418,15 @@ __metadata: linkType: hard "tar@npm:^7.5.4": - version: 7.5.22 - resolution: "tar@npm:7.5.22" + version: 7.5.20 + resolution: "tar@npm:7.5.20" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10/129606384b3c895f422aaca48bf316357be6dc7aa35c1066c3f06c28acfff0be5b356e1b0a0be7c53a7a8945534ac06cec6d8e296d170d8a09c8bff67b29300e + checksum: 10/90a0fe423ac921197ad5eefc5e5f7ad7f42b06e80444c8c347c1e4112384cbe9cb53ade3ef71748eed3684888756869c2aeef6b6303c766101f3217e254d4be9 languageName: node linkType: hard @@ -6118,11 +6118,11 @@ __metadata: linkType: hard "yaml@npm:^2.5.1": - version: 2.6.0 - resolution: "yaml@npm:2.6.0" + version: 2.9.0 + resolution: "yaml@npm:2.9.0" bin: yaml: bin.mjs - checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 + checksum: 10/9a95e8e08651c3d292ab6a5befeb5f57b76801caa097c75bb45c9a70ce19c1b11f57e87a6ef84a579ea070ed2c2c8ac541c88c0ae684d544d5f42c7e77d11b7b languageName: node linkType: hard