actualbudget/actual · warning
Blocked request to disallowed protocol: ${url.protocol}
Error message
Blocked request to disallowed protocol: ${url.protocol} What it means
The SSRF guard restricts outbound requests made by the sync server to http: and https:. If the parsed URL uses any other scheme — file:, ftp:, gopher:, data:, etc. — it throws this error to prevent the server from being tricked into accessing non-HTTP resources or local files.
Source
Thrown at packages/sync-server/src/util/ssrf.ts:84
*
* Pass { allowPrivateNetwork: true } for callers (e.g. SimpleFIN) whose
* upstream may legitimately be a self-hosted server on the local network; the
* always-blocked ranges (cloud metadata, reserved, broadcast) remain blocked
* regardless.
*/
export async function assertUrlAllowed(
targetUrl: string,
options: SsrfOptions = {},
): Promise<void> {
let url: URL;
try {
url = new URL(targetUrl);
} catch {
throw new Error('Invalid URL');
}
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
throw new Error(`Blocked request to disallowed protocol: ${url.protocol}`);
}
// URL keeps the surrounding brackets on IPv6 hosts (e.g. "[::1]"); strip
// them so the address can be parsed and resolved.
const hostname = url.hostname.replace(/^\[|\]$/g, '');
// Literal IP address: check it directly without a DNS lookup.
if (ipaddr.isValid(hostname)) {
if (isBlockedIp(hostname, options)) {
throw new Error(`Blocked request to private/local IP: ${hostname}`);
}
return;
}
// Hostname: resolve every address it points to and reject if any is blocked.
let addresses: { address: string }[];
try {
addresses = await dnsLookup(hostname, { all: true });View on GitHub (pinned to d4334cb6e6)
Solutions
- Use only http:// or https:// URLs for the server/bootstrap endpoint configuration.
- If you intentionally serve over a custom scheme, switch that endpoint to HTTPS behind a proxy.
- If you are the server operator and this appears in logs, treat it as a malicious/probing request and review source IPs.
- Sanitize user-supplied URLs client-side to http(s) before sending.
Example fix
// before
const url = 'file:///etc/passwd';
// after
if (!/^https?:\/\//.test(url)) throw new Error('Only http(s) URLs are supported'); Defensive patterns
Strategy: validation
Validate before calling
function isHttpProtocol(value) {
try {
const u = new URL(value);
return ['http:', 'https:'].includes(u.protocol);
} catch {
return false;
}
} Try / catch
try {
await assertUrlAllowed(targetUrl);
} catch (e) {
if (e.message.startsWith('Blocked request to disallowed protocol')) {
logger.warn('Refused non-http(s) target:', targetUrl);
return null;
}
throw e;
} Prevention
- Only configure http/https endpoints for bootstrap/claim URLs.
- Reject non-http(s) schemes in any user-facing URL input field.
- Log blocked-protocol attempts and alert on repeats (likely probing).
When it happens
Trigger: Calling claimAccessKey/getAccounts with a userFileUrl whose protocol is not http or https, e.g. 'file:///etc/passwd', 'ftp://host/file', or 'javascript:...' injected via request parameters.
Common situations: Attack payloads probing for SSRF/file-read via the bootstrapped/claim endpoints; misconfigured self-hosted setups pointing at non-HTTP internal services; template/env values accidentally containing a file: scheme.
Related errors
- Invalid URL
- Blocked request to private/local IP: ${hostname}
- Blocked request to host resolving to private/local IP: ${hos
- Unable to resolve host: ${hostname}
- No id returned from download.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/5b7da9bd199fc8ab.
Report an issue: GitHub.