{"record":{"id":"730a108581efe653","repo":"actualbudget/actual","slug":"invalid-file-id-730a10","errorCode":"invalid-file-id","errorMessage":"invalid fileId","messagePattern":"invalid fileId","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-pluggyai/app-pluggyai.js","lineNumber":31,"sourceCode":"import { pluggyaiService } from './pluggyai-service';\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\nfunction canAccessFile(fileId, userId) {\n  return isAdmin(userId) || UserService.countUserAccess(fileId, userId) > 0;\n}\n\napp.post(\n  '/status',\n  handleError(async (req, res) => {\n    const fileId = req.get('X-Actual-File-Id');\n    if (!!fileId) {\n      if (!isValidFileId(fileId)) {\n        res.status(400).send({\n          status: 'error',\n          reason: 'invalid-file-id',\n          details: 'invalid fileId',\n        });\n        return;\n      }\n\n      if (!canAccessFile(fileId, res.locals.user_id)) {\n        res.status(403).send({\n          status: 'error',\n          reason: 'file-access-denied',\n          details: \"You don't have permissions over this file\",\n        });\n        return;\n      }\n    }\n\n    const source = pluggyaiService.getCredentialSource(fileId);","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-pluggyai/app-pluggyai.js#L13-L49","documentation":"The Pluggy.ai integration's POST /status endpoint validates the X-Actual-File-Id header with isValidFileId when present. If the header value is not a valid file id, it returns HTTP 400 with reason 'invalid-file-id' and details 'invalid fileId'. This is an input-validation guard before any authorization or database lookup.","triggerScenarios":"POST /status with X-Actual-File-Id set to a malformed/non-UUID value (empty-ish string, arbitrary text, truncated id).","commonSituations":"Client integration hard-coding a wrong id; passing a budget name instead of its fileId; copy/paste losing characters; sending the header with an empty-but-present value.","solutions":["Send the correct budget fileId — the exact id string from the Actual app/server — in X-Actual-File-Id","Omit the X-Actual-File-Id header entirely if no budget context is needed (the check only runs when a value is present)","Log the outgoing header and compare it against a fileId listed by the sync-server","Regenerate/locate the id via the server API rather than manual input"],"exampleFix":"// before\nheaders: { 'X-Actual-File-Id': 'My Budget' }\n// after\nheaders: { 'X-Actual-File-Id': '3f1c9a2e-...-valid-uuid' }","handlingStrategy":"validation","validationCode":"const fileId = '3f1c9a2e-8b7d-4c2a-9e1f-0d6b5a4c3e2d'; // example\nfunction isPlausibleFileId(id) {\n  return typeof id === 'string' && id.trim().length > 0 && /^[0-9a-f-]{16,}$/i.test(id.trim());\n}\nif (!isPlausibleFileId(fileId)) throw new Error('Refusing request: malformed fileId');","typeGuard":"function isValidFileId(v) {\n  return typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);\n}","tryCatchPattern":"const res = await fetch('/pluggyai/status', { headers: { 'X-Actual-File-Id': fileId } });\nif (res.status === 400) {\n  const body = await res.json();\n  if (body.reason === 'invalid-file-id') {\n    throw new Error(`Bad fileId header: ${body.details}`);\n  }\n}","preventionTips":["Copy fileIds from the app/server listing, never type them manually","Omit the header entirely when no budget is targeted","Keep a constants file of validated budget ids","Log and diff outgoing headers in integration tests"],"tags":["http-400","validation","pluggyai","headers"],"backgroundTag":"invalid-file-id","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}