Skip to content

chore: apply @lavamoat/harden defaults - #335

Merged
Mrtenz merged 19 commits into
mainfrom
mrtenz/harden
Aug 19, 2026
Merged

chore: apply @lavamoat/harden defaults#335
Mrtenz merged 19 commits into
mainfrom
mrtenz/harden

Conversation

@Mrtenz

@Mrtenz Mrtenz commented Jul 16, 2026

Copy link
Copy Markdown
Member

This implements the @lavamoat/harden defaults using yarn dlx @lavamoat/harden defaults --level strict --package-manager yarn. Changed include:

  • The allow-scripts plugin was replaced with a more readable (unminified) version.
  • A new runner plugin was added, which intercepts all package.json script calls and enables Node.js permissions.
    • The permissions are configured in the lavamoat/scripts.*.json files and the scriptsConfig field 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 run scripts 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/plugins is removed in favor of local plugins: lavamoat/plugin-allow-scripts.js (still runs yarn run allow-scripts after install) and lavamoat/.runner-plugin.js, which hooks wrapScriptExecution for workspace scripts.

The runner loads per-script permission JSON from package.json scriptsConfig (default lavamoat/scripts.strict.json, with lint, test:vitest, etc. mapped to scripts.lint.json, scripts.test.json, scripts.yarn.json, and related profiles). It applies those options via NODE_OPTIONS, strips env vars matching keywords in lavamoat/.env.ban.json, and reorders PATH so node_modules/.bin is resolved last.

Supporting tweaks: split test / lint:dependencies into sub-scripts so each can get its own profile; test:package packs to .yarn/package.tgz for attw; add devEngines for Yarn 4.16+; ignore lavamoat/ in ESLint/oxfmt; bump @metamask/auto-changelog and lockfile checksums.

Reviewed by Cursor Bugbot for commit 228265e. Bugbot is set up for automated code reviews on this repo. Configure here.

@socket-security

socket-security Bot commented Jul 16, 2026

Copy link
Copy Markdown

Warning

MetaMask internal reviewing guidelines:

  • Do not ignore-all
  • Each alert has instructions on how to review if you don't know what it means. If lost, ask your Security Liaison or the supply-chain group
  • Copy-paste ignore lines for specific packages or a group of one kind with a note on what research you did to deem it safe.
    @SocketSecurity ignore npm/PACKAGE@VERSION
Action Severity Alert  (click "▶" to expand/collapse)
Warn Low
Potential code anomaly (AI signal): npm tar is 66.0% likely to have a medium risk anomaly

Notes: This module acts as a standard tar extraction wrapper using synchronous and asynchronous code paths. There is no evident malicious activity within this fragment. Security risk hinges on the behavior of the Unpack/UnpackSync implementation and how tar entries are written to disk (e.g., path traversal). No hardcoded secrets or network calls are present here. Recommend ensuring tar extraction handles path traversal and destination path sanitization in Unpack, and consider validating opt.file presence and type before streaming.

Confidence: 0.66

Severity: 0.56

From: ?npm/@lavamoat/allow-scripts@5.1.0npm/tar@7.5.20

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/tar@7.5.20. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm yaml is 68.0% likely to have a medium risk anomaly

Notes: The code presents a standard, non-malicious NodeBase component used in YAML/JS conversion. The primary risk surface is the optional reviver and onAnchor callbacks provided by the user: if untrusted, these can execute arbitrary code or influence the transformed representation via applyReviver or the reviver itself. This is expected behavior for extensible YAML libraries; ensure callbacks come from trusted sources and sandbox or validate revivers where possible.

Confidence: 0.68

Severity: 0.60

From: ?npm/typedoc@0.26.11npm/yaml@2.9.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/yaml@2.9.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Comment thread .yarnrc.yml Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can now remove the old version of plugin-allow-scripts. The new one is the exact same functionality but readable.

Comment thread package.json Outdated
Comment thread package.json Outdated
}
},
"scriptsConfig": {
"#default": "lavamoat/scripts.loose.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add some stronger policies for specific scripts here.
I'll give it a go later.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you prefer for this file to also strt with a dot? I'd consider it an inconsistency on my side.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@socket-security

socket-security Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​metamask/​auto-changelog@​6.1.0 ⏵ 6.2.199 +3100100 +196 +4100

View full report

Comment thread lavamoat/scripts.lint.json Outdated
Co-authored-by: Zbyszek Tenerowicz <naugtur@gmail.com>
Comment thread lavamoat/scripts.strict.json Outdated
Comment thread lavamoat/scripts.yarn.json Outdated
Comment thread package.json
"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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread package.json
"eslint-plugin-import-x>unrs-resolver": false
}
},
"scriptsConfig": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the field is named scriptsConfig so that it lands after scripts when ordered alphabetically, so it's hard to miss the relationship ;)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's oxfmt reformatting the package.json automatically 😅. It places unknown fields at the bottom.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think I prefer scriptsConfig over that 🤔

naugtur
naugtur previously approved these changes Aug 18, 2026
@Mrtenz
Mrtenz marked this pull request as ready for review August 19, 2026 10:10
@Mrtenz
Mrtenz requested a review from a team as a code owner August 19, 2026 10:10
Comment thread .yarnrc.yml
Comment on lines +13 to +14
# Disable Yarn's mirror feature to avoid writing to the global cache.
enableMirror: false

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .yarnrc.yml
Comment on lines +20 to +22
# Despite `enableGlobalCache` and `enableMirror` being `false`, Yarn is still
# writing to the global folder.
globalFolder: .yarn/global

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread package.json
Comment on lines +43 to +46
"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",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread package.json
"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"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attw --pack internally calls npm pack which doesn't work with devEngines below. It blocks NPM from running completely.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread package.json
projectRoot: extra.cwd,
pathBinMatcher: (fragment) => {
return fragment.endsWith(binFolder)
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c93e854. Configure here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin is added by @lavamoat/harden.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin is added by @lavamoat/harden.

Comment thread package.json
"@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",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we keep these in sync now that they are just local copies?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I validate the contents of this one? 😄

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lavamoat/.env.ban.json
@@ -0,0 +1 @@
["SESSION", "SSH", "KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit like a footgun tbh

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that we need this to "glue" scripts together makes me worried that we accidentally give this permission to something we don't expect

Comment thread lavamoat/scripts.yarn.json
Comment thread .depcheckrc.json
@Mrtenz
Mrtenz merged commit f20b5e7 into main Aug 19, 2026
28 checks passed
@Mrtenz
Mrtenz deleted the mrtenz/harden branch August 19, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants