NocoDB stores OAuth tokens in source configs that low-priv users can pull from the meta API. Patch, rotate, stop reusing personal OAuth.
You spun up NocoDB on a $5 Fly machine three months ago because Airtable was about to charge you $200 a month for seats you weren't using. You pointed it at your Postgres, plugged in a Google OAuth provider so the team could sign in, and added a Google Sheets sync source so marketing could keep updating the launch list. It worked. You moved on. Last week your contractor, who you gave an editor role to so she could clean up a customer table, hit one of NocoDB's meta API endpoints from her browser console. The JSON that came back had a field called config. Inside config: your Google refresh_token in plain text. The same refresh_token that lets her read every Sheet, Doc, and Drive file on the account that set up the integration. Your account. That is GHSA-g72g-r7m4-9x4g. Not a clever zero day, not an APT. You shipped NocoDB the way the docs told you to, gave a contractor the role the UI suggested, and the platform handed her your OAuth tokens.
02The tokens live one API call away
NocoDB's meta database stores everything: users, bases, views, and the integration configs for every source you connect. When you OAuth into Google to set up a Sheets sync, NocoDB persists the access_token and refresh_token in the meta record for that source. Same for Airtable imports, GitHub apps, anything that needs a token to talk to a third party. The bug is that until you patch, several /api/v2/meta endpoints return source records with the full config blob, tokens included, to any logged-in user above guest. Editor reads it. In some setups, commenter reads it. You can confirm on your own instance in 30 seconds. Grab a low-priv API token from a non-admin user and curl: curl -H "xc-token: $LOW_PRIV_TOKEN" https://your-nocodb.example.com/api/v2/meta/bases/$BASE_ID/sources Look at the response. If you see refresh_token, client_secret, or api_key strings sitting in plain JSON, you are exposed. If you see them base64 encoded, you are also exposed, because base64 is not encryption. The check to run today: log into NocoDB as your lowest-priv real user, open the network tab, and click around any source or integration. If any response body contains a token-shaped string, every account at that role can pull it. That includes the freelancer you forgot to offboard in March.
03How this ships in a normal week
You did not write this code. NocoDB did. The mistake ships the moment you do the normal founder thing. Day one, you authenticate with your personal Google account because it is faster than setting up a service account. NocoDB stores the refresh_token tied to your Google identity with scopes for Drive and Sheets. Day fourteen, your VA needs to update the affiliate list. You make her an editor on that workspace. The role label says "can edit rows," which sounds fine. Day sixty, a Vercel preview link of your internal dashboard leaks into a Loom you posted on Twitter. The preview proxies to the same NocoDB instance. A curious viewer with an account, or with the preview's xc-token in localStorage, hits /api/v2/meta and pulls your Google refresh_token. The failure mode is not exotic. NocoDB's API surface trusts the UI's role labels to mean what they look like, while a separate code path hands out raw config to anyone authenticated. Same shape as Supabase RLS off by default, except here it is an OAuth refresh token instead of a row. Check today: SELECT user_id, roles FROM your NocoDB users table. Anyone with editor or higher who is not currently an employee should be rotated out. Then list every source and ask: if this token leaked tonight, what scopes would it walk away with? If the answer is "all of my Google Drive," that integration needs a service account with one folder of access, not your personal OAuth.
04Patch, rotate, and stop reusing personal OAuth
Three things, in order. First, upgrade. The advisory has a fixed version. Pin it, deploy it, redeploy your Fly machine. If you are on a Docker tag like nocodb:latest pinned six months ago, you are running vulnerable code right now. Second, rotate every token NocoDB has touched. The fix patches the leak going forward. It does not retroactively un-leak the tokens that have already been served to anyone with editor access for the last few months. Revoke the Google refresh_token from myaccount.google.com. Rotate the Airtable PAT. Rotate the GitHub OAuth app secret. Re-authorize NocoDB with the new ones, only after the upgrade is live. Third, change the pattern. NocoDB is a great Airtable replacement. It is not a safe place to park personal OAuth tokens with full Drive access. For every integration that supports it, create a dedicated service account or app with the smallest possible scope and one folder of access. If the integration must use a personal account, use a throwaway Google identity that owns nothing except the data NocoDB needs. Guardian scans your NocoDB instance the way that contractor's browser did. It hits the meta endpoints as a low-priv user and tells you which source configs return a refresh_token, client_secret, or API key in plain JSON, and which user roles can see them. Free, 90 seconds, one URL.
Is your NocoDB instance leaking OAuth refresh tokens?
Guardian hits your NocoDB meta API as a low-priv user and flags every source config that returns a refresh_token, client_secret, or API key in plain JSON.
Scan my app free