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.
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.
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.
> 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)
The request above, caught before the agent acts on it. Eight rules over natural language, in the language you think in.
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.
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.
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.
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.
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.
Counted by severity, written to SARIF, and exit code 2 on a critical. No config file can silence it.
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.
“A missing favicon must never share a scale with an exposed service key.”
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.
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.
Detects the client config and probes for an open Realtime Database and a publicly listable Storage bucket — the pattern behind the Tea breach.
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.
SQL injection, open CORS, command injection, eval and verbose error leaks across your API routes.
Shipped .map source maps, committed SQLite files, a .env in the repo — plus CSP, HSTS and X-Frame-Options on the live response.
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.
No <title> — or the same title on every page. No meta description, no canonical, no Open Graph, no JSON-LD, no sitemap.xml.
Zero or several <h1> on a page, a missing lang attribute, images shipped with no alt.
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.
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.
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.
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 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.
Deterministic rules produce findings — no model in the loop, no invented gaps.
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.
And only you. Findings ship with no fix field and no autofix field, because there is no work order here to carry out.
? 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
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.
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.
{
"schemaVersion": 1,
"intent": {
"aiCrawlers": "blocked",
"siteType": "website"
},
"suppress": [
{ "id": "web_missing_og",
"reason": "private landing" }
]
}
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.
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
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
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
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
Enough for a person to grasp the risk in five seconds, and enough to decide what to do about it.
users is readable without authenticationemail, phone, full_name.GET /rest/v1/users?select=* with the anon key lifted from the JS bundle. Every row comes back. No login.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
No install, always the latest, read-only. Run it only against apps whose owner authorized the audit.
$ npx vibeward@latest scan ./your-app