{"record":{"id":"8a1bfc9611e99cef","repo":"actualbudget/actual","slug":"invalid-file-id-8a1bfc","errorCode":"invalid-file-id","errorMessage":"invalid fileId","messagePattern":"invalid fileId","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-secrets.js","lineNumber":52,"sourceCode":"}\n\napp.post('/', async (req, res) => {\n  const { name, value } = req.body || {};\n  const fileId = req.get('X-Actual-File-Id');\n  const perBudgetFile = fileId != null;\n\n  if (!(name in SecretName)) {\n    res.status(400).send({\n      status: 'error',\n      reason: 'invalid-secret-name',\n      details: 'Unknown secret name',\n    });\n    return;\n  }\n\n  if (perBudgetFile) {\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 (!canManagePerBudgetFileSecrets(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  } else if (!canManageGlobalSecrets(res.locals.user_id)) {\n    res.status(403).send({\n      status: 'error',","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-secrets.js#L34-L70","documentation":"When a POST /secrets request includes an X-Actual-File-Id header it is treated as a per-budget secret, and the server validates the header with isValidFileId(fileId) before touching the store. A header that is present but not a well-formed budget file id is rejected with HTTP 400 and reason 'invalid-file-id'.","triggerScenarios":"POST /secrets with an X-Actual-File-Id header that is an empty string, random UUID, or a value not matching the expected budget file id format; sending placeholder header values from templates; a budget id from a different server instance.","commonSituations":"Copy-pasting a budget name or account id instead of the file id; hardcoded dummy header values ('<fileId>') left in scripts; old ids after re-creating a budget.","solutions":["Send the actual budget file id (cloud file id) in X-Actual-File-Id, obtained from the app or the files listing.","Omit the X-Actual-File-Id header entirely if you intend to set a global secret (requires admin).","Validate the id format client-side before calling (UUID-like) to fail fast.","Re-fetch the file id if the budget was recreated or synced from another instance."],"exampleFix":"// before\nheaders: { 'X-Actual-File-Id': 'my-budget' }\n// after\nheaders: { 'X-Actual-File-Id': '3f1c2a4e-....' } // real budget file id, or omit header for global secret","handlingStrategy":"validation","validationCode":"const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nfunction assertValidFileId(fileId) {\n  if (!UUID_RE.test(fileId)) throw new Error(`Bad file id: ${fileId}`);\n}","typeGuard":"function isFileId(v) {\n  return typeof v === 'string' && v.length > 0 && /^[0-9a-f-]{36}$/i.test(v);\n}","tryCatchPattern":"if (!isFileId(fileId)) {\n  // fail fast client-side; do not call the API\n} else {\n  const res = await fetch('/secrets', { method: 'POST', headers: { 'X-Actual-File-Id': fileId }, ... });\n  if (res.status === 400 && (await res.json()).reason === 'invalid-file-id') { /* re-fetch budget id */ }\n}","preventionTips":["Copy file ids from the app/API, never hand-type them","Do not commit placeholder header values into scripts","Re-resolve file ids after budget recreation or server migration","Validate UUID format before every request"],"tags":["http-400","validation","headers","secrets"],"backgroundTag":"invalid-identifier","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}