{"id":"CVE-2026-28396","kind":"vulnerability","aliases":["CVE-2026-28396","GHSA-x4vh-j75g-268g"],"related":[],"title":"NocoDB's Refresh Tokens Not Revoked on Password Reset","description":"### Summary\nThe password reset flow did not revoke existing refresh tokens, allowing an attacker with a previously stolen refresh token to continue minting valid JWTs after the victim resets their password.\n\n### Details\n`passwordReset()` in `users.service.ts` updated `token_version` (invalidating JWTs) but did not call `UserRefreshToken.deleteAllUserToken()`. The `refreshToken()` method only checked token existence, not `token_version`. Both `passwordChange()` and `signOut()` correctly deleted all refresh tokens.\n\n### Impact\nAn attacker who previously obtained a refresh token retains access after password reset until the token expires.\n\n### Credit\nThis issue was reported by [@bugbunny-research](https://github.com/bugbunny-research) (bugbunny.ai).","summary":"NocoDB's password reset invalidated access JWTs but did not delete existing refresh tokens. An attacker holding a previously stolen refresh token can keep minting valid JWTs after the victim resets their password, until that token expires.","severity":"medium","cvss":6.5,"epss":0.0021,"exploited":false,"exploited_at":null,"products":[{"name":"nocodb","fixed":["0.301.3"],"affected":"<= 0.301.2","ecosystem":"npm"},{"name":"nocodb","fixed":["0.301.3"],"affected":"< 0.301.3","ecosystem":"vendor"}],"ecosystems":["npm","vendor"],"tags":["cwe-613","nocodb","node","package","self-hosted"],"not_affected_if":["no-stolen-refresh-tokens","version-0.301.3-or-later"],"how_to_check":"- Check the installed `nocodb` package version in your project, for example with `npm ls nocodb`.\n- Look for a `passwordReset` path in `users.service.ts` that does not call `UserRefreshToken.deleteAllUserToken()`.","fix":"- `nocodb` moves to `0.301.3` or later.","status":"open","published_at":"2026-03-02T17:16:34+00:00","first_seen_at":"2026-11-05T22:57:14+00:00","triaged_at":"2026-11-05T22:59:00+00:00","updated_at":"2026-11-05T23:15:56+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-05T22:59:00Z","model":"claude-haiku-5-5","output":{"fix":"- `nocodb` moves to `0.301.3` or later.","reach":"internet","title":"NocoDB: password reset leaves stolen refresh tokens usable","impact":"medium","summary":"NocoDB's password reset invalidated access JWTs but did not delete existing refresh tokens. An attacker holding a previously stolen refresh token can keep minting valid JWTs after the victim resets their password, until that token expires.","audience":"businesses","category":"package","nicknames":[],"who_cares":["nocodb","self-hosted","node"],"confidence":0.8,"fix_status":"available","needs_auth":"yes","worst_case":"Attacker keeps account access after password reset until token expiry.","analyst_note":"","how_to_check":"- Check the installed `nocodb` package version in your project, for example with `npm ls nocodb`.\n- Look for a `passwordReset` path in `users.service.ts` that does not call `UserRefreshToken.deleteAllUserToken()`.","plain_action":"","needs_analyst":false,"not_affected_if":["version-0.301.3-or-later","no-stolen-refresh-tokens"],"who_is_affected":"NocoDB instances at version `0.301.2` or earlier are affected. The exposure requires an attacker who has already obtained a refresh token, so teams that never leaked tokens face less risk. Teams that use password reset as a response to a suspected account compromise should treat it as relevant.","user_interaction":"no","needs_special_setup":"no","severity_if_unknown":"unknown"},"cost_usd":0.000713,"input_tokens":4487,"output_tokens":529,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-2026':11A,19A '-28396':12A,20A '-613':24B '/bugbunny-research)':161C '268g':17A 'access':36B,142C 'allow':79C 'attack':46B,81C,134C 'bugbunni':157C 'bugbunny-research':156C 'bugbunny.ai':162C 'call':111C 'check':117C 'continu':89C 'correct':127C 'credit':150C 'cve':10A,18A 'cwe':23B 'delet':41B,128C 'detail':99C 'exist':42B,76C,119C 'expir':67B,149C 'flow':72C 'ghsa':14A 'ghsa-x4vh-j75g-268g':13A 'github.com':160C 'github.com/bugbunny-research)':159C 'hold':47B 'host':30B 'impact':132C 'invalid':35B,106C 'issu':152C 'j75g':16A 'jwts':37B,57B,92C,107C 'keep':54B 'method':115C 'mint':55B,90C 'nocodb':1A,21A,22A,25B,31B 'node':26B 'obtain':137C 'packag':27B 'password':8A,33B,63B,70C,98C,144C 'passwordchang':124C 'passwordreset':100C 'previous':49B,84C,136C 'refresh':3A,43B,51B,77C,86C,130C,139C 'refreshtoken':114C 'report':154C 'research':158C 'reset':9A,34B,61B,71C,96C,145C 'retain':141C 'revok':6A,75C 'self':29B 'self-host':28B 'signout':126C 'stolen':50B,85C 'summari':68C 'token':4A,44B,52B,66B,78C,87C,104C,118C,121C,131C,140C,148C 'updat':103C 'userrefreshtoken.deleteallusertoken':112C 'users.service.ts':102C 'valid':56B,91C 'version':105C,122C 'victim':60B,95C 'x4vh':15A","nicknames":[],"mitigations":[],"who_affected":"NocoDB instances at version `0.301.2` or earlier are affected. The exposure requires an attacker who has already obtained a refresh token, so teams that never leaked tokens face less risk. Teams that use password reset as a response to a suspected account compromise should treat it as relevant.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["nocodb"],"ptokens":["nocodb"],"labels":["cwe613","medium","nocodb","node","npm","package","selfhosted","vendor"],"campaign_type":null,"first_public_at":"2026-03-02T14:09:37+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-x4vh-j75g-268g","url":"https://github.com/advisories/GHSA-x4vh-j75g-268g","severity":"medium","published_at":"2026-03-02T19:53:17+00:00","modified_at":"2026-11-05T22:53:53+00:00","first_seen_at":"2026-11-05T22:57:14+00:00","fetched_at":"2026-11-05T23:06:47+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-28396","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-28396","severity":null,"published_at":"2026-03-02T17:16:34+00:00","modified_at":"2026-06-17T10:28:32+00:00","first_seen_at":"2026-11-05T23:15:56+00:00","fetched_at":"2026-11-05T23:15:56+00:00","note":null}],"documents":[{"id":81231,"url":"https://api.github.com/repos/nocodb/nocodb/security-advisories/GHSA-x4vh-j75g-268g","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":81232,"url":"https://github.com/nocodb/nocodb/security/advisories/GHSA-x4vh-j75g-268g","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":"2026-03-02T14:09:37+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":80893,"url":"https://github.com/nocodb/nocodb/releases/tag/0.301.3","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"},{"id":81234,"url":"https://github.com/nocodb/nocodb/commit/dfbe836577354ba780563a5cf8cb50bfc61a203e","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"}],"exploit_sources":[{"type":"epss","score":0.0021,"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-28396"}],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}