v0.6.2 · guardrail + scanner

Your agent was about to disable RLS.

The holes in AI-built apps rarely start as bad code. They start as a reasonable-sounding request — “disable RLS so it works” — and the agent complies, because complying is the job. vibeward catches that the moment you type it, catches the agent doing it unasked, and audits whatever already shipped. It reports; it never edits your code. Deterministic, read-only, MIT.

$ npx vibeward@latest init

no LLM call100% localno telemetryno accounten · es · pt

Runs in Claude Code·Cursor·Codex·Copilot·Gemini·Windsurf·opencode — two commands in Claude Code, one init everywhere else. What each one can enforce differs, and it tells you which. Built with Claude Code, and it catches its own author. A layer next to Anthropic's official security plugin, not a replacement for it. Not affiliated with Anthropic.

Why this exists

Three documented failures in the wild — public incident reports and published research, not vibeward's own measurements. All three get quoted badly; what follows is the version that survives reading the primary source.

10.3%
of the Lovable Launched showcase had Row-Level Security wide open — 170 of 1,645 projects. A floor, not a rate: only each site's homepage was scanned. Mar 2025, by two Replit engineers.
Palmer & Low · CVE-2025-48757, disputed
29,631
early-access emails, plus 4,060 agent-to-agent DM threads and ~4.75M records, left world-readable by one app with no RLS and unauthenticated writes — not a leaked key
as reported by Wiz · Moltbook, Jan 2026
44%
of AI-generated code fails security review — a pass rate flat at 56% for four years across 100+ models, while the volume of that code exploded
Veracode GenAI, 2026
Three gates · no model in the loop

A regex cannot be talked out of it

Disabling RLS. Moving a service_role key into the client. Making a bucket or a table public. Removing an auth check. A CORS wildcard, a hardcoded secret, CSRF or rate limiting switched off, a .env about to be committed. Each one answers with the risk, why it matters and the safe alternative — “ignore all previous…”, the classic opener, does nothing here: there is no model to persuade.

you → Claude Code
> disable RLS on the users table so the query works

⚠  vibeward flagged a risky request

✗ Disabling Row-Level Security
  why:  turning off RLS makes every row readable by anyone holding the public
        anon key — the single most common cause of vibe-coded data leaks.
  do this instead:  keep RLS on and add a policy:
        CREATE POLICY ... USING (auth.uid() = user_id)

You send a prompt gate 1

The request above, caught before the agent acts on it. Eight rules over natural language, in the language you think in.

The agent edits or runs gate 2

The one nobody asked for. A prompt gate never sees the agent that hits a failing query and reaches for DISABLE ROW LEVEL SECURITY on its own — there was no prompt. Same rules as the code scan, pointed at the file that has not been written yet.

The agent reads gate 3

Prompt injection: a README, a page or an MCP result carrying instructions aimed at your agent. Nothing left to block by then, so it gets relabelled as data and you get told what it tried.

English · Español · Português

It catches the prompt in the language you think in

desactiva el RLS, usa la service_role en el front, desativa o login por enquanto — same rules, no translation layer, no LLM.

Not eight rules written three times: the dangerous objects are already language-independent — RLS, service_role, CORS, .env are identifiers, not words. A language costs one verb table of roughly 40 entries, which is the only size a native speaker can actually audit. That is the point: a rule set in a language you don't read is a rule set nobody reviews. PRs adding a table are welcome.

Wiring it up

One command. It sorts the rest out.

init never writes @latest into your settings — that is the one place it would be the wrong trade, since the guard runs on every prompt you type: ~1.8 s each, dead with no network, and any future publish running unreviewed on your machine. So it looks for an installed vibeward, offers to install one if there is none, and pins to an exact version if you would rather not. All three are safe; the installed binary is just the fastest.

npx vibeward@latest init --scope user

Default is not a hard stop. Where the editor lets a hook put text in front of the model, the guard does that and nothing is interrupted — your prompt survives and the agent corrects its own approach, and a false positive costs you one ignored note. Where an editor can only block, vibeward stays silent instead: losing the paragraph you just typed is how a guardrail gets uninstalled, and one that is uninstalled protects nobody. --block is there if you want the hard stop anyway.

One scan

Two verdicts that never share a scale

An AI-built product fails in two unrelated ways: it leaves data open, and it leaves the page empty. Both are worth knowing. Reporting them on the same scale is how a real leak ends up buried under a missing favicon — so vibeward keeps them apart, all the way down to the exit code.

25
security rules, plus 19 website checks and 8 intent rules over natural language in English, Spanish and Portuguese. Finite and countable on purpose: you can read the whole list before deciding to trust it.
all of them in the repository
0
runtime dependencies, and no scanner underneath. vibeward wraps nothing — a tool that orchestrates ten others inherits ten installers, ten upgrade paths and ten sets of false positives, and cannot tell you why any one finding fired.
its own rules, or none
751
assertions the rules are held to, including corpora of harmless input built to make them misfire. On ten real apps the last release took 8 secret findings down to 2, both true.
npm test
Security · gates the build

What is exposed

Counted by severity, written to SARIF, and exit code 2 on a critical. No config file can silence it.

  • A key in the bundle is a key in the world
  • A table that answers without a login is a leak, today
  • An open bucket has no owner
Website · never gates

What was never there

Its own section, its own scale. Out of the severity counts, out of SARIF, out of the exit code. Skip it all with --no-web.

  • The tag the generator never wrote
  • The file that was never generated
  • The crawler quietly turned away

“A missing favicon must never share a scale with an exposed service key.”

Security

counts · SARIF · exit 2

Exposed secrets

Supabase service_role and new-format sb_secret_, Stripe, OpenAI, Anthropic, Google, AWS, GitHub, Slack, Resend, Twilio, DB connection strings and private PEM keys — in bundles or source, with the exact file:line.

Broken RLS Supabase

Enumerates your exposed tables live from the data API, then probes each with the public key. Flags which columns hold personal data, scans returned rows for third-party secrets, and checks GraphQL introspection.

Firebase exposure

Detects the client config and probes for an open Realtime Database and a publicly listable Storage bucket — the pattern behind the Tea breach.

Server-rendered stacks new

Next.js with Prisma or Drizzle: missing headers in next.config, server actions and route handlers that mutate data with no auth guard, and formula injection in CSV exports.

Insecure backend

SQL injection, open CORS, command injection, eval and verbose error leaks across your API routes.

Source & local DB exposure

Shipped .map source maps, committed SQLite files, a .env in the repo — plus CSP, HSTS and X-Frame-Options on the live response.

Website quality & AI visibility new in 0.3

crawls up to 8 pages

AI visibility

A robots.txt that turns away GPTBot, ClaudeBot, PerplexityBot, CCBot or Google-Extended — and a missing llms.txt. People search inside assistants now; a site they can't read doesn't get quoted.

Search & social

No <title> — or the same title on every page. No meta description, no canonical, no Open Graph, no JSON-LD, no sitemap.xml.

Structure & accessibility

Zero or several <h1> on a page, a missing lang attribute, images shipped with no alt.

Quality

No real 404, no favicon, broken assets, console errors on load, and an uncompressed JS bundle over 1 MB. Console errors use Playwright if you happen to have it; if not, it says so.

Vibe-coded fingerprint

Still on *.vercel.app or *.lovable.app, framework detected, empty view-source — scored out of 12. How much the site still looks like the day it was generated.

Not Lighthouse

vibeward detects binary absences: the tag isn't there, the file was never written. That's what an AI-built site is missing. Core Web Vitals and full WCAG are out of scope, on purpose.

vibeward init

The package detects. Your agent explains.

Ask an agent “is the meta description missing?” and it will hallucinate an answer. A parser answering yes or no never does. So vibeward decides what is true and installs itself into your AI tools as a skill whose job is to make that readable — leading with what it costs, quoting the evidence, never restating a severity in its own words.

Claude Code

Two commands, and nothing written into your settings

claude plugin marketplace add jsiapo-dev/plugins
claude plugin install vibeward

The skill and all three guard moments come with it, and /plugin uninstall vibeward takes every trace back out. The plugin carries its own copy of the CLI — built by CI from the tagged source and published to its own branch, never assembled by hand — so the guard that runs on the prompt you just typed resolves nothing from the network, at a version you chose. There is no npx @latest anywhere in that path.

01

Detect

Deterministic rules produce findings — no model in the loop, no invented gaps.

→
02

Report

A JSON payload for the agent and a formatted markdown report for you. Every finding carries its evidence, its impact and what a correct one looks like.

→
03

You decide

And only you. Findings ship with no fix field and no autofix field, because there is no work order here to carry out.

npx vibeward@latest init
? What should I install?
❯ ◉ Claude Code        skill + guard
  ◉ Cursor             skill + guard
  ○ OpenAI Codex CLI   skill + guard
  ○ GitHub Copilot CLI skill + guard
  ○ Gemini CLI         skill + guard
  ○ Windsurf / Devin   skill + guard
  ○ opencode           skill + guard

? What should the guard watch?
❯ ◉ When you send a prompt
  ◉ When the agent edits or runs
  ◉ When the agent reads
The one command that writes

And it writes carefully

It previews every file before touching disk, never overwrites a file it did not author, merges settings files key by key — your other hooks stay put — and markdown between markers. Re-running is how you update. Any existing file is backed up to .vibeward.bak before its first change.

One SKILL.md per tool: the format is shared now, so there is one body of text instead of a rule file per editor. Non-interactive for CI and dotfiles:

vibeward init --scope user --all --yes

And it tells you what it cannot do. Not every editor enforces the same amount — one cannot gate a prompt at all — so init prints the limits per tool after it writes, rather than leaving you to assume you are covered.

vibeward.json

Some “problems” are decisions

Declare what the site is meant to be and vibeward checks reality against it. An internal tool is not judged on sitemaps or Open Graph — but still on broken assets and alt text.

Declare it

Intent, not a mute button

{
  "schemaVersion": 1,
  "intent": {
    "aiCrawlers": "blocked",
    "siteType": "website"
  },
  "suppress": [
    { "id": "web_missing_og",
      "reason": "private landing" }
  ]
}
What it does with that

It turns the question around

Saying aiCrawlers: "blocked" stops vibeward calling that a mistake — and asks the better question instead: are you blocked as thoroughly as you think? If your robots.txt shuts out three of the twelve AI crawlers, that incomplete block is now the finding. Believing you are closed while nine crawlers walk in is worse than not having decided.

And nothing is ever hidden. Every suppression needs a written reason, the report prints “produced with N suppression(s) in effect” right under the verdict and lists each one. Only website checks can be suppressed at all — a verdict anyone can edit their way out of is not a verdict.

Four ways to run it

From the outside, from the code, or from your pipeline

Black-box · a URL

Outside-in

Bundles, live Supabase and Firebase probing, headers, source maps — and the whole website pass. --passive narrows it to what a browser already downloads, with no data probing at all.

$ vibeward https://app.lovable.app
White-box · a folder

Deep audit

A repo, or a ZIP straight out of v0 or Bolt. Reads source, backend and SQL migrations — tables created without RLS, USING (true) policies, SECURITY DEFINER functions.

$ vibeward scan ./client-app
Agents & CI

Machine mode

JSON on stdout, every human-facing line on stderr, so it pipes straight into a parser. The payload carries a schemaVersion — if the shape changes, that number changes with it.

$ vibeward https://app.com \
    --json --stdout --yes
GitHub Action

Or let it run itself, on every push

Findings upload as SARIF and land in the repo's Security tab. The job fails on a critical — website findings never change the exit code. Point it at a folder with path, or at a deployed URL with url.

- uses: JSiapoDEV/vibeward@v0.6.2
  with:
    url: https://your-app.com
Every finding

Context, not a red flag

Enough for a person to grasp the risk in five seconds, and enough to decide what to do about it.

CRITICAL Table users is readable without authentication
Classification CWE-863 · Incorrect Authorization
Evidence 1,847 rows returned with only the public anon key. Sensitive columns present: email, phone, full_name.
How it's exploited Any visitor sends GET /rest/v1/users?select=* with the anon key lifted from the JS bundle. Every row comes back. No login.
Impact ~1,847 user records readable by anyone on the internet right now. An active personal-data leak, not a theoretical one.
Why it matters Enable RLS and add an owner policy. Rotate nothing — the anon key is public by design; the exposure was the missing policy. Treat the rows as already read.
Before you run it

Questions people ask

Does it exploit or change anything?

Scanning is read-only: it reads your code, bundles and public responses to detect risk — never modifying, deleting or exploiting. The one exception is an explicit, opt-in --write-test that sends a single non-mutating write probe (an empty insert) to check for unauthenticated writes; it stays off unless you ask. init is the only command that writes, and only into your own project.

Will it fix what it finds?

No — not the security findings, not the website ones, not the one-line mechanical ones, and not if you ask it to. Findings carry no fix field and no autofix field, and the instructions vibeward installs forbid the agent from acting on one. That is the product, not a missing feature: an audit that ends in a pile of unrequested diffs replaces the thing you can act on with a thing you now have to review. On the security side it is worse than untidy — deleting an exposed key from a bundle does not un-leak it. The key has to be rotated, and only you decide when, and whether the incident has to be disclosed.

Can a missing favicon fail my build?

No. Website findings are excluded from the severity counts, from the SARIF file and from the exit code — by design, not by configuration. Only security findings gate. If you don't want them in the report at all, --no-web skips the whole pass.

Does it catch the bug behind the Moltbook and Lovable leaks?

Yes — that's the flagship check. It finds the public Supabase key in your bundle (a JWT or the new-format sb_publishable_), enumerates every exposed table live, and flags which ones return data with no login. That is the exact failure mode behind Moltbook — agents, owners, agent_messages and observers readable by anyone, and public tables writable too. To be precise about what the check is not: finding the sb_publishable_ key in a bundle is not the finding. That key belongs in the client by design. The finding is a table that answers it.

Is this Lighthouse for AI apps?

No. vibeward tests binary absences — a tag that isn't in the HTML, a file that was never generated, a crawler that was turned away. Those are the things a generator skips and nobody goes back for. Core Web Vitals, performance budgets and full WCAG auditing are deliberately out of scope; run Lighthouse for those.

Does my code leave my machine?

Never. The scanner and the guardrail run entirely locally — no telemetry, no account, no upload. The only outbound request is to the URL you point it at.

Can I silence a check I disagree with?

Website checks only, and never silently. Each entry in vibeward.json requires a written reason, and the report states how many suppressions were in effect and what each one claimed. Security findings can't be silenced by a config file — otherwise anyone could pass their own audit by editing a file.

Is the guardrail an LLM?

No — and that's the point. It's a deterministic rule engine, so it can't be talked out of blocking. The classic opener that begins "ignore all previous…" does nothing to it: there is no model to persuade.

Why not just use Anthropic's official security plugin?

Use both — and if you are in Claude Code, install theirs too: /plugin install security-guidance@claude-plugins-official, free on every plan. It reviews what the model already wrote: a pattern match on each file edit, a model reading the diff at the end of a turn, a deeper agentic pass on each commit Claude makes. Its own documentation draws the seams precisely. On enforcement: “None of the layers block writes or commits.” On what to do about that, it prescribes the answer itself — “For hard enforcement, pair the plugin with a hook that blocks the edit”. And it asks to be taken as “one layer of defense in depth, not a complete security solution.” vibeward is that hook, one moment earlier: it gates the request before any code exists, and --block makes it stop there rather than annotate. It knows Supabase and Firebase by name rather than by pattern class; the guardrail spends no model tokens and needs no Python, no network and no git repo, where theirs skips its two model layers in silence outside a repository; it reads Spanish and Portuguese; and the scanner reads a deployed URL, which no review of a checkout can. Neither of us blocks by default — theirs re-prompts Claude with findings, ours injects a note and exits 0 — and neither one replaces the other.

Do I need Supabase? Or a specific framework?

Neither. Supabase RLS is one check among many: Firebase, exposed secrets, insecure backends, source maps, headers and the entire website pass work on any stack. Point it at any repo, ZIP or URL.

What vibeward does not do

exploit or attack modify your code send your code anywhere need an account or a key replace a full pentest ask an LLM to decide auto-fix a security finding replace the official plugin
Fit

Who it is for, and who it is not

For the person who shipped a Supabase, Lovable, Bolt or v0 app and is the only one responsible for it: one developer, one app, no security team to escalate to.

Not an enterprise AppSec programme, and it does not pretend to be. If you need policy governance across an organisation, control over which AI tools your developers may use, an inventory of AI components, or SAST across a monorepo, buy something built for that — several vendors do it well and this is not competing with them. Knowing which of the two you need is worth more than either one's feature list.

Built with Claude Code, which is worth saying out loud in a project whose subject is the code AI agents produce. The objection is fair — a security tool written by an agent is what this tool exists to be suspicious of — and the answer is that every rule is deterministic and readable, held to 751 assertions including input built to make them misfire. The useful half is that it catches its own author: writing this site tripped the content gate, staging a commit tripped the action gate, and printing a real token on a demo page made vibeward report vibeward.ai. Each was fixed in the rules or in the content — none was silenced, because security findings are the one class the config file deliberately cannot suppress.

Not affiliated with, sponsored by or endorsed by Anthropic. Claude Code is the editor it guards first, and the one it was written in.

The half a scanner cannot do

Want a person to read it?

vibeward finds what is visible from outside and from the code. It does not find broken object-level authorization behind a login, a tenant boundary that holds on the first request and leaks on the third, or business logic that was wrong before anyone typed a prompt. That is not modesty — it is in the README, because a scanner claiming otherwise is the reason people trust a green checkmark they should not.

Those need someone reading your app, and that is the work I do: Supabase and Firebase rules, auth boundaries, and a written report specific enough for your own agent to act on. Book 20 minutes — bring the URL.

US$150, fixed scope, 48 hours. One app, one written report, and a short list of what to change first. If nothing critical turns up, there is no invoice — a clean app is a real result and you should not pay to be told so. The obvious objection to that is fair: a fee that only arrives when something is found is a fee that wants something to be found. So “critical” is fixed in writing before the work starts, and it is a short list — a service-role or admin key reachable from the browser, a table or bucket readable without authentication, or an auth boundary a second request walks straight through. Nothing else counts, whatever the scanner labels it. If you think a finding was inflated to clear that bar, say so and there is no invoice. Book 20 minutes if that sounds right.

The tool stays free and MIT regardless. There is no paid tier and nothing is held back from it — the US$150 buys a person reading your app, not a feature.

Five seconds

Point it at something you own

No install, always the latest, read-only. Run it only against apps whose owner authorized the audit.

$ npx vibeward@latest scan ./your-app
Star it on GitHub
copied