actualbudget/actual · error

URL not allowed: Unable to verify allowlist

Error message

URL not allowed: Unable to verify allowlist

What it means

Before checking the target against allowlist rules, the proxy fetches the latest allowlist. If fetchAllowlist() throws (e.g. the allowlist source is unreachable), the proxy conservatively refuses the request with 403 'URL not allowed / Unable to verify allowlist'. This is a fail-closed behavior: inability to verify is treated as not allowed.

Source

Thrown at packages/sync-server/src/app-cors-proxy.js:153

  // Validate session/token
  const session = await validateSession(req, res);
  if (!session) {
    return; // validateSession already sent the response
  }

  let url;
  try {
    url = new URL(targetUrlString);
  } catch {
    return res.status(400).json({ error: 'Invalid url parameter' });
  }

  // Fetch the latest allowlist
  try {
    await fetchAllowlist();
  } catch (error) {
    console.error('Failed to fetch allowlist:', error);
    return res.status(403).json({
      error: 'URL not allowed',
      message: 'Unable to verify allowlist',
    });
  }

  // Check if the URL is allowed
  if (!isUrlAllowed(url.href)) {
    console.warn('Blocked request to unauthorized URL:', url.href);
    return res.status(403).json({
      error: 'URL not allowed',
      message:
        'Only allowlisted plugin repositories are allowed (localhost only in development)',
    });
  }

  try {
    const { method = 'GET', headers: customHeaders = {} } = req.body || {};

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Check the sync server's outbound network access and the console error logged server-side ('Failed to fetch allowlist') for the root cause.
  2. Verify the allowlist source URL/configuration is correct and reachable (curl it from the server host).
  3. Retry after the transient outage, or host a local allowlist copy so verification does not depend on an external service.

Example fix

// before (server env with no egress)
ALLOWLIST_URL=https://plugins.actualbudget.org/allowlist.json
// after: serve the allowlist locally
ALLOWLIST_URL=http://localhost:5006/allowlist.json
Defensive patterns

Strategy: retry

Validate before calling

const allowlistUrl = process.env.ALLOWLIST_URL;
if (allowlistUrl) {
  const r = await fetch(allowlistUrl).catch(() => null);
  if (!r || !r.ok) console.warn('Allowlist source unreachable; proxy requests will 403');
}

Try / catch

async function proxyWithRetry(url, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(`/cors-proxy?url=${encodeURIComponent(url)}`);
    if (res.status !== 403) return res;
    await new Promise(r => setTimeout(r, 2 ** i * 1000));
  }
  throw new Error('Proxy could not verify allowlist (403)');
}

Prevention

When it happens

Trigger: The remote allowlist fetch fails — network outage on the server, allowlist host down, DNS failure, or invalid allowlist content — while a proxied request arrives.

Common situations: Self-hosted server without outbound internet access; allowlist URL misconfigured or moved; corporate firewall blocking the server's egress; transient upstream outage.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/c3f0cef466e24d96. Report an issue: GitHub.