fix: possible CSRF attack via GET APIs CVE-2026-61502
Massimo Melina committed
Jul 11, 2026 at 11:38 UTC
a1c6cc0a0568e03619573e0fc079c07a4a1faef6
2 files changed
+6
-3
src/apiMiddleware.ts
+2
-3
@@ -28,12 +28,11 @@ export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
28
const params = isPost ? ctx.state.params || {} : ctx.query
29
const apiName = ctx.path
30
console.debug('API', ctx.method, apiName, { ...params })
31
- const csrfSafe = !isPost
32
- || ctx.get('x-hfs-anti-csrf') // automatic browser actions won't carry this header
31
+ const csrfSafe = ctx.get('x-hfs-anti-csrf') // automatic browser actions won't carry this header
32
|| apiName.startsWith('get_') // "get_" apis are safe because they make no change
33
|| /^(curl|wget|python|go-|java|axios|postman|httpie|insomnia|bruno)/i.test(ctx.get('user-agent') || '') // only browser are subject to CSRF
34
if (!csrfSafe)
36
- return send(HTTP_FOOL, "missing header x-hfs-anti-csrf=1")
35
+ return send(HTTP_FOOL, "missing header x-hfs-anti-csrf:1")
36
const customApiRest = apiName.startsWith(PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(PLUGIN_CUSTOM_REST_PREFIX.length)
37
const apiFun = customApiRest && firstPlugin(pl => pl.getData().customRest?.[customApiRest])
38
|| apis.hasOwnProperty(apiName) && apis[apiName]!
tests/test.ts
+4
@@ -177,6 +177,10 @@ describe('basics', () => {
177
test('missing perm', reqList('/for-admins/', 401))
178
test('missing perm.file', req('/for-admins/alfa.txt', 401))
179
test('missing anti-csrf', reqApi('rename', { uri: '/f1', dest: 'x' }, 418, { headers: {} })) // overriding anti-csrf
180
+ test('missing anti-csrf.get mutation', req(API + 'add_account?username=csrf&password=x&admin=true', 418, {
181
+ headers: { 'user-agent': 'Mozilla/5.0' },
182
+ jar: {},
183
+ }))
184
test('malformed body', reqApi('rename', { uri: '/f1', dest: 'x' }, { status: 400 }, {
185
headers: { 'x-hfs-anti-csrf': '1', 'content-type': 'application/json' },
186
body: '{'