Skip to main content

Command Palette

Search for a command to run...

The Week My Database Learned to Defend Itself

Updated
8 min readView as Markdown
A

System-level engineer building reliable backend systems with a focus on performance, correctness, and real-world constraints. I work across APIs, databases, networking, and infrastructure, enjoy understanding how systems behave under load and failure, and write to break down complex backend and distributed-systems concepts through practical, real-world learnings.

How AxioDBCloud went from an open TCP socket to a TLS-encrypted, authenticated, pooled, and AI-drivable system in seven days.


There is a specific kind of discomfort that comes from looking at your own code and realizing it trusts everyone.

That was me last week, staring at AxioDBCloud.

Quick context first. AxioDB is my open-source embedded NoSQL database for Node.js. Think SQLite, but with MongoDB-style queries and zero native dependencies. For most of its life it ran embedded, sitting inside your app, reading and writing files on your disk. Then I added AxioDBCloud, a TCP layer that lets you deploy AxioDB in Docker and talk to it over the network using the exact same API you already knew.

Great for a demo. Also, quietly, a liability.

Because the version I had shipped worked like this: it opened a TCP port and answered anyone who knocked. Nothing encrypted the wire. Nothing checked who you were. Concurrency was basically hope. It ran fine on my laptop, which is the most dangerous sentence in all of software.

So I gave myself one week and a goal that is easy to say and annoying to build: turn a connection into infrastructure.

Here is how the seven days went.

Day one: locking the front door

The first problem was not performance or scale. It was that the door had no lock.

So I started with authentication, on two fronts at once.

The web GUI, the little dashboard that ships with AxioDB, got proper session-based auth. Cookies, login and logout, password change, and a real role system behind it all. I built out an AuthController, a RoleManagementController, and a UserManagementController, wired a permission middleware in front of the routes, and added a few boring but important guardrails like reserved database-name checks so nobody can clobber internal state by naming a database something cursed.

Then the harder half: authentication over TCP. The remote layer needed the same idea, a real login, an AuthenticatedUser concept, and role-based access control that travels with the connection. While I was in there, I also added index management commands over the wire, including a LIST_INDEXES command, because a remote client that cannot see its own indexes is only half a client.

By the end of day one, AxioDBCloud finally cared who you were. Small thing. Felt like the whole week turned on it.

Day two: the wire and the flood

Knowing who is connecting does not help if anyone in the middle can read the conversation, or if one loud client can knock the server over. So day two was about the wire and the load.

First, TLS. I added optional TLS/SSL for TCP connections, with TLSCertPath and TLSKeyPath options and client-side CA verification, so the traffic between client and server can be encrypted and the client can actually verify it is talking to the right server. Encryption on the wire is one of those features nobody notices until it is missing, and then it is the only thing they notice.

Second, backpressure. I added a per-IP concurrent connection cap and rate limiting on connection attempts, plus a brute-force-resistant LoginRateLimiter sitting in front of the auth path. One client, whether hostile or just buggy, should not be able to exhaust the server by opening sockets or spamming logins.

Third, and this was the fun one: connection pooling. Instead of treating every request as a lonely one-off, AxioDBCloud now keeps a managed pool of connections ready to work. But I did not want naive round-robin, which happily hands your next request to a connection that is already buried under slow work. So the pool uses least-busy routing: a new request goes to the connection with the fewest in-flight calls. The pool tracks who is busy and dispatches to whoever has the lightest load right now.

That single choice, least-busy instead of round-robin, is the difference between a pool that smooths out load and a pool that just reshuffles the same traffic jam.

I also made the security work non-negotiable going forward by adding RBAC and TCP-auth test gates to CI. If a future me breaks auth, the pipeline yells before anyone ships it.

Day three: handing an AI the keys, carefully

By day three the connection was secured, pooled, and routed. So I did something that would have been reckless to do on day zero: I let AI agents drive the database.

I shipped an MCP server. MCP, the Model Context Protocol, is the emerging standard for letting AI agents call tools in a structured way. AxioDB's MCP server exposes 32 tools over a Streamable HTTP transport: full document CRUD, aggregation, index management, dashboard stats, and user and role administration. It is opt-in inside the Docker image, off unless you set AXIODB_MCP=true, and it listens on its own port so it never gets tangled up with the GUI or the TCP layer.

Here is the part I care about most. Every single one of those 32 tools is gated by a real login and the logged-in user's actual RBAC role. An agent has to call axiodb_login first, and from that point on it can only do what that human could do. Nothing more. The MCP surface mirrors the HTTP control server's permissions exactly. There are session tools too, axiodb_logout, axiodb_whoami, axiodb_change_own_password, so an agent's session behaves like a person's session.

I think this matters more than the tool count. It is easy to bolt an AI interface onto a database and accidentally hand it god mode. The interesting problem is not "can an agent query my data," it is "an agent should get exactly the permissions the human behind it has, and not one grant more." That is the version I wanted to ship.

Running the whole thing now looks like this:

bash

docker run -d \
  --name axiodb-server \
  -e AXIODB_MCP=true \
  -p 27018:27018 \   # HTTP GUI dashboard
  -p 27019:27019 \   # TCP remote access (AxioDBCloud)
  -p 27020:27020 \   # MCP server for AI agents
  -v axiodb-data:/app \
  theankansaha/axiodb

Three ports, three ways in, all sitting behind the same auth and role model.

The bugs I met along the way

The part nobody puts in the highlight reel: I found real bugs while doing all of this, some of them a little embarrassing, and I would rather write them down than pretend they never existed.

The one that made me wince: collection metadata responses were leaking the raw AES encryption key. An endpoint meant to describe a collection was quietly handing back the key that protects it. Fixed.

The sneaky one: delete-by-query would report success while deleting nothing, if the isMany flag was left unset. The worst kind of bug, the kind that lies to you and looks fine in the logs. Fixed.

The math one: aggregation with $sum or $avg crashed when you passed a numeric literal, like { $sum: 1 }, which is a completely normal thing to write. Fixed.

The counting one: total document count was including the collection's internal indexes folder in the total, so your numbers were subtly wrong. Fixed.

And a small bit of housekeeping: I ripped out a pile of dead chmod-based file and directory locking code that was sitting in the engine but was never actually engaged by any real flow. Deleting code you were once proud of is its own kind of progress.

What the week actually taught me

If I zoom out, the whole week was one move repeated in different clothes: turning a thing that worked into a thing you can trust.

An open socket became an authenticated one. A plaintext link became an encrypted one. A single fragile connection became a pool that routes to whoever is least busy. A database an AI could theoretically wreck became one where the AI inherits a human's exact, limited permissions.

None of these are glamorous features. You cannot really screenshot "we no longer leak the encryption key." But this is the unglamorous middle distance where a side project either grows into something people can actually deploy, or stays a demo forever.

I am self-taught, no CS degree, and for a long time I assumed the "serious" database internals, TLS handshakes, RBAC, connection pooling, protocol design, were things other people built. Turns out they are just problems, and problems yield to a week of stubbornness like anything else.

AxioDB is open source and MIT licensed. If you want to poke at any of this, the docs walk through AxioDBCloud, the Docker setup, the security model, and the MCP server:

And I will leave you with the question that shaped the hardest day of the week: if you let an AI agent connect to your database, what should it be allowed to do, and what should it never be able to touch? I would like to hear how other people are drawing that line.