-
Notifications
You must be signed in to change notification settings - Fork 72
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
Conversation
@@ -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() |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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}) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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?
packages/core/lib/lockdown.umd.js
Outdated
@@ -3027,7 +3027,7 @@ const tagError = (err, optErrorName = err.name) => { | |||
*/ | |||
const makeError = ( | |||
optDetails = redactedDetails`Assert failed`, | |||
ErrorConstructor = globalThis.Error, | |||
ErrorConstructor = Error, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
if (globalThis?.EventTarget?.prototype) { | ||
props = [...props, ...Object.getOwnPropertyNames(EventTarget.prototype)] | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@henzycuong1 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" |
sentry-install.js:1 Property eval will not be hardened because it is scuttled by LavaMoat protection. Visit #360 to learn more. |
## **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. [](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.
… 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.  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 [](https://codespaces.new/MetaMask/metamask-extension/pull/25675?quickstart=1) ## **Related issues** Fixes: #25674 ## **Manual testing steps** 1. Check ci ## **Screenshots/Recordings**   ## **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.
I have same problem and when change
to
i get MetaMask Offscreen Page where can't find any element by selenium webdriver |
Hello guys, I come from metamask console log :) |
Hi anybody can help with the above? Would love some advice |
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 realglobalThis
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:
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 (seeusage
section below)Support
Currently this feature is only usable for browser based apps built withbrowserify
With #391
node
/core
/browserify
support scuttlingUsage
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.WIP
Some later related PRs: