Skip to content

Start compartment globalThis scuttling (browserify only) #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Sep 19, 2022

Conversation

weizman
Copy link
Member

@weizman weizman commented Aug 8, 2022

This PR introduces @kumavis's idea for scuttling the globalThis object of the start compartment in LavaMoat.

Motivation

Since each package LavaMoat wraps has its own compartment and own set of intrinsics (including the app that is protected which is referred to as root package), this effectively means the real globalThis object is out of use and serves almost exclusively attackers only.

In other words, the main app has access to everything the globalThis object has to offer, but it goes through LavaMoat to do so which provides everything from the compartment - not the actual globalThis object.

However, if a package manages to access the real globalThis object which no code in the app really uses, it has access to sensitive APIs it shouldn't have.

Based on how this serves no purpose other then a malicious one, it would make sense to offer as a configurable feature in LavaMoat the option to disarm the globalThis object completely, by redefining all of its properties to not return their real values (aka scuttling).

Implementation

So when scuttling is turned on, any code that runs within the app and manages to access the globalThis object, will encounter a thrown error when trying to access anything it offers:

Screen Shot 2022-09-18 at 15 01 20

Issues

If you encounter an unexpected issue when running an app under scuttling mode, please let us know, that should not be the case.

If the app is trying to access a property that it should have access to, you can easily fix that by adding it to the lavamoatOpts.scuttleGlobalThisExceptions config property (see usage section below)

Many have reported issues with WebDriver manipulation of MM when this feature is on, please refer to my comment below for further info on this

Support

Currently this feature is only usable for browser based apps built with browserify
With #391 node/core/browserify support scuttling

Usage

By adding the scuttleGlobalThis=true config property, scuttling can be turned on (see here)

Optionally, you can also set scuttleGlobalThisException=[] property to skip certain props from being scuttled.

const lavamoatOpts = {

  // turn scuttling off
  scuttleGlobalThis: false, // true,

  // set scuttle exceptions
  scuttleGlobalThisException: [], // ['fetch', 'alert'],
}

WIP

Some later related PRs:

@@ -72,6 +72,9 @@
const rootPackageName = '$root$'
const rootPackageCompartment = createRootPackageCompartment(globalRef)

// scuttle globalThis right after we used it to create the root package compartment
scuttleGlobalThis()
Copy link
Member

Choose a reason for hiding this comment

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

Feels like we'd want this to be configurable in the final version

Copy link
Member Author

Choose a reason for hiding this comment

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

https://github.com/LavaMoat/LavaMoat/pull/376/files
(ignore 'runtime.js` was added by mistake)

Copy link

Choose a reason for hiding this comment

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

Hey I've tried disabling scuttling and it seems to work the first time. However, when running it with an automated software after that, the metamask extention does not seem to be able to load. Is there anything else that needs to be changed apart from:

await lavapack.buildRuntime({
    scuttleGlobalThis: {
      enabled: false,
      scuttlerName: 'SCUTTLER',
      exceptions: scuttleGlobalThisExceptions,
    },
  });
}

Is there a need to change/modify the exceptions param?


for (const prop of props) {
if (!avoids.includes(prop)) {
Object.defineProperty(window, prop, {value: undefined})
Copy link
Member

Choose a reason for hiding this comment

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

How about a getter that throws with helpful information?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

also, for scuttling to work, lockdown umd must access the new scoped version of Error, instead of directly via globalThis, so had to fix that too 7601883
is this how we update lockdown umd? should it be updated also elsewhere?

@weizman weizman changed the title [DRAFT] poc of globalThis start compartment scutteling Start compartment globalThis scuttling (browserify only) Sep 18, 2022
@weizman weizman marked this pull request as ready for review September 18, 2022 11:48
@weizman weizman requested a review from naugtur September 18, 2022 12:38
@@ -3027,7 +3027,7 @@ const tagError = (err, optErrorName = err.name) => {
*/
const makeError = (
optDetails = redactedDetails`Assert failed`,
ErrorConstructor = globalThis.Error,
ErrorConstructor = Error,
Copy link
Member

Choose a reason for hiding this comment

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

You can't modify this. It's a vendored SES build

Copy link
Member Author

Choose a reason for hiding this comment

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

yea i was asking about it here

what are my options then? can we change that at SES? any reason for having to use globalThis.Error instead of just Error?

Copy link
Member Author

Choose a reason for hiding this comment

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

as agreed with @naugtur,

undo any changes made to this file 5ebd45b and avoid scuttling Error 785b4a7

}
if (globalThis?.EventTarget?.prototype) {
props = [...props, ...Object.getOwnPropertyNames(EventTarget.prototype)]
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if the browser optimizes this. Probably not. It might be more efficient to collect the properties inside ifs into some variables and then avoiding multiple spreads on props.

Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -82,6 +90,43 @@
Object.freeze(kernel)
return kernel

function scuttleGlobalThis(extraAvoids = new Array()) {
let props = [...Object.getOwnPropertyNames(globalThis)]
Copy link
Member

Choose a reason for hiding this comment

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

Why copy the result of getOwnPropertyNames? it's not like something else is using this reference

Copy link
Member Author

Choose a reason for hiding this comment

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

@github-actions github-actions bot mentioned this pull request Aug 17, 2023
@henzycuong1
Copy link

mark: image ==>> image

hi, my metamask has been corrupted after change that. how it work ?

@cdoky
Copy link

cdoky commented Sep 3, 2023

@henzycuong1 What happend with you

@henzycuong1
Copy link

What happend with you

sorry, it worked. But i have to reinstall my metamask, that caused the extension's id be changed. Anyway i can fix it on old metamask extension or keep the extension's id is "nkbihfbeogaeaoehlefnkodbefgpgknn"

@jhxiyu
Copy link

jhxiyu commented Mar 3, 2024

sentry-install.js:1 Property eval will not be hardened because it is scuttled by LavaMoat protection. Visit #360 to learn more.
e. @ sentry-install.js:1
sentry-install.js:1 Translator - Unable to find value of key "buyAndSell" for locale "zh_CN"
e. @ sentry-install.js:1
popup.html#confirmation:1 [Intervention] Slow network is detected. See https://www.chromestatus.com/feature/5636954674692096 for more details. Fallback font will be used while loading: chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf
8Framework Event Listeners API Errors:
devtoolsFrameworkEventListeners call produced error: Error: LavaMoat - property "self" of globalThis is inaccessible under scuttling mode. To learn more visit #360.
fetcher call produced error: Error: LavaMoat - property "Node" of globalThis is inaccessible under scuttling mode. To learn more visit #360
e. @ sentry-install.js:1
sentry-install.js:1 Translator - Unable to find value of key "buyAndSell" for locale "zh_CN"
e. @ sentry-install.js:1
popup.html#confirmation:1 [Intervention] Slow network is detected. See https://www.chromestatus.com/feature/5636954674692096 for more details. Fallback font will be used while loading: chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/fonts/Euclid/EuclidCircularB-Regular-WebXL.ttf

@iniyavans
Copy link

iniyavans commented Mar 23, 2024

It works only after changing the value of 'scuttleGlobalThis' to enable status as true from false.
image

danjm added a commit to MetaMask/metamask-extension that referenced this pull request Apr 18, 2024
## **Description**

When loading a prod mv3 build, we observed that a majority of the time,
the offscreen document would not be created. In those case, we saw the
following error in the service worker console:

```
runtime-lavamoat.js:13190
Uncaught Error: LavaMoat - property "chrome" of globalThis is inaccessible under scuttling mode. To learn more visit LavaMoat/LavaMoat#360.
  at Object.get (runtime-lavamoat.js:13190:11)
  at app-init.js:1:1766
get	@	runtime-lavamoat.js:13190
(anonymous)	@	app-init.js:1
```

Which comes from this code in `app-init.js`:
```
async function createOffscreen() {
  if (await chrome.offscreen.hasDocument()) {
    return;
  }

  await chrome.offscreen.createDocument({
    url: './offscreen.html',
    reasons: ['IFRAME_SCRIPTING'],
    justification:
      'Used for Hardware Wallet and Snaps scripts to communicate with the extension.',
  });

  console.debug('Offscreen iframe loaded');
}
```

The solution is to cache a version of chrome before it has been
scuttled.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/24103?quickstart=1)

## **Related issues**

Fixes:

## **Manual testing steps**

1. `yarn dist:mv3`
2. Install at chrome://extensions
3. A second or so after the service worker is created, the offscreen
document should be created.
4. Reload the extension 10 times, the offscreen document should be
created every time

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
seaona referenced this pull request in MetaMask/metamask-extension Jul 5, 2024
… on three confirmations concurrently` (#25675)

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**
The queue UI test fails has a couple of flaky points.

Sometimes we see the console errors for network unresponsive -- when
this happens the test fails.
When we don't see the console errors for network unresponsive the test
passes. The problem is that in the tests we were ignoring the
PollingBlock errors, but more errors appear when the network is down,
making the test fail when they appear in the console.

![Screenshot from 2024-07-05
14-45-37](https://github.com/MetaMask/metamask-extension/assets/54408225/4f105322-3bd0-4908-a107-8984fce1915d)


https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/90602/workflows/e41e1e65-d445-4b60-9602-89a261cc5654/jobs/3362082/parallel-runs/4?filterBy=ALL

There are more sources of flakiness, that can also cause this test to
fail. Those are described here and fixed in this same PR:

- Performing a subsequent action when the popup is closed, causes the
`NoSuchWindowError: no such window: target window already closed` error.
We need to switch back to an existing window, before proceeding with
test actions
- Triggering a tx/signature with an unresponsive network (when we kill
ganache) takes a significant amount of time to open the popup. Now we
wait for the expected number of windows before switching to the popup
and we add a big timeout for this case (it's a max value and won't delay
the test if it's not needed, like in FF). There's an issue open to fix
this on the app level in [this
ticket](#25690).
- Asserting values from elements found by their css/testid can introduce
flakiness as the elements could be rendered without having the expected
text yet loaded. Now the approach for these validations has been fixed
by looking for the exact selector which includes the text
- A lavamoat error appeared once, but this seem unrelated to this
specific test and just saw it once so it's left outside the scope of
this PR `error caught in retry(): JavascriptError: javascript error:
LavaMoat - property "name" of globalThis is inaccessible under scuttling
mode. To learn more visit
https://github.com/LavaMoat/LavaMoat/pull/360.`


- Failure 2: 
-
https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/90432/workflows/81444bef-1d2c-4da0-bf60-1c0c51a5b530/jobs/3353522/parallel-runs/17

- Logs:

```
[driver] Called 'switchToWindowWithTitle' with arguments ["MetaMask Dialog",["8C425F6EC00FF98E5C0A643C0CDF0DD6","0BF6602327E4E63F0FAE641DF2BB71E0","45802EBF75EF61BE99D7F235354A0FC6","109C90EB942C664573D051FD7952C0CE","4199B96477AFE13BB203FFC80C399B9F"]]
[driver] Called 'findClickableElement' with arguments ["[data-testid=\"confirmation-submit-button\"]"]
[driver] Called 'clickElement' with arguments ["[data-testid=\"confirmation-submit-button\"]"]
[driver] Called 'openNewPage' with arguments ["http://127.0.0.1:8082"]
Failed to handle request: socket hang up
Failure on testcase: 'Request-queue UI changes handles three confirmations on three confirmations concurrently @no-mmi', for more information see the artifacts tab in CI

NoSuchWindowError: no such window: target window already closed
from unknown error: web view not found
```


- Failure 3:
-
https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/90534/workflows/87faa6e4-aa65-4273-8d7e-d369782cfe1d/jobs/3358659/parallel-runs/2

- Logs from error 2:

```
JsonRpcEngine: Response has no error or result for request:
{
  "jsonrpc": "2.0",
  "id": "8871ca57-2969-45f2-8838-115e34bfa417",
  "method": "eth_blockNumber",
  "params": [],
  "origin": "metamask",
  "networkClientId": "networkConfigurationId",
  "tabId": 1870836432
}
  at m._fetchLatestBlock (chrome-extension://aohcjlklkjjpdgebibknknkinbdpdmoe/common-1.js:1:357185)
  at async m._updateLatestBlock (chrome-extension://aohcjlklkjjpdgebibknknkinbdpdmoe/common-1.js:1:356869)
  at async m._updateAndQueue (chrome-extension://aohcjlklkjjpdgebibknknkinbdpdmoe/common-1.js:1:357352)
  at m._updateAndQueue (chrome-extension://aohcjlklkjjpdgebibknknkinbdpdmoe/common-1.js:1:357401)

----------End of Chrome error----------
---This error is on the ignore list----


[driver] Called 'findElement' with arguments ["[data-testid=\"network-display\"], [data-testid=\"signature-request-network-display\"]"]
[driver] Called 'findElement' with arguments [".confirm-page-container-summary__origin bdi, .request-signature__origin .chip__label"]
[driver] Called 'executeScript' with arguments [null]
Failure on testcase: 'Request-queue UI changes should gracefully handle network connectivity failure for confirmations @no-mmi', for more information see the artifacts tab in CI
```

- Failure 4: hasn't appeared but since it could potentially appear in
the future, has also been fixed


https://github.com/MetaMask/metamask-extension/assets/54408225/4ba1effc-61a4-4fe5-b4a9-c2785856eba3





[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25675?quickstart=1)

## **Related issues**

Fixes: #25674

## **Manual testing steps**

1. Check ci

## **Screenshots/Recordings**
![Screenshot from 2024-07-04
09-03-08](https://github.com/MetaMask/metamask-extension/assets/54408225/8acaf835-7b25-4fb6-b0fc-fa01f026d627)

![Screenshot from 2024-07-05
10-03-57](https://github.com/MetaMask/metamask-extension/assets/54408225/22530cc1-376b-4075-9871-43fff63bbe10)

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
@phakel21
Copy link

phakel21 commented Jul 5, 2024

I have same problem and when change

await lavapack.buildRuntime({ scuttleGlobalThis: { enabled: applyLavaMoat && shouldScuttle, scuttlerName: 'SCUTTLER', exceptions: scuttleGlobalThisExceptions, }, });

to

await lavapack.buildRuntime({ scuttleGlobalThis: { enabled: false, scuttlerName: 'SCUTTLER', exceptions: scuttleGlobalThisExceptions, }, });

i get MetaMask Offscreen Page where can't find any element by selenium webdriver

@x5engine
Copy link

Hello guys, I come from metamask console log :)

@SantiiRepair
Copy link

mark: image ==>> image

hi, my metamask has been corrupted after change that. how it work ?

Same here

@SantiiRepair
Copy link

SantiiRepair commented Aug 9, 2024

Changing it to false breaks the extension, when you close the browser and open it and then open the extension it doesn't work anymore, even chrome deletes it.

PS: I edited the runtime-lavamoat.js file in the extension directory inside chrome, in case you were wondering.

Screenshot 2024-08-09 141153

@f-javier-aguerri
Copy link

Hi, using yarn download-builds:test breaks the app. When opened, the metamask extension shows up as a black pop-up.
Screenshot 2025-01-03 at 09 41 51

@agupagu
Copy link

agupagu commented Feb 6, 2025

Hi I'm trying to use Browser-Use to automate metamask and have used yarn start to build a local build of metamask with lavamoat disabled. However, I am still getting these errors. Anyone can help?
Screenshot 2025-02-06 at 9 34 56 AM
Screenshot 2025-02-06 at 9 34 47 AM

@agupagu
Copy link

agupagu commented Feb 11, 2025

Hi anybody can help with the above? Would love some advice

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.