@actual-app/sync-server's missing authorization on GET /secret/:name allows non-admin OpenID users to enumerate admin-configured bank-sync secrets
Date: 2026-06
Affected: npm/@actual-app/sync-server
Attack vector: auth-bypass
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-46700
CWE: CWE-285
CVSS: 4.3 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N)

## Summary In `@actual-app/sync-server`, the `GET /secret/:name` endpoint (`app-secrets.js:53`) checks only that the caller has a valid session — it does not verify the caller is an admin. The sibling `POST /secret/` handler does enforce an admin check in OpenID mode, exposing an authorization asymmetry. Any authenticated non-admin (BASIC) user in OpenID multi-user deployments can probe the secrets store and learn which admin-managed bank-sync integrations have been configured (existence, not values). This includes integration credentials that are not otherwise observable to non-admins, such as `simplefin_accessKey`, `pluggyai_clientSecret`, `pluggyai_itemIds`, and the `gocardless_*` secrets. ## Details `packages/sync-server/src/app-secrets.js` mounts `validateSessionMiddleware` at the router level (line 15), so all handlers inherit only "must be authenticated." The POST handler then explicitly upgrades to an admin check when the active auth method is `openid`: ```js // app-secrets.js:17-46 app.post('/', async (req, res) => { // ... look up active auth method ... if (method === 'openid') { const canSaveSecrets = isAdmin(res.locals.user_id); if (!canSaveSecrets) { res.status(403).send({ status: 'error', reason: 'not-admin', details: 'You have to be admin to set secrets', }); return; } } secretsService.set(name, value); // ... }); ``` The sibling GET handler skips both the method check and the admin check enti


Mitigations:

Architectural layers (MAESTRO):