BLOG POST

The SQL Injection That SAST Didn't Find

Semgrep scanned NocoDB and flagged 222 issues but missed the critical SQL injection in the Oracle client - 17 injection points across one file, invisible to pattern matching because the code sat inside a query builder context. It's the clearest example of why semantic analysis catches what SAST can't.
February 20267 min read
Jost
NocoDBsemantic analysisapplication securitySQL injectionSAST comparison

I want to tell you about something that happened when we scanned NocoDB last December. It's the clearest example I have of why pattern-based security tools don't work on modern codebases.

Semgrep looked at NocoDB and found 222 things. We looked at each one of them. And the most important SQL injection, the one that could really put a database at risk, wasn't on the list.

This isn't a bad article about Semgrep. Semgrep is something I like. It's probably the best open-source SAST tool out there for what it does. But what happened with NocoDB shows exactly where pattern matching fails, and I think it's important to know why.


A quick look at NocoDB

NocoDB is an open-source version of Airtable that you can use. You can point it at a database like MySQL, PostgreSQL, SQLite, or Oracle, and it will give you a collaborative spreadsheet interface on top of it. There are real users and real deployments of the project.

That last part is important. It's not just a school project when there's a SQL injection in NocoDB. Anyone can run any kind of query against your real database.


The Semgrep results

This is what Semgrep found:

222 total results. 208 false positives. That's a 94% noise rate.

There were 14 valid but low-impact issues, such as Dockerfiles missing user instructions, Vue components using v-html, and postMessage settings that were too open. Things that protect you from all sides. Not urgent, but worth fixing.

The SQL injection in the Oracle database client? No detection.

I want to be honest about this. The 14 valid findings from Semgrep were real problems. We made five rules out of them, sent in PRs for all of them, and they were all helpful changes.

But they were like fixing the lock on the back door when the front wall is missing.


What was really there

We found the weakness in packages/nocodb/src/db/sql-client/lib/oracle/OracleClient.ts by doing a deep code scan.

17 different places to inject. Lines 169, 325, 345, 399, 538, 563, 637, 683–685, 755, 808, 862, 909, 955, 995, 1035, 1081, and 1123.

The Oracle database client was putting user-controlled parameters directly into SQL queries without using parameters. Not in one spot, but in seventeen. Checking if a user exists, listing tables, and looking at the schema. It was all made with string concatenation instead of bind variables.

If you were an Org Creator or higher, you could run any SQL on the Oracle database. From there, you could read anything in the database, get DBA privileges, and maybe even put the whole system at risk.

The solution is simple in theory: use parameterised queries with Oracle bind variables instead of concatenation, and use Knex's built-in parameterisation whenever you can. We sent it in as PR #12748.


Why Semgrep couldn't find it

This is the part that really interests me because it's not a bug in Semgrep. It's a basic limit.

This is what a textbook SQL injection looks like:

1const query = "SELECT * FROM users WHERE id = " + userId;

That's something that every SAST tool can find. It's the first step in pattern matching.

But the Oracle client for NocoDB doesn't look like that. The code is in the context of a query builder. There is a layer that abstracts the database. It looks like the string concatenation happens inside methods that are part of a structured query system.

When a pattern matcher sees the query builder wrapping, it thinks, "Okay, this is taken care of."

But it's not taken care of. The query builder worked fine with PostgreSQL and MySQL because those backends used the right kind of parameterisation. But the Oracle client took a different path to implementation. In one backend, the same user-controlled values were safely parameterised, but in another, they were being combined into raw SQL strings.

You need to follow the flow of data to see this:

  1. Begin with the HTTP request.

  2. Go through the parameters in NocoDB's API layer.

  3. See them go into the database abstraction.

  4. Then look at each backend implementation one at a time and see that the Oracle path does something different from the others.

That's analysis that goes across files and modules. Semgrep doesn't do that - it works on one file and one pattern at a time. It saw the query builder and then moved on.


How we found it

Our Tier 2 analysis goes beyond file boundaries. It makes a map of how data moves through the whole app.

For NocoDB, this meant tracking user input from API endpoints through middleware, into the database service layer, and then into each database client one at a time. The analysis found the concatenation 17 times, in 17 different places, all because of the same problem.

We checked that each one could be reached from a secure API endpoint. We checked to see if the injection could be used. We wrote down the path of the attack. After that, we told someone.


The other four

The SQL injection was the main story, but it wasn't the only one. We found five holes in our scan:

  • V1 - SQL Injection in Oracle Client (Critical) The one I talked about. Needs the Org Creator+ role. PR #12748.

  • V2 - Bypassing WebSocket Authentication The WebSocket auth middleware doesn't show any errors and lets all connections through anyway. This mostly just messes up analytics in the open-source version; WebSocket only handles telemetry. But in Enterprise, where real-time data sync happens over WebSocket, attackers can get live database updates even if their tokens are invalid or have expired. PR #12749.

  • V3 - SSRF in Attachment Upload (High) The downloadAndStoreAttachment function gets URLs from users and gets them from the server. There is no SSRF protection. As an editor, you can make the server hit cloud metadata endpoints, internal APIs, or any other endpoints you want. PR #12750.

  • V4 - SSRF in uploadViaURL (High) Same story, but a different code path. There is no SSRF filtering on the axios.head() call in uploadViaURL(). The funny thing is that the next function in the chain, fileCreateByUrl(), has SSRF protection. The first request, HEAD, does not. PR #12751.

  • V5 - Errors That Reveal Information (Medium) Error objects that haven't been processed are dumped straight into API responses. Paths to servers, usernames for systems, details about the OS, and internal IDs. This is useful information for anyone who wants to plan a bigger attack. PR #12752.

None of these five were found by Semgrep. It did find 222 other things, though.


The timeline

We follow responsible disclosure, so this is what happened.

December 10, 2025

We sent the full report to security@nocodb.com. Everything: technical details, steps to reproduce, suggested fixes, and more.

December 16

Follow-up to remind them that their 7-day SLA was coming up. No answer.

December 19

The SLA expired without anyone saying anything. We went ahead and made the fixes public by sending them in as PRs #12748 through #12752.

NocoDB then made changes to their own private internal branches.

I wish the communication had gone more smoothly, but the problems are fixed, and that's what matters.


What this really means

I keep going back to that comparison table because it's hard to argue with:

Metric

Traditional SAST

Semantic Analysis

Total findings

222

5

True vulnerabilities

0

5

False positives

208

0

Needs review

14

0

Signal rate

0%

100%

222 warnings. None of the critical vulnerabilities were found. Five real problems, all missed.

This isn't a problem with NocoDB or Semgrep. There is a problem with the category. Semantic vulnerabilities are the most dangerous ones in modern codebases because the code compiles, runs, and looks fine. The problem is with what the code means, not how it looks. And tools that only look at syntax can't find them because of how they are made.

We found the same pattern in all 45 of the repositories we looked at. 225 holes in security. 90.24% of maintainers agreed. You can find every assessment with all the information at kolega.dev/security-wins.

The NocoDB case is the one where the numbers are easiest to see.

Simple 3 click setup.

Deploy Kolega.dev.

Find and fix your technical debt.