fix: a request whose origin has no host is refused, not treated as a site (closes #527) #560

Merged
clawbot merged 1 commits from issue-527-opaque-origin into next 2026-10-16 04:44:50 +02:00
3 changed files with 75 additions and 12 deletions
+13 -8
View File
@@ -2129,14 +2129,19 @@ view would leave a wallet one click from deletion.
`wallet_requestPermissions` and is on neither the allowed nor the denied list. `wallet_requestPermissions` and is on neither the allowed nor the denied list.
A site is its full origin, `scheme://host[:port]`, on both lists and for a A site is its full origin, `scheme://host[:port]`, on both lists and for a
connection allowed without "Remember": a choice for `https://dapp.example` connection allowed without "Remember": a choice for `https://dapp.example`
says nothing about `http://dapp.example` or another port of that host. The says nothing about `http://dapp.example` or another port of that host. A
background script prefers the toolbar popup (`action.openPopup()`) and falls request whose origin has no host names no site, so it is refused with EIP-1193
back to a separate popup window (`src/background/index.js`, code `4100` and opens no prompt. Two such origins are `null`, which the
`requestApproval()`). Only one exists per site at a time: a further connection browser reports for a page with an opaque origin, such as one its server
request from a site whose prompt is still unanswered is refused with EIP-1193 sandboxes with a Content-Security-Policy, and `file://`, which Chrome reports
code `-32002` and opens no new prompt. If that prompt was in a toolbar popup for a local file. The background script prefers the toolbar popup
that closed before it connected, and the toolbar popup has since been set to (`action.openPopup()`) and falls back to a separate popup window
open something else, the refused request shows that prompt again. (`src/background/index.js`, `requestApproval()`). Only one exists per site at
a time: a further connection request from a site whose prompt is still
unanswered is refused with EIP-1193 code `-32002` and opens no new prompt. If
that prompt was in a toolbar popup that closed before it connected, and the
toolbar popup has since been set to open something else, the refused request
shows that prompt again.
- **Elements**: - **Elements**:
- "Connection Request" heading - "Connection Request" heading
- Phishing warning banner (shown when the hostname is on the phishing - Phishing warning banner (shown when the hostname is on the phishing
+15 -2
View File
@@ -1457,7 +1457,14 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
// the message carries. Firefox before 126 gives no sender.origin, so // the message carries. Firefox before 126 gives no sender.origin, so
// the origin of sender.url is used: the frame that sent the message, // the origin of sender.url is used: the frame that sent the message,
// not the tab's page, which may be another site embedding that // not the tab's page, which may be another site embedding that
// frame. With neither, the request is refused. // frame.
//
// A site is scheme://host[:port], so a request is refused when its
// origin has no host, as when it has no origin at all, rather than
// credited to the site its URL names. The browser reports "null" for
// a page with an opaque origin, such as one its server sandboxes with
// a Content-Security-Policy, and Chrome reports "file://" for a local
// file.
let trustedOrigin = sender.origin; let trustedOrigin = sender.origin;
if (!trustedOrigin && sender.url) { if (!trustedOrigin && sender.url) {
try { try {
@@ -1466,7 +1473,13 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
// an unparseable URL leaves the origin unknown // an unparseable URL leaves the origin unknown
} }
} }
if (!trustedOrigin) { let host = "";
try {
host = new URL(trustedOrigin).host;
} catch {
// no origin, or "null", is not a URL and so has no host
}
if (!host) {
sendResponse({ sendResponse({
error: { error: {
code: 4100, code: 4100,
+47 -2
View File
@@ -8,8 +8,15 @@
// and a request the browser said nothing about was credited to whatever the // and a request the browser said nothing about was credited to whatever the
// page wrote (https://git.eeqj.de/sneak/AutistMask/issues/407). // page wrote (https://git.eeqj.de/sneak/AutistMask/issues/407).
// //
// Every sender here lacks sender.origin, as on old Firefox. The connection // An origin with no host names no site, so its request is refused
// check on eth_accounts is what shows which site a request was credited to. // (https://git.eeqj.de/sneak/AutistMask/issues/527). The browser reports
// "null" for a page with an opaque origin, such as one its server sandboxes
// with a Content-Security-Policy, and Chrome reports "file://" for a local
// file.
//
// Every other sender here lacks sender.origin, as on old Firefox. The
// connection check on eth_accounts is what shows which site a request was
// credited to.
const { makeStorageStub } = require("./support/storageStub"); const { makeStorageStub } = require("./support/storageStub");
@@ -144,3 +151,41 @@ describe("a request the browser does not say the sender of", () => {
}); });
}); });
}); });
describe("a request whose origin has no host", () => {
test("from a page with an opaque origin is refused, whatever its URL says", async () => {
const accounts = loadBackground();
const result = await accounts({
origin: "null",
url: CONNECTED_ORIGIN + "/sandboxed.html",
tab: { url: CONNECTED_ORIGIN + "/sandboxed.html" },
});
expect(result).toEqual({
error: {
code: 4100,
message:
"The wallet could not tell which site sent this request.",
},
});
});
test("from a local file is refused", async () => {
const accounts = loadBackground();
const result = await accounts({
origin: "file://",
url: "file:///home/user/page.html",
tab: { url: "file:///home/user/page.html" },
});
expect(result).toEqual({
error: {
code: 4100,
message:
"The wallet could not tell which site sent this request.",
},
});
});
});