{"record":{"id":"f807113ac8ea44c4","repo":"actualbudget/actual","slug":"invalid-method-parameter","errorCode":null,"errorMessage":"Invalid method parameter","messagePattern":"Invalid method parameter","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/sync-server/src/app-cors-proxy.js","lineNumber":173,"sourceCode":"      message: 'Unable to verify allowlist',\n    });\n  }\n\n  // Check if the URL is allowed\n  if (!isUrlAllowed(url.href)) {\n    console.warn('Blocked request to unauthorized URL:', url.href);\n    return res.status(403).json({\n      error: 'URL not allowed',\n      message:\n        'Only allowlisted plugin repositories are allowed (localhost only in development)',\n    });\n  }\n\n  try {\n    const { method = 'GET', headers: customHeaders = {} } = req.body || {};\n\n    if (typeof method !== 'string') {\n      return res.status(400).json({ error: 'Invalid method parameter' });\n    }\n    const methodNormalized = method.toUpperCase();\n    if (!['GET', 'HEAD'].includes(methodNormalized)) {\n      return res.status(405).json({ error: 'Method not allowed' });\n    }\n\n    const requestHeaders = {\n      ...req.headers,\n      ...customHeaders,\n      host: url.host,\n    };\n\n    // Remove headers that shouldn't be forwarded\n    delete requestHeaders['x-actual-token'];\n    delete requestHeaders['content-length'];\n    delete requestHeaders['cookie'];\n    delete requestHeaders['cookie2'];\n","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-cors-proxy.js#L155-L191","documentation":"The proxy accepts an optional JSON body where `method` specifies the HTTP verb to use for the proxied request. If method is present but not a string (e.g. a number or object), the proxy returns 400 'Invalid method parameter'. Only the type is checked here; allowed verbs are validated separately.","triggerScenarios":"POSTing to the proxy with body { method: 200 } or { method: { ... } } — any non-string value for method.","commonSituations":"Auto-generated clients serializing method as a number (e.g. a status code); a config value passed through unvalidated; copy-pasted body using wrong field types.","solutions":["Send method as a string, e.g. { method: 'GET' }.","Coerce/validate the method in the calling code: typeof method === 'string' || String(method).","Omit the method field entirely to use the default 'GET'."],"exampleFix":"// before\nconst body = { method: statusCode }; // number\n// after\nconst body = { method: 'GET' }; // or String(method).toUpperCase()","handlingStrategy":"type-guard","validationCode":"const method = opts.method ?? 'GET';\nif (typeof method !== 'string') throw new TypeError('method must be a string');","typeGuard":"function isMethodString(v) {\n  return typeof v === 'string';\n}","tryCatchPattern":"try {\n  return await proxy({ url, method });\n} catch (e) {\n  if (e.status === 400 && /Invalid method/.test(e.message)) {\n    console.error('method must be a string like \"GET\"');\n  }\n  throw e;\n}","preventionTips":["Always pass method as an uppercase string literal.","Omit the method field to use the safe default 'GET'.","Type the proxy options object (method?: string) to catch mistakes at compile time."],"tags":["cors-proxy","validation","rest-api"],"backgroundTag":"invalid-parameter-type","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}