Skip to content

fix(security): Constrain raw store and submitRequestToBackground exposure in state hooks #41958

Draft
MajorLift wants to merge 4 commits into
mainfrom
jongsun/fix/agentic-hooks-allowlist
Draft

fix(security): Constrain raw store and submitRequestToBackground exposure in state hooks #41958
MajorLift wants to merge 4 commits into
mainfrom
jongsun/fix/agentic-hooks-allowlist

Conversation

@MajorLift
Copy link
Copy Markdown
Contributor

@MajorLift MajorLift commented Apr 20, 2026

Description

Constrains the agentic/CDP automation hooks added in #41648 (globalThis.stateHooks.store, .submitRequestToBackground, .getPerpsStreamManager) so that a build-flag regression or unexpected dev-mode reachability cannot grant god-mode access to the full Redux store or background RPC surface.

Compared to #41648, autonomous agents will need to be set up with the following changes:

  • Set METAMASK_AGENTIC_HOOKS=true in addition to METAMASK_DEBUG=true.
  • globalThis.stateHooks.store.getState() is available. Just .store or .store.dispatch is blocked.
  • globalThis.stateHooks.submitRequestToBackground currently doesn't allow anything so any methods needed by agents will have to be added to the AGENTIC_BACKGROUND_METHODS Set defined in ui/index.js.
  • globalThis.stateHooks.services.perps() instead of globalThis.stateHooks.getPerpsStreamManager().

Scope:

  1. Read-only store facade. Replaces raw Redux store exposure with { getState, subscribe } only — no dispatch, no replaceReducer. Observation/wait-for-condition flows still work; arbitrary state mutation is removed.
  2. Allowlist-gated submitRequestToBackground. Wraps the real RPC with a Set<string> check against AGENTIC_BACKGROUND_METHODS. The seed is empty — harness authors must open follow-up PRs listing specific methods with justification, making additions auditable. Non-allowlisted calls throw a clear error pointing at the allowlist location.
  3. stateHooks.services.perps namespace. Moves getPerpsStreamManager into a services bucket, establishing the pattern for future UI-side service singletons (swaps, bridge, etc.) rather than growing flat top-level entries.
  4. New METAMASK_AGENTIC_HOOKS build flag. Hooks now require both METAMASK_DEBUG and METAMASK_AGENTIC_HOOKS at compile time. Manual QA and general local debug builds stop exposing the automation surface unless the flag is explicitly opted in via .metamaskrc. Both flags are DefinePlugin constants → dead-code-eliminated in release builds.
  5. CODEOWNERS for bootstrap files. app/scripts/lib/setup-initial-state-hooks.* and /ui/index.js — the two entry points where globalThis.stateHooks is populated — now route through @MetaMask/extension-platform review, so future changes to the capability surface are visible to the team responsible for it.

Why: submitRequestToBackground is the full wire protocol to the background (keyring ops, transactions, networks, permissions). Exposing it raw on globalThis — gated only by a single compile-time flag — is a significant blast radius if the gate ever misfires (supply-chain injection, debug leak to release, LavaMoat compartment escape in debug builds). Curating the surface and adding a second flag provides defense-in-depth without breaking the agentic harness (names/call-signatures preserved; behavior changes only at the capability boundary).

Changelog

CHANGELOG entry: null

Related issues

Follows up on #41648 (original exposure) and #41687 (PERPS_ENABLED in main builds — the latter makes getPerpsStreamManager reachable in default debug builds, which motivated tightening this surface now).

Manual testing steps

  1. Build with METAMASK_DEBUG=true and METAMASK_AGENTIC_HOOKS=true.
    • Confirm window.stateHooks.store.getState() returns the Redux state.
    • Confirm window.stateHooks.store.dispatch is undefined.
    • Confirm window.stateHooks.submitRequestToBackground('anyMethod') throws with the "not in the agentic allowlist" message.
    • Confirm window.stateHooks.services.perps is the PerpsStreamManager singleton.
  2. Build with METAMASK_DEBUG=true and METAMASK_AGENTIC_HOOKS=false (or unset).
    • Confirm window.stateHooks.store, .submitRequestToBackground, and .services are all undefined (block is dead-code-eliminated).
  3. Build a release artifact and confirm the block does not appear in the bundle.

Pre-merge author checklist

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.

…h a curated surface

Expose a read-only `store` facade (`getState` + `subscribe`, no `dispatch`),
an allowlist-gated `submitRequestToBackground` wrapper (seed empty), and move
`getPerpsStreamManager` under `stateHooks.services.perps`. Still gated on
`METAMASK_DEBUG` and stripped from release builds.
@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-extension-platform Extension Platform team label Apr 20, 2026
Agentic/CDP automation hooks on `globalThis.stateHooks` now require both
`METAMASK_DEBUG` and `METAMASK_AGENTIC_HOOKS` to be true at build time.
Manual QA and general local debug builds no longer expose the automation
surface unless the harness flag is opted in via `.metamaskrc`.
…k files

`app/scripts/lib/setup-initial-state-hooks.*` and `/ui/index.js` are the
bootstrap entry points where `globalThis.stateHooks` is populated; changes to
either can broaden the debug/automation surface area without being obvious in
review. Routing reviews through `@MetaMask/extension-platform` keeps the
capability boundary visible.
@metamaskbotv2
Copy link
Copy Markdown
Contributor

metamaskbotv2 Bot commented Apr 20, 2026

✨ Files requiring CODEOWNER review ✨

🔒 @MetaMask/extension-security-team (1 files, +4 -0)
  • 📁 .github/
    • 📄 CODEOWNERS +4 -0

@metamaskbot metamaskbot added the INVALID-PR-TEMPLATE PR's body doesn't match template label Apr 20, 2026
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@MajorLift MajorLift self-assigned this Apr 20, 2026
@github-project-automation github-project-automation Bot moved this to Needs dev review in PR review queue Apr 20, 2026
@metamaskbotv2
Copy link
Copy Markdown
Contributor

metamaskbotv2 Bot commented Apr 20, 2026

Builds ready [7bbc499]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 8 warn · 🔴 0 fail)

Baseline (latest main): 71bd826 | Date: 10/14/58243 | Pipeline: 24672929791 | Baseline logs

Interaction Benchmarks · Samples: 5
Benchmarkchrome-browserify
loadNewAccount🟡 [Show logs]
confirmTx🟡 [Show logs]
bridgeUserActions🟡 [Show logs]

📈 Results compared to the previous 5 runs on main

  • loadNewAccount/load_new_account: -58%
  • loadNewAccount/total: -58%
  • bridgeUserActions/bridge_load_page: -20%
  • bridgeUserActions/bridge_load_asset_picker: -33%
  • bridgeUserActions/bridge_search_token: -28%
  • bridgeUserActions/total: -28%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 loadNewAccount/FCP: p75 2.5s
  • 🟡 confirmTx/FCP: p75 2.5s
  • 🟡 bridgeUserActions/FCP: p75 2.5s
  • 🟡 bridgeUserActions/LCP: p75 2.5s
Startup Benchmarks · Samples: 100
Benchmarkchrome-browserifychrome-webpackfirefox-browserifyfirefox-webpack
startupStandardHome🟢 [Show logs]🟢 [Show logs]🟢 [Show logs]🟢 [Show logs]

📈 Results compared to the previous 5 runs on main

  • startupStandardHome/uiStartup: -22%
  • startupStandardHome/load: -11%
  • startupStandardHome/domContentLoaded: -13%
  • startupStandardHome/firstReactRender: -14%
  • startupStandardHome/initialActions: -33%
  • startupStandardHome/loadScripts: -16%
  • startupStandardHome/setupStore: +14%
  • startupStandardHome/numNetworkReqs: -37%
  • startupStandardHome/uiStartup: -27%
  • startupStandardHome/load: -22%
  • startupStandardHome/domContentLoaded: -21%
  • startupStandardHome/backgroundConnect: -43%
  • startupStandardHome/firstReactRender: -27%
  • startupStandardHome/loadScripts: -22%
  • startupStandardHome/setupStore: -20%
  • startupStandardHome/numNetworkReqs: -44%
  • startupStandardHome/domInteractive: -56%
  • startupStandardHome/backgroundConnect: +17%
  • startupStandardHome/initialActions: -33%
  • startupStandardHome/numNetworkReqs: -34%
  • startupStandardHome/uiStartup: -18%
  • startupStandardHome/load: -10%
  • startupStandardHome/domContentLoaded: -10%
  • startupStandardHome/domInteractive: -63%
  • startupStandardHome/initialActions: -43%
  • startupStandardHome/loadScripts: -11%
  • startupStandardHome/setupStore: -54%
  • startupStandardHome/numNetworkReqs: -34%
User Journey Benchmarks · Samples: 5 · mock API
Benchmarkchrome-browserify
onboardingImportWallet🟢 [Show logs]
onboardingNewWallet🟢 [Show logs]
assetDetails🟡 [Show logs]
solanaAssetDetails🟡 [Show logs]
importSrpHome🟡 [Show logs]
sendTransactions🟡 [Show logs]
swap🟡 [Show logs]

📈 Results compared to the previous 5 runs on main

  • onboardingImportWallet/srpButtonToSrpForm: -83%
  • onboardingImportWallet/metricsToWalletReadyScreen: -10%
  • onboardingImportWallet/doneButtonToHomeScreen: -76%
  • onboardingImportWallet/openAccountMenuToAccountListLoaded: +27%
  • onboardingImportWallet/total: -42%
  • onboardingNewWallet/srpButtonToPwForm: -78%
  • onboardingNewWallet/skipBackupToMetricsScreen: -69%
  • onboardingNewWallet/agreeButtonToOnboardingSuccess: -11%
  • onboardingNewWallet/doneButtonToAssetList: -36%
  • onboardingNewWallet/total: -35%
  • assetDetails/assetClickToPriceChart: -46%
  • assetDetails/total: -46%
  • solanaAssetDetails/assetClickToPriceChart: -69%
  • solanaAssetDetails/total: -69%
  • importSrpHome/loginToHomeScreen: -12%
  • importSrpHome/openAccountMenuAfterLogin: -83%
  • importSrpHome/homeAfterImportWithNewWallet: -67%
  • importSrpHome/total: -60%
  • sendTransactions/selectTokenToSendFormLoaded: +32%
  • sendTransactions/reviewTransactionToConfirmationPage: +36%
  • sendTransactions/total: +35%
  • swap/openSwapPageFromHome: -97%
  • swap/fetchAndDisplaySwapQuotes: +32%
  • swap/total: +11%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 assetDetails/INP: p75 288ms
  • 🟡 assetDetails/FCP: p75 2.5s
  • 🟡 solanaAssetDetails/FCP: p75 2.5s
  • 🟡 importSrpHome/FCP: p75 2.6s
  • 🟡 sendTransactions/FCP: p75 2.5s
  • 🟡 swap/FCP: p75 2.5s
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-browserify
dappPageLoad🟢 [Show logs]
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 5 Bytes (0%)
  • common: -120 Bytes (0%)

@MajorLift MajorLift changed the title fix: Constrain raw store and submitRequestToBackground exposure in state hooks fix(security): Constrain raw store and submitRequestToBackground exposure in state hooks Apr 20, 2026
Reflects that the agentic hooks now require both `METAMASK_DEBUG` and
`METAMASK_AGENTIC_HOOKS` to be true at build time.
@metamaskbotv2
Copy link
Copy Markdown
Contributor

metamaskbotv2 Bot commented Apr 20, 2026

Builds ready [91b53b4]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 8 warn · 🔴 0 fail)

Baseline (latest main): 71bd826 | Date: 10/14/58243 | Pipeline: 24677845459 | Baseline logs

Interaction Benchmarks · Samples: 5
Benchmarkchrome-browserify
loadNewAccount🟡 [Show logs]
confirmTx🟡 [Show logs]
bridgeUserActions🟡 [Show logs]

📈 Results compared to the previous 5 runs on main

  • loadNewAccount/load_new_account: -53%
  • loadNewAccount/total: -53%
  • bridgeUserActions/bridge_load_page: -21%
  • bridgeUserActions/bridge_load_asset_picker: -29%
  • bridgeUserActions/bridge_search_token: -28%
  • bridgeUserActions/total: -29%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 loadNewAccount/FCP: p75 2.5s
  • 🟡 confirmTx/FCP: p75 2.5s
  • 🟡 bridgeUserActions/FCP: p75 2.5s
Startup Benchmarks · Samples: 100
Benchmarkchrome-browserifychrome-webpackfirefox-browserifyfirefox-webpack
startupStandardHome🟢 [Show logs]🟢 [Show logs]🟢 [Show logs]🟢 [Show logs]

📈 Results compared to the previous 5 runs on main

  • startupStandardHome/uiStartup: -26%
  • startupStandardHome/load: -15%
  • startupStandardHome/domContentLoaded: -17%
  • startupStandardHome/firstReactRender: -14%
  • startupStandardHome/initialActions: -33%
  • startupStandardHome/loadScripts: -21%
  • startupStandardHome/numNetworkReqs: -37%
  • startupStandardHome/uiStartup: -21%
  • startupStandardHome/load: -16%
  • startupStandardHome/domContentLoaded: -16%
  • startupStandardHome/backgroundConnect: -37%
  • startupStandardHome/firstReactRender: -23%
  • startupStandardHome/loadScripts: -16%
  • startupStandardHome/setupStore: -13%
  • startupStandardHome/numNetworkReqs: -44%
  • startupStandardHome/uiStartup: -14%
  • startupStandardHome/domInteractive: -59%
  • startupStandardHome/initialActions: -33%
  • startupStandardHome/setupStore: -17%
  • startupStandardHome/numNetworkReqs: -34%
  • startupStandardHome/uiStartup: -17%
  • startupStandardHome/domInteractive: -53%
  • startupStandardHome/initialActions: +14%
  • startupStandardHome/setupStore: -60%
  • startupStandardHome/numNetworkReqs: -34%
User Journey Benchmarks · Samples: 5 · mock API
Benchmarkchrome-browserify
onboardingImportWallet🟢 [Show logs]
onboardingNewWallet🟢 [Show logs]
assetDetails🟡 [Show logs]
solanaAssetDetails🟡 [Show logs]
importSrpHome🟡 [Show logs]
sendTransactions🟡 [Show logs]
swap🟡 [Show logs]

📈 Results compared to the previous 5 runs on main

  • onboardingImportWallet/srpButtonToSrpForm: -85%
  • onboardingImportWallet/metricsToWalletReadyScreen: -51%
  • onboardingImportWallet/doneButtonToHomeScreen: -76%
  • onboardingImportWallet/openAccountMenuToAccountListLoaded: +26%
  • onboardingImportWallet/total: -44%
  • onboardingNewWallet/srpButtonToPwForm: -78%
  • onboardingNewWallet/skipBackupToMetricsScreen: -66%
  • onboardingNewWallet/doneButtonToAssetList: -42%
  • onboardingNewWallet/total: -39%
  • assetDetails/assetClickToPriceChart: -14%
  • assetDetails/total: -14%
  • solanaAssetDetails/assetClickToPriceChart: -69%
  • solanaAssetDetails/total: -69%
  • importSrpHome/loginToHomeScreen: -20%
  • importSrpHome/openAccountMenuAfterLogin: -57%
  • importSrpHome/homeAfterImportWithNewWallet: -67%
  • importSrpHome/total: -60%
  • sendTransactions/openSendPageFromHome: -20%
  • sendTransactions/selectTokenToSendFormLoaded: -15%
  • sendTransactions/reviewTransactionToConfirmationPage: +41%
  • sendTransactions/total: +38%
  • swap/openSwapPageFromHome: -96%
  • swap/fetchAndDisplaySwapQuotes: +31%
  • swap/total: +12%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 assetDetails/FCP: p75 2.5s
  • 🟡 solanaAssetDetails/FCP: p75 2.6s
  • 🟡 importSrpHome/FCP: p75 2.4s
  • 🟡 sendTransactions/FCP: p75 2.6s
  • 🟡 swap/FCP: p75 2.6s
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-browserify
dappPageLoad🟢 [Show logs]
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: -107 Bytes (0%)
  • common: -120 Bytes (0%)

@MajorLift MajorLift moved this from Needs dev review to Needs more work from the author in PR review queue Apr 22, 2026
@MajorLift MajorLift removed the status in PR review queue Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

INVALID-PR-TEMPLATE PR's body doesn't match template size-S team-extension-platform Extension Platform team

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants