gildas-lormeau/SingleFile · error · Error
Unauthorized sender
Error message
Unauthorized sender
What it means
SingleFile's background message handler for external capture (onMessage in external-capture-permissions.js) only accepts messages originating from the extension's own options page. If the sender's tab/url does not match the options page, it throws 'Unauthorized sender'. This guards the permission-setting API from being invoked by arbitrary web pages or other extension contexts.
Source
Thrown at src/core/bg/external-capture-permissions.js:68
throw new Error("Cannot identify the extension requesting SingleFile capture");
}
const permissions = await config.getExternalCapturePermissions();
if (permissions.allowedExtensionIds.includes(extensionId)) {
return true;
}
if (permissions.deniedExtensionIds.includes(extensionId)) {
return false;
}
const { request, created } = getOrCreatePendingRequest(extensionId, sender, message);
if (created) {
await openOptionsPage(request);
}
return request.promise;
}
async function onMessage(message, sender) {
if (!isOptionsPageSender(sender)) {
throw new Error("Unauthorized sender");
}
if (message.method.endsWith(".getPermissions")) {
return config.getExternalCapturePermissions();
}
if (message.method.endsWith(".setPermissions")) {
await config.setExternalCapturePermissions(message.permissions);
return {};
}
if (message.method.endsWith(".getPendingRequest")) {
return getPendingRequest(message.requestId);
}
if (message.method.endsWith(".respondPendingRequest")) {
return respondPendingRequest(message.requestId, message.approved);
}
}
function isOptionsPageSender(sender) {
return Boolean(sender) && sender.id == browser.runtime.id &&View on GitHub (pinned to 517fb7c5cf)
Solutions
- Send the message from the extension's options page context (or open the options page and run the call there).
- If calling programmatically, obtain the options page's tab and use browser.tabs.sendMessage to it so isOptionsPageSender passes.
- In tests, construct a sender object matching what isOptionsPageSender expects (correct url/id fields).
Example fix
// before (from a content script)
browser.runtime.sendMessage({ method: 'externalCapture.setPermissions', permissions: [...] });
// after (from the options page itself)
const tabs = await browser.tabs.query({ url: browser.runtime.getURL('options/index.html') });
await browser.tabs.sendMessage(tabs[0].id, { method: 'externalCapture.setPermissions', permissions: [...] }); Defensive patterns
Strategy: validation
Validate before calling
function isOptionsPageSender(sender) {
const optionsUrl = browser.runtime.getURL('options/index.html');
return sender && sender.tab && sender.url && sender.url.startsWith(optionsUrl);
}
// only sendMessage when this returns true Type guard
function isFromOptionsPage(sender) {
return typeof sender?.url === 'string' &&
sender.url.startsWith(browser.runtime.getURL('options/'));
} Try / catch
try {
await browser.runtime.sendMessage({ method: 'externalCapture.setPermissions', permissions });
} catch (e) {
if (e.message === 'Unauthorized sender') {
// move the call into the options page context
}
} Prevention
- Only call permission APIs from the options page context
- Verify sender.url against the options page URL before sending
- In tests, build sender objects that match isOptionsPageSender expectations
When it happens
Trigger: Calling browser.runtime.sendMessage from a context whose sender is not the options page (e.g. a content script, popup, or another extension) with a method ending in .getPermissions or .setPermissions.
Common situations: Developers wiring up external capture automation call setPermissions from a script or console in the wrong context; tests invoke the handler directly with a fabricated sender object lacking options-page URL/id.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot identify the extension requesting SingleFile capture
- Cannot access the tab contents
- SingleFile capture was not approved for this extension
- ERR_HOST_FETCH
- response && response.error && response.error.toString()
AI-assisted analysis of gildas-lormeau/SingleFile@517fb7c5cf (2026-09-01).
Data as JSON: /api/errors/50758e33c9a5b259.
Report an issue: GitHub.