Plugin / Docs / Allow-list & Permissions

Allow-list & Permissions

The extension acts only on domains explicitly allowed by the user in its popup. This page explains the mental model and how to guide users to enable hosts.

Privacy-first • No cookies • No background tracking • Firefox ✓ (Chrome MV3 soon) • Canonical envelopes v7.10.1

v7.10.1
Mental model
  • Per-host control: actions run only on hosts present in the Allow-list (e.g., app.example.com).
  • Wildcards: patterns like *.example.com allow all subdomains of example.com.
  • Granular & local: the list is stored locally by the extension and never uploaded.

2) Default policy & banner (v7.10+)

By default (deny-by-default), no remote domains are allowed when the list is empty. When you call open/navigate to a non-listed domain, the extension shows a banner:

  • “Domain not allowed — Add to the list?”Add writes the host into the allow-list (wildcards supported, e.g. *.example.com).
  • The pending action (open/navigate) is automatically resumed after “Add”, preserving the requested focus (on/off).

Allow-list entries may be provided as HTTPS origins (e.g., https://example.org) or as hosts / wildcards (e.g., *.example.*). Origins are normalized to HTTPS by default.

Security reminders
  • Use https:// URLs. javascript: is blocked. file:// is not supported for remote control.
  • LAN/private hosts (e.g., localhost, 192.168.x.x) require explicit allow-listing and may be restricted by the browser.

3) When you’ll need the Allow-list

  • Whenever you call open, navigate, focus*, dom*, or runJs on a target domain.
  • Reading remote URL/title also requires a controlled tab on an allowed domain.

4) Detect & Show a Banner (copy-paste)

Pattern: attempt a harmless action (e.g., navigate) → if the extension replies with ok:false and error.code:"DOMAIN_NOT_ALLOWED", show an app-side banner that explains how to add the domain, then offer “Try again”.

4.1 Minimal request helper (canonical v7.10.1)
<script>
const ADMIN_ORIGIN='admin', EXT_ORIGIN='extension';
let _seq=0, _waiters=new Map();
addEventListener('message', (e) => {
  const d=e.data; if(!d||typeof d!=='object') return;
  if(d.origin!==EXT_ORIGIN) return;
  if(d.type==='RTO_RESPONSE' && d.requestId && _waiters.has(d.requestId)) {
    const fn=_waiters.get(d.requestId); _waiters.delete(d.requestId); fn(d);
  }
});
function ask(action, args={}, timeoutMs=12000){
  const requestId='r'+(++_seq);
  postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId, action, args }, '*');
  return new Promise((resolve,reject)=>{
    const t=setTimeout(()=>{ _waiters.delete(requestId); reject(Object.assign(new Error('timeout'),{ code:'TIMEOUT' })); }, timeoutMs);
    _waiters.set(requestId, (resp)=>{ clearTimeout(t); resolve(resp); });
  });
}
// Optional: retry with backoff for transient hiccups (e.g., first-run content script warmup)
async function askBackoff(action, args={}, tries=3, base=300){
  let n=0;
  while(true){
    try{
      const r = await ask(action, args, 12000);
      if(!r.ok) throw Object.assign(new Error(r.error?.message||'error'), r.error||{});
      return r;
    }catch(e){
      if(++n >= tries) throw e;
      await new Promise(r=>setTimeout(r, base*Math.pow(2,n-1)));
    }
  }
}
</script>
4.2 Test a domain and raise the banner

We purposely “probe” with navigate (safe and idempotent). If it fails with DOMAIN_NOT_ALLOWED, we reveal the banner. Les anciennes versions pouvaient simplement expirer — on gère aussi ce cas.

<div id="allow-banner" class="alert alert-warning d-none">
  <strong>Allow-list required:</strong>
  Open the extension popup and add <code class="domain">example.com</code>.
    <button class="btn btn-sm btn-outline-primary" id="allow-try">Try again</button>
</div>

<script>
async function ensureAllowed(url){
  const host = new URL(url).host;
  try{
    const r = await ask('navigate', { url, focus:false }, 12000);
    if (r.ok) return true;
    if (r.error?.code === 'DOMAIN_NOT_ALLOWED'){
      const box = document.getElementById('allow-banner');
      box.classList.remove('d-none');
      box.querySelector('.domain').textContent = host;
      document.getElementById('allow-try').onclick = () => location.reload();
      return false;
    }
    // Other structured errors: surface and stop
    throw Object.assign(new Error(r.error?.message||'error'), r.error||{});
  }catch(e){
    // TIMEOUT or older builds that didn't answer → show banner anyway
    const box = document.getElementById('allow-banner');
    if (box){
      box.classList.remove('d-none');
      box.querySelector('.domain').textContent = host;
      document.getElementById('allow-try').onclick = () => location.reload();
    }
    return false;
  }
}
ensureAllowed('https://example.com/');
</script>

The extension also displays its own “Domain not allowed — Add to the list?” banner and automatically resumes the pending action after the user confirms. This app-side banner is a complementary UX, not a requirement.

4.3 Non-blocking “preflight” check

Instead of navigating, you can preflight with a dry getUrl call. It’s less explicit but avoids navigation during checks.

async function preflightAllow(url){
  try{
    const r = await ask('getUrl', {}, 4000);
    const cur = r.ok ? (r.data?.url||'') : '';
    return cur.startsWith(new URL(url).origin);
  }catch(_){ return false; }
}

5) Friendly UI Patterns

  • Inline banner with the exact domain: “Add app.example.com to the Allow-list”.
  • Retry button that simply reloads or re-calls the action.
  • Explain where the popup is: “Click the extension icon (top-right), then ‘Allow-list’ → Add”.
  • Progressive enhancement: hide “Remote” buttons until the extension responds to a quick ask('getUrl') or ask('getTitle').
Tip: store a boolean like localStorage.rtoAllowed["example.com"]=true after a successful call to avoid re-prompting every page view.

6) Multi-domain Projects

If your flow touches several hosts, guide users to allow them all once. Use a simple checklist:

6.1 Checklist component
<ul id="rto-allow-checklist" class="list-group"></ul>
<script>
// Requires the canonical ask() and ensureAllowed() above.
const NEED = ['https://app.example.com','https://docs.example.com','https://admin.example.com'];
(async()=>{
  const ul = document.getElementById('rto-allow-checklist');
  for(const base of NEED){
    const li=document.createElement('li'); li.className='list-group-item d-flex justify-content-between align-items-center';
    const host=new URL(base).host; li.innerHTML = '<span>'+host+'</span><span class="badge bg-secondary">checking…</span>';
    ul.appendChild(li);
    const ok = await ensureAllowed(base);
    const badge = li.querySelector('.badge');
    badge.className = 'badge '+(ok?'bg-success':'bg-warning text-dark');
    badge.textContent = ok?'allowed':'needed';
  }
})();
</script>

Sub-domains are distinct: adding example.com doesn’t always imply app.example.com. Prefer listing exact hosts (or use a wildcard like *.example.com if appropriate).

7) FAQ & Troubleshooting

Question Answer
Can my web app add a domain to the Allow-list automatically? No. For privacy and safety, only the user can modify the Allow-list via the extension popup.
We allowed example.com but actions on app.example.com still fail. Allow the exact host you target. Many setups treat sub-domains separately unless you add a wildcard.
What code indicates this problem? ok:false with error.code:"DOMAIN_NOT_ALLOWED" in the canonical response. Show the banner and guide the user.
Can I test without real navigation? Yes, use a preflight like ask('getUrl') (see 4.3), or a no-op ask('runJs', { code:'true' }) once the tab is on the right origin.
What if the user closes the popup without adding the domain? Keep the banner visible and provide “Try again” after they reopen the popup and add it.
Need help writing safe flows?
Try the ChatGPT helper: Remote Tab Opener Copilot.

Next Pages

Page Description
Start Overview, how it works, setup, quick start, and links to all sections.
Detect the Extension Detection contract (ping/pong), install prompts, and graceful fallbacks when the extension isn’t present.
Allow-list & Permissions Deny-by-default model, domain allow-list via popup, and suggested UX to guide users to enable hosts.
Remote Tab Control Open/navigate/focus the controlled tab, read URL/title, select an existing tab, lifecycle & restore patterns.
DOM & Automation Action catalog (type, setValue, click, select, submit, waitFor) and safe interaction tips.
Recipes & Flows Copy-paste flows: resilient login, dashboard checks, table waits, filters, playlists, and allow-list banners.
Events Event stream reference, subscription helpers, live logger widget, and basic metrics/durations collection.
Diagnostics Inline console, error cookbook, QA self-check, and performance tips for troubleshooting flows quickly.
Full API Reference Canonical message envelopes, request/response schemas, error codes, and return shapes for every action.
Compatibility Supported browsers/versions, permissions nuances, CSP/iframe notes, and known limitations or workarounds.
Advanced Patterns Idempotent flows, retries/backoff, state restore after reload, playlist strategies, and robust selectors.
Favorites Star and group URLs, quick actions (open/focus/navigate), export/import sets, and playlist runs from favorites.
Real-World Flows (E2E) Run end-to-end flows from your admin page: helper setup, modern message contract, copy-paste journeys (login, multi-tab, extract), lightweight assertions, best practices, and troubleshooting.