actualbudget/actual · error
Invalid GoCardless identifier: ${String(id)}
Error message
Invalid GoCardless identifier: ${String(id)} What it means
sanitizeId validates GoCardless path/query identifiers (institutionId, requisitionId, country, accountId) against SAFE_ID = /^[a-zA-Z0-9_-]+$/. Anything else (empty, containing slashes, dots, spaces, or non-strings) throws Error('Invalid GoCardless identifier: ...'), acting as an injection/SSRF guard before values reach the GoCardless API URL.
Source
Thrown at packages/sync-server/src/app-gocardless/app-gocardless.ts:57
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
throw new Error('Invalid Origin header');
}
return url.origin;
}
function resolveRedirectHost(req: Request) {
const { origin } = req.headers;
const host = req.get('host');
if (origin === ELECTRON_APP_ORIGIN && host) {
return `${req.protocol}://${host}`;
}
return validateOrigin(origin);
}
const SAFE_ID = /^[a-zA-Z0-9_-]+$/;
function sanitizeId<T extends string = string>(id: unknown): T {
if (typeof id !== 'string' || !SAFE_ID.test(id)) {
throw new Error(`Invalid GoCardless identifier: ${String(id)}`);
}
return id as T;
}
const LINK_PAGE_HTML = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Actual</title>
</head>
<body>
<script>
window.close();
</script>
<p>Please wait...</p>
<p>
The window should close automatically. If nothing happened you can closeView on GitHub (pinned to d4334cb6e6)
Solutions
- Trim and verify the id against /^[a-zA-Z0-9_-]+$/ before sending it
- Use ids exactly as returned by the GoCardless institutions/requisitions endpoints (do not pass URLs)
- Add String(x).trim() at the client boundary if values may have whitespace
- Log the offending value from the error message and correct the caller
Example fix
// before
const id = input.id; // "REL_1234/extra"
await fetch(`/gocardless/get-account/${id}`)
// after
const id = String(input.id).trim();
if (!/^[a-zA-Z0-9_-]+$/.test(id)) throw new Error('bad id');
await fetch(`/gocardless/get-account/${id}`) Defensive patterns
Strategy: validation
Validate before calling
const SAFE_ID = /^[a-zA-Z0-9_-]+$/;
function assertSafeGoCardlessId(id) {
if (typeof id !== 'string' || !SAFE_ID.test(id)) {
throw new Error(`Refusing to send invalid GoCardless id: ${String(id)}`);
}
return id.trim();
}
assertSafeGoCardlessId(institutionId); Type guard
function isSafeId(id: unknown): id is string {
return typeof id === 'string' && /^[a-zA-Z0-9_-]+$/.test(id);
} Try / catch
try {
await gocardlessRoute({ institutionId });
} catch (e) {
if (e.message.startsWith('Invalid GoCardless identifier')) {
console.error(`Bad id "${institutionId}" — must match /^[a-zA-Z0-9_-]+$/; use the raw id, not a URL`);
} else throw e;
} Prevention
- Trim ids copied from dashboards or docs before use
- Pass ids, never full URLs, to GoCardless endpoints
- Guard variables for undefined/empty before interpolation in scripts
- Keep the SAFE_ID regex check as the single choke point for all ids
When it happens
Trigger: Passing an institution id like 'SANDBOXFINANCE_SFIN000060 ' (trailing space), a full URL, an empty string, undefined/null, or an id containing characters like ':' or '/' into the GoCardless route handlers.
Common situations: Copy-pasting ids with trailing whitespace or surrounding quotes; accidentally passing a requisition URL instead of its id; a bank picker supplying an untrimmed value; scripts interpolating undefined variables.
Related errors
- Invalid font-family value for "${property}": function calls
- Invalid font src: only data: URIs are allowed in @font-face.
- Theme CSS contains forbidden at-rules (@import, @media, @key
- Invalid budget id "${id}". Check the id of your budget in th
- Account with ID ${upgradingId} not found.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/d86a964fee5bb4de.
Report an issue: GitHub.