actualbudget/actual · warning
Method not allowed
Error message
Method not allowed
What it means
After type-checking, the proxy normalizes the method to uppercase and only permits GET and HEAD for proxied requests. Any other verb produces 405 'Method not allowed'. This restricts the proxy to read-only operations by design.
Source
Thrown at packages/sync-server/src/app-cors-proxy.js:177
// Check if the URL is allowed
if (!isUrlAllowed(url.href)) {
console.warn('Blocked request to unauthorized URL:', url.href);
return res.status(403).json({
error: 'URL not allowed',
message:
'Only allowlisted plugin repositories are allowed (localhost only in development)',
});
}
try {
const { method = 'GET', headers: customHeaders = {} } = req.body || {};
if (typeof method !== 'string') {
return res.status(400).json({ error: 'Invalid method parameter' });
}
const methodNormalized = method.toUpperCase();
if (!['GET', 'HEAD'].includes(methodNormalized)) {
return res.status(405).json({ error: 'Method not allowed' });
}
const requestHeaders = {
...req.headers,
...customHeaders,
host: url.host,
};
// Remove headers that shouldn't be forwarded
delete requestHeaders['x-actual-token'];
delete requestHeaders['content-length'];
delete requestHeaders['cookie'];
delete requestHeaders['cookie2'];
// Add GitHub authentication if token is configured and request is to GitHub
const githubToken = config.get('github.token');
if (
githubToken &&View on GitHub (pinned to d4334cb6e6)
Solutions
- Use GET (or HEAD) for the proxied request — the proxy is intentionally read-only.
- If a write operation is needed, use a dedicated API/integration path instead of the CORS proxy.
- Ensure you send the verb in the JSON body's `method` field, not just via the HTTP method of the proxy request itself.
Example fix
// before
proxy({ url: target, method: 'POST', body: payload });
// after
proxy({ url: target, method: 'GET' }); // read-only proxy Defensive patterns
Strategy: validation
Validate before calling
const method = (opts.method ?? 'GET').toUpperCase();
if (!['GET', 'HEAD'].includes(method)) {
throw new Error(`Proxy supports only GET/HEAD, got ${method}`);
} Type guard
function isProxyMethod(m) {
return typeof m === 'string' && ['GET', 'HEAD'].includes(m.toUpperCase());
} Try / catch
try {
return await proxy({ url, method });
} catch (e) {
if (e.status === 405) {
console.error('CORS proxy is read-only: use GET or HEAD');
}
throw e;
} Prevention
- Treat the CORS proxy as read-only; route writes through proper APIs.
- Normalize the method client-side before sending.
- Document the GET/HEAD restriction in plugin development guides.
When it happens
Trigger: Requesting the proxy with body { method: 'POST' }, 'PUT', 'DELETE', 'PATCH', etc., or a lowercase variant that normalizes to a disallowed verb.
Common situations: A plugin trying to publish/upload through the proxy; reusing client code that defaults to POST; assuming the proxy forwards the incoming request's own method instead of reading the body field.
Related errors
- URL not allowed: Only allowlisted plugin repositories are al
- Missing url parameter
- Invalid url parameter
- URL not allowed: Unable to verify allowlist
- Invalid method parameter
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/90f3ddd3a226a25b.
Report an issue: GitHub.