Inspiration
Anyone can build an app with Claude Code now. A salon owner can describe a booking site and have it running in an afternoon. But the moment that app has two customers, it has a security problem, and the person who built it has no way to see it.
The classic small-app breach isn't exotic. It's a query that forgot WHERE owner_id = ?, a page that loads /bookings/1234 without checking whose booking 1234 is, and a later edit that quietly drops a check that used to be there. When we tested plain vibe-coded builds, they often got the basics right. The problem is that nothing guarantees it, and the builder can't review the code to find out.
So we asked: what if the rules that keep one customer's data away from another's didn't live in the AI-written code at all?
What it does
Basalt is a platform of fixed, secure building blocks that sits underneath apps built with Claude Code. The builder works the normal way: they describe what they want to Claude. Claude builds, tests and ships through one MCP connector. Basalt enforces the ground rules below the app's code, in Postgres and at the edge, so whatever the AI writes, it can't leak data.
There is no AI inside Basalt. The rules are fixed and checked the same way every time.
- Basalt Auth: sign-up, sign-in, sessions and MFA. Passwords, tokens and cookies never pass through app code.
- Basalt API: a backend framework where every route requires sign-in by default and
ctx.sqlruns inside the user's own row rules. - Data block: the only way an app touches its database. Six calls (get, list, create, update, archive, restore) plus file uploads. Postgres itself verifies the login token and only ever returns that person's rows, or rows explicitly shared with them.
- Basalt Cloud: one locked-down database per app. Anything that would show data to more people waits for the owner to tap "Yes" on a plain-English approval page.
- Guard: on every deploy, Guard signs in as two people and a stranger and attacks the live app's data. A failed check stops the deploy, and the owner gets a safety receipt that lists every check in plain words.
- Basalt Host: build, test, preview, stage and ship on one machine, with sandboxed builds, private previews, instant rollback and an owner dashboard.
How we built it
Everything is TypeScript on Node, Postgres 17 and Redis, run with Docker Compose.
- Login checked inside the database. Every access token carries a data grant. Postgres verifies its signature itself, with a verifier written against built-in
sha256()and no extensions. The Data service never holds the signing secret, so even a fully compromised Data service can't impersonate a user. - One door in. The Data service's database login can call exactly one function,
basalt.api(), and nothing else: no tables, no other schemas, no temp tables, no sleep. That function runs as a role nobody can log in as and only touches tables listed in a registry generated from the app's data contract. - Forced row-level security on every table, with policies rebuilt from scratch on every migrate, so a stale or planted policy can't survive.
- Damage limits and an audit log inside Postgres. Reads and writes per user per minute are counted before an operation runs, so they hold across every service instance, even against someone who took over the service.
- Auth uses Argon2id password hashing, Ed25519 JWTs with per-project keys, rotating HMAC refresh tokens with theft detection, HttpOnly
__Host-cookies, TOTP MFA, anti-enumeration and layered rate limits. Revoked sessions sync into every app database within 10 seconds. - Hosting: an edge that routes by host and HMAC-signs requests to backends, which live on per-environment internal networks. An egress proxy blocks cloud metadata, LAN and non-web ports. A Playwright browser worker lets Claude drive the app in a real browser. Cloudflare Tunnel is the only ingress.
- The MCP connector (
@basalt/host-mcp) installs from the platform's own registry withnpx, so the builder has nothing to install. - We built it with Claude Code, and Codex reviewed it in several rounds. We fixed what it found, and the open items are listed under Known limits in the repo README.
Challenges we ran into
- Verifying JWTs in pure SQL. We wanted Postgres itself to check every login, with no extensions, and strict enough to reject
alg: none, payload swaps, non-canonical encodings, string or fractional expiry and over-long lifetimes. - Making the attack demo impossible to fool. Review found "false greens": an outage, a validation error or another customer's row tucked into page two of a search could all look like a refusal. The demo now treats anything that isn't a clear "no" as CAN'T TELL. It's checked against fake backends that leak on purpose.
- Bugs you only find by deploying. Package tarball URLs pointed at an internal hostname, Chromium hard-wires
*.localhostto loopback and ignores/etc/hosts, and deleting an app mid-sync crashed Cloud because a forcedDROP DATABASEkilled connections that had no error handler. Each one now has a test. - Writing for people who don't code. Every approval page, data map and receipt had to explain who can see what in plain words, with the technical detail second.
Accomplishments that we're proud of
- A real customer, signed in with a real login, attacking from inside the app's own page, gets refused every time, and the refusal comes from the database, not from code the AI wrote.
- Guard turns security into a deploy gate a non-coder can read: a receipt, not a promise.
- One connector and zero setup: Claude builds and ships the whole app, and the only thing the human does is tap "Yes".
- A 47 of 47 end-to-end run on a fresh laptop stack.
What we learned
- Security for AI-written code has to sit below the code. Reviewing generated code doesn't scale to people who can't read it, and later edits can undo a check that was there.
- Deterministic guardrails beat clever ones. Keeping AI out of the enforcement path makes the guarantees testable.
- A security check that can't report "I can't tell" will eventually lie to you.
What's next for Basalt
- Run it on a real server with a domain and Cloudflare Tunnel (today it runs and passes everything on one laptop).
- Ship the standalone black-box tester (
basalt-tester.mjs), so anyone can point Basalt's attacks at any running app. - Test the Windows start script on real Windows machines.
- More building blocks with the same shape: payments, email and scheduled jobs, each with fixed rules the AI can't get wrong.
Built With
- claude
- claude-code
- cloudflare
- docker
- docker-compose
- hono
- jose
- model-context-protocol
- node.js
- playwright
- postgresql
- redis
- typescript
- verdaccio
- zod
Log in or sign up for Devpost to join the conversation.