{"record":{"id":"d86a964fee5bb4de","repo":"actualbudget/actual","slug":"invalid-gocardless-identifier-string-id","errorCode":null,"errorMessage":"Invalid GoCardless identifier: ${String(id)}","messagePattern":"Invalid GoCardless identifier: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/app-gocardless/app-gocardless.ts","lineNumber":57,"sourceCode":"  if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n    throw new Error('Invalid Origin header');\n  }\n  return url.origin;\n}\n\nfunction resolveRedirectHost(req: Request) {\n  const { origin } = req.headers;\n  const host = req.get('host');\n  if (origin === ELECTRON_APP_ORIGIN && host) {\n    return `${req.protocol}://${host}`;\n  }\n  return validateOrigin(origin);\n}\n\nconst SAFE_ID = /^[a-zA-Z0-9_-]+$/;\nfunction sanitizeId<T extends string = string>(id: unknown): T {\n  if (typeof id !== 'string' || !SAFE_ID.test(id)) {\n    throw new Error(`Invalid GoCardless identifier: ${String(id)}`);\n  }\n  return id as T;\n}\n\nconst LINK_PAGE_HTML = `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Actual</title>\n  </head>\n  <body>\n    <script>\n      window.close();\n    </script>\n\n    <p>Please wait...</p>\n    <p>\n      The window should close automatically. If nothing happened you can close","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-gocardless/app-gocardless.ts#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst id = input.id; // \"REL_1234/extra\"\nawait fetch(`/gocardless/get-account/${id}`)\n// after\nconst id = String(input.id).trim();\nif (!/^[a-zA-Z0-9_-]+$/.test(id)) throw new Error('bad id');\nawait fetch(`/gocardless/get-account/${id}`)","handlingStrategy":"validation","validationCode":"const SAFE_ID = /^[a-zA-Z0-9_-]+$/;\nfunction assertSafeGoCardlessId(id) {\n  if (typeof id !== 'string' || !SAFE_ID.test(id)) {\n    throw new Error(`Refusing to send invalid GoCardless id: ${String(id)}`);\n  }\n  return id.trim();\n}\nassertSafeGoCardlessId(institutionId);","typeGuard":"function isSafeId(id: unknown): id is string {\n  return typeof id === 'string' && /^[a-zA-Z0-9_-]+$/.test(id);\n}","tryCatchPattern":"try {\n  await gocardlessRoute({ institutionId });\n} catch (e) {\n  if (e.message.startsWith('Invalid GoCardless identifier')) {\n    console.error(`Bad id \"${institutionId}\" — must match /^[a-zA-Z0-9_-]+$/; use the raw id, not a URL`);\n  } else throw e;\n}","preventionTips":["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"],"tags":["validation","gocardless","input-sanitization","security"],"backgroundTag":"invalid-identifier","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}