No dapp connection authorization: any site can enumerate accounts and spam sign popups

Component: ghost-extension-wallet @ 66bd207, src/inpage/pjsInject.ts:22-24, src/background/createBackgroundRpc.ts:271-278.

Description: The enable function in pjsInject.ts ignores origin entirely, with an explicit comment in the code:

injectExtension(
    async (_origin) => {
        // TODO: validate allowed origin

The getAccounts background handler (createBackgroundRpc.ts:47-62) asks for no user consent at all. The middleware only restricts the web channel to 3 methods (createTx, getAccounts, pjsSignPayload), and all three are callable from ANY origin. Origin is only logged (port.sender?.url, set by the browser, not forgeable by the page) for display in the popup. There is no origin allowlist and no “connect” prompt like the polkadot-js extension pattern.

Impact: (a) Privacy leak: any malicious site the user visits immediately gets all addresses + chains with no interaction, enabling fingerprinting and targeting. (b) Sign-request spam: a site can open repeated approval popups (each request = a new chrome.windows.create) for social engineering / click-fatigue. Funds are not directly at risk because each sign needs an explicit Approve click in a separate popup showing the origin.

Reproduction:

Static: open any site with the content script injected → window.injectedWeb3["<name>"].enable("https://evil.example") → accounts.get() returns all addresses with no prompt. Code path: pjsInject.ts:28-48 → rpc.getAccounts → keyring.getAccounts. Passive (reads own addresses only).

Severity: Medium. Reason: no direct fund loss (signing still needs per-popup approval), but non-consensual address enumeration is a real privacy violation for a wallet, and sign-spam is a phishing amplifier. Comparable to the commonly Medium-rated “missing connection prompt” class in wallet audits.

Recommendation: Implement an origin allowlist in chrome.storage.local plus a first-time per-origin connection consent prompt (polkadot-js extension pattern); gate getAccounts behind that consent.

Verification: VERIFIED.

Thank you for your report!

You’ve highlighted one of the most painful points on the extension side. A couple of months ago, the community and the team reached consensus on this: we now use only one public address exposed by the extension, regardless of derivation. While selection on the extension side is semi-random from an attacker’s point of view, the signature can only take place on the active address that is exposed.

I believe you can check yesterday’s YouTube stream for a showcase of how it will look. You can also check the draft in the community group - it will be live next week with all sources publicly available.

We can come back to this same issue afterward if it persists - or do you have other thoughts on this?

Thanks, that context helps. The single-exposed-address direction does shrink the enumeration side: one stable address is far less fingerprintable than the full account list, so I am happy to treat that half as mitigated-by-design once it ships.

The other half stands on its own, though: with no per-origin connection consent, any site can still fire repeated sign-request popups (chrome.windows.create per request) to wear the user down. That phishing amplifier does not depend on how many addresses are exposed, and the code still carries the // TODO: validate allowed origin marker. A first-time per-origin connect prompt (the polkadot-js extension pattern) would close it without changing the single-address design.

I will re-test against the public draft once it is live next week and report back whether the popup-spam vector survives there.

What about latest version of the repo, does the issue you are describing persists?