Technical PreviewNot ready for production. Breaking changes and rough edges to be expected — share feedback.

The web framework that turns security bugs into build errors

Make security holes a build error -- not a 2AM incident.

The Kovo compiler catches the most common security vulnerabilities -- SQL injection, XSS, CSRF, IDOR -- as soon as your coding agent writes them.

$ npx create-kovo
Start the tutorial
Every other framework
attacker submitsSQL runsdropped ✗
kovo
check
What Kovo adds
caught at buildharmless ✓

Sign up · name

Robert'); DROP TABLE users;--

users3 rows3 rows✗ droppedcaught at build✓ intact

idname
1ada
2grace
3linus
··!Three users. A sign-up form that stores your name.An attacker submits a name that is really SQL.✗ the string executes -- DROP TABLE users. Everyone is gone.✗ KV422 -- a build error in Kovo. The query never shipped.-> parameterized: where(eq(users.name, input.name))✓ the input is bound as a value -- stored as a name, no SQL run.

Secure by construction

The unsafe line never compiles.

Pick a vulnerability class. The compiler traces untrusted input to the dangerous sink and answers before the code runs: the exact line, the rule, and the fix.

app/routes/signup.tsx$ kovo check · 0.2s
// name comes straight from the sign-up form (untrusted)
db.query(`select * from users where name = '${input.name}'`)
✗ KV422 signup.tsx:14 untrusted input reaches SQL as text
sourceform field name · request body
sinkraw SQL string in db.query()
fixdb.query(sql`select … where name = ${input.name}`)
app/routes/comment.tsx$ kovo check · 0.2s
// comment body is user-submitted (untrusted)
<article>{ raw(comment.body) }</article>
✗ KV424 comment.tsx:22 untrusted value reaches an HTML sink
sourcecomment.body · request data
sinkraw HTML output
fix<article>{comment.body}</article> escapes by default
app/routes/transfer.ts$ kovo check · 0.2s
// money movement, but CSRF is switched off
endpoint('/transfer', { csrf: false }, (req) => pay(req.session))
✗ KV418 transfer.ts:8 csrf-exempt endpoint depends on the session
sourcecross-site POST · forged
sinksession-authenticated mutation
fixendpoint('/transfer', (req) => pay(req.session))
app/domain/invoices.ts$ kovo check · 0.2s
// invoice id comes from the URL (client-supplied)
db.select().from(invoices).where(eq(invoices.id, params.id))
✗ KV414 invoices.ts:17 owner-table read not scoped to the session
sourceparams.id · client-supplied
sinkowner-scoped table read
fix.where(and(eq(invoices.id, params.id), eq(invoices.userId, session.userId)))

No stale UI

Your UI can't disagree with itself.

Add to cart, and every view of the cart agrees by construction. Kovo owns the path from the database to the DOM, so a view that could drift out of date is a compile error, not a bug your users find later.

And nothing is wired by hand: declare what a view reads, and the compiler invalidates exactly the views a mutation touches -- no cache tags, no invalidateQueries, no useEffect.

Every other framework
idleadd to cartstale, shipped ✗
kovo
check
What Kovo adds
caughtconsistent ✓
Northwindcart 23
Aeron Chair
$48.00

Your cart

Items23
Aeron Chair×23
Subtotal$96$144
+!Two views of one cart, in agreement.Added 1 to the cart, refreshing the views...Header shows 2, cart shows 3 -- the stale UI most frameworks ship.KV251 -- cart/add updates items, but the header reads cart.count and isn't in its touch set.-> add cart.count to the cart/add touch setkovo check passed -- the header, cart, and subtotal are one fact.
✗ Other frameworkscart.ts
async function addToCart(item) {
  await db.cart.add(item)

  // remember every view that reads cart:
  invalidate('cart')
  invalidate('cart-badge')
  invalidate('free-shipping')
  // miss one and it silently goes stale.
}
Forget one invalidate(...) and you ship the stale bug -- the badge that disagrees with the cart.
✓ In Kovocart.ts
// a view reads what it needs:
const total = cart.total

// the mutation only writes:
mutation('cart/add', (item) =>
  db.cart.add(item))

// every view that reads cart refreshes.
// nothing to invalidate. checked at build.
The read set is the invalidation set. Add a view and it is already wired -- diffable in CI.

Instant load

Interactive at first paint. No uncanny valley.

No hydration means no window where the page looks ready but ignores your clicks. The JavaScript you do use loads on first interaction, not on load. Turn JavaScript off and every page still renders, every form still posts.

0 ms

Time-to-interactive equals first paint.

JS off

Every page renders, every form posts.

Typical SPA3.2s

⚠ looks ready, ignores clicks until 3.2s

SSR + hydration1.6s

⚠ frozen until the bundle hydrates

Kovofirst paint

✓ every click works at 0ms, handlers load on demand

Batteries included

Everything from the database to the DOM.

Kovo owns the whole path: a Drizzle row becomes a DOM node, and the types follow it the entire way. It does not reinvent the foundations. It stands on libraries you already trust and type-checks the seams between them.

Built on · shipped Inspired by · borrowed
top of stackwhat the browser gets
DOMReal HTMLServer-rendered, interactive at first paint. No hydration, no client router.
Components · inspiredshadcn/uiYou own the component source. Copy it in, read it, change it.
Styles · inspiredStyleXAtomic CSS compiled at build time, zero runtime. This page ships its styles the same way.
CompilerTypeScriptNo new language to learn. tsc is the engine every guarantee runs on.
BuildViteDev server and bundler. Kovo's compiler runs as a Vite plugin, and HMR morphs the DOM in place.
AuthBetter AuthSessions, accounts, providers. Kovo traces ownership into every query.
DatabaseDrizzleYour schema and queries, fully typed. The same types feed the compiler.
bottom of stackwhere the data lives

Who builds this

Made by people who build AI tools for a living.

Kovo comes from the team behind Dyad, the open-source, local AI app builder with 20k+ stars on GitHub. We built Kovo because we wanted a target our own agents could generate and verify without guessing.

Why yet another web framework?+

Because the stale-UI bug class and the hydration gap are still unsolved at the framework level. Kovo turns “this view drifted out of sync” into a compile error and makes first paint interactive. If your stack already proves those two things, you do not need Kovo.

Can AI agents actually write Kovo code?+

That is the whole design goal. Generated apps fail tsc when wiring is wrong, and kovo check returns the exact line, the reason, and candidate fixes. The agent loops on edit, check, fixed -- not edit, deploy, bug report. Skills, an MCP server, and LLM-readable docs ship with it. That is not a claim of prompt-injection immunity: the framework narrows blast radius with default-deny guards, structured sinks, and the egress floor, but an app that lets a model read hostile content or call tools still needs its own LLM01 posture.

Do I have to throw away React?+

You keep the model you know: composable components, props, TypeScript. You give up the client router, hydration, and the runtime store. Kovo compiles your components to real HTML and wires interactivity on demand.

Is it production-ready?+

Not yet. Kovo is pre-v1 and under active implementation; nothing is published to npm. The spec, the conformance suite, and this site are open -- follow along and kick the tires.

If this resonates, star it.

Stars tell us the problem is worth solving and help other builders find Kovo early.

Star kovojs/kovo