chore: apply @lavamoat/harden defaults - #335
Conversation
|
Warning MetaMask internal reviewing guidelines:
|
| 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' | ||
| spec: https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js |
There was a problem hiding this comment.
you can now remove the old version of plugin-allow-scripts. The new one is the exact same functionality but readable.
| } | ||
| }, | ||
| "scriptsConfig": { | ||
| "#default": "lavamoat/scripts.loose.json" |
There was a problem hiding this comment.
we could add some stronger policies for specific scripts here.
I'll give it a go later.
There was a problem hiding this comment.
Would you prefer for this file to also strt with a dot? I'd consider it an inconsistency on my side.
There was a problem hiding this comment.
I don't think runner-plugin needs to start with a dot, since it's already inside a separate lavamoat folder. But either way is fine with me.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Co-authored-by: Zbyszek Tenerowicz <naugtur@gmail.com>
| "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", |
There was a problem hiding this comment.
looking at this gives me a feature idea.
"scriptsConfig": {
"lint": "lavamoat/scripts.yarn.json",
"lint:*": "lavamoat/scripts.lint.json",
"lint:constraints": "lavamoat/scripts.yarn.json",lint:* would cover any beginsWith matches that don't have an exact match.
That would save us a few lines while not introducing more complex syntax for one-to-many matching.
| "eslint-plugin-import-x>unrs-resolver": false | ||
| } | ||
| }, | ||
| "scriptsConfig": { |
There was a problem hiding this comment.
the field is named scriptsConfig so that it lands after scripts when ordered alphabetically, so it's hard to miss the relationship ;)
There was a problem hiding this comment.
That's oxfmt reformatting the package.json automatically 😅. It places unknown fields at the bottom.
There was a problem hiding this comment.
I didn't anticipate that one. Gotta hijack an existing field :P
Also, made me think - since we're in control of the script string being passed around too, I could accept the config name with a separator at the beginning of the script.
"scripts": {
"lint": "{lavamoat/scripts.lint.json} eslint"I like the way it looks. It does make the default a little weird. And eliminates the ability to do prefixes for groups from the comment above.
There was a problem hiding this comment.
Hmm, I think I prefer scriptsConfig over that 🤔
| # Disable Yarn's mirror feature to avoid writing to the global cache. | ||
| enableMirror: false |
There was a problem hiding this comment.
Even though we disable the global cache above, Yarn still mirrors the local cache to the global cache (meaning it's still writing there), which is disallowed by the runner plugin in certain cases, e.g., when running yarn dedupe in a script.
| # Despite `enableGlobalCache` and `enableMirror` being `false`, Yarn is still | ||
| # writing to the global folder. | ||
| globalFolder: .yarn/global |
There was a problem hiding this comment.
Yarn always instantiates the PNPM linker class (I think?), which does some setup in the constructor including creating folders in the Yarn global folder. To get around this, the global folder is now stored locally.
| "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", |
There was a problem hiding this comment.
This split of scripts makes it possible to control permissions more granularly. All "orchestrator" scripts (scripts that only call other scripts like "lint", "lint:dependencies", "test") are set to use the "scripts.yarn.json" config, and all others use a more specific config.
| "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" |
There was a problem hiding this comment.
attw --pack internally calls npm pack which doesn't work with devEngines below. It blocks NPM from running completely.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c93e854. Configure here.
| projectRoot: extra.cwd, | ||
| pathBinMatcher: (fragment) => { | ||
| return fragment.endsWith(binFolder) | ||
| }, |
There was a problem hiding this comment.
PATH reorder targets Yarn bin folder
Medium Severity
pathBinMatcher prefers BERRY_BIN_FOLDER over node_modules/.bin. Yarn always sets that temp folder during script runs, so envPathOpinions moves Yarn's shims to the end of PATH instead of untrusted project bins. That can pick a global binary and does not mitigate bin-confusion attacks.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c93e854. Configure here.
There was a problem hiding this comment.
In my tests BERRY_BIN_FOLDER is the only path yarn adds to $PATH. So dependencies trying to run code via bin confusion could only be there.
Explain how node_modules/.bin would end up in PATH to make this a valid concern.
There was a problem hiding this comment.
This plugin is added by @lavamoat/harden.
There was a problem hiding this comment.
This plugin is added by @lavamoat/harden.
| "@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", |
There was a problem hiding this comment.
Versions before 6.2.1 broke under the runner plugin because the binary didn't contain a shebang.
| * @param {any} require - Yarn's require function | ||
| * @returns {{ hooks: { wrapScriptExecution: Function } }} | ||
| */ | ||
| module.exports = { |
There was a problem hiding this comment.
How do we keep these in sync now that they are just local copies?
There was a problem hiding this comment.
I don't think there's a good way to keep Yarn plugins in sync in general 😅. @lavamoat/harden has a check command which we can implement, but have to think about how to do it in a way that we don't break random PRs.
There was a problem hiding this comment.
How can I validate the contents of this one? 😄
There was a problem hiding this comment.
You can run yarn dlx @lavamoat/harden defaults --level strict --package-manager yarn locally and diff. Currently there will be a one line difference to fix a bug we found. Zb is working on upstreaming it.
| @@ -0,0 +1 @@ | |||
| ["SESSION", "SSH", "KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"] | |||
There was a problem hiding this comment.
These are environment variables that are removed before running a script based on partial key match I believe.
| @@ -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`.", | |||
There was a problem hiding this comment.
This seems a bit like a footgun tbh
There was a problem hiding this comment.
Just that we need this to "glue" scripts together makes me worried that we accidentally give this permission to something we don't expect


This implements the
@lavamoat/hardendefaults usingyarn dlx @lavamoat/harden defaults --level strict --package-manager yarn. Changed include:lavamoat/scripts.*.jsonfiles and thescriptsConfigfield in package.json.Note
Medium Risk
Changes how every workspace npm script executes (env, PATH, Node permissions) and install-time Yarn behavior; misconfigured profiles could break CI or local dev, but scope is repo tooling rather than runtime product code.
Overview
Applies @lavamoat/harden strict defaults so
yarn runscripts run under Node’s permission model, with Yarn install hardening alongside existing allow-scripts controls.Yarn (
.yarnrc.yml) turns on hardened mode, disables mirroring, and routes the global folder to.yarn/global. The bundled allow-scripts plugin under.yarn/pluginsis removed in favor of local plugins:lavamoat/plugin-allow-scripts.js(still runsyarn run allow-scriptsafter install) andlavamoat/.runner-plugin.js, which hookswrapScriptExecutionfor workspace scripts.The runner loads per-script permission JSON from
package.jsonscriptsConfig(defaultlavamoat/scripts.strict.json, withlint,test:vitest, etc. mapped toscripts.lint.json,scripts.test.json,scripts.yarn.json, and related profiles). It applies those options viaNODE_OPTIONS, strips env vars matching keywords inlavamoat/.env.ban.json, and reordersPATHsonode_modules/.binis resolved last.Supporting tweaks: split
test/lint:dependenciesinto sub-scripts so each can get its own profile;test:packagepacks to.yarn/package.tgzforattw; adddevEnginesfor Yarn 4.16+; ignorelavamoat/in ESLint/oxfmt; bump@metamask/auto-changelogand lockfile checksums.Reviewed by Cursor Bugbot for commit 228265e. Bugbot is set up for automated code reviews on this repo. Configure here.