ubon
Version:
Security scanner for AI-generated apps (Cursor, Lovable, Windsurf, v0). Catches hardcoded secrets, prompt injection, hallucinated imports, Server Actions / Edge runtime mistakes, and the vibe-coded vulnerabilities traditional linters miss.
213 lines (176 loc) • 18.8 kB
Markdown
# Rules Glossary
This file is auto-generated from the rule registry by `scripts/generate-rules-md.js`.
Do not hand-edit; run `npm run rules:gen` after building.
Total rules: **155**.
## Security (JS/TS)
- **COOKIE001** — Set-Cookie missing HttpOnly/Secure/SameSite ([docs](https://owasp.org/www-community/controls/SecureCookieAttributes))
- **COOKIE002** — JWT token exposed in client-side cookie without security flags ([docs](https://owasp.org/www-community/controls/SecureCookieAttributes))
- **COOKIE003** — Sensitive data returned in JSON response (potential token leak)
- **COOKIE004** — Cookie used without domain/path restrictions
- **JSNET001** — HTTP request without timeout/retry policy ([docs](https://developer.mozilla.org/docs/Web/API/AbortController))
- **LOG001** — Potential secret logged to console/logger ([docs](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html))
- **OSV001** — Vulnerable dependency detected
- **SEC001** — Potential API key or secret token exposed ([docs](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html))
- **SEC002** — Supabase URL hardcoded (should use env var)
- **SEC003** — Supabase anon key hardcoded (JWT token pattern)
- **SEC004** — Supabase URL hardcoded in variable
- **SEC005** — Supabase key hardcoded in variable
- **SEC006** — Hardcoded password detected
- **SEC007** — Database URL hardcoded
- **SEC008** — Environment variable with hardcoded fallback
- **SEC009** — AWS Access Key ID exposed
- **SEC010** — Google OAuth token exposed
- **SEC011** — GitHub token exposed
- **SEC012** — Stripe live secret key exposed
- **SEC013** — Stripe live publishable key exposed
- **SEC014** — OpenAI API key exposed ([docs](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html))
- **SEC015** — Console statement found (may leak sensitive info)
- **SEC016** — Use of eval() detected (security risk) ([docs](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/eval))
- **SEC017** — dangerouslySetInnerHTML usage (XSS risk) ([docs](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html))
- **SEC018** — High-entropy string literal (possible secret) ([docs](https://owasp.org/Top10/A02_2021-Cryptographic_Failures))
- **SEC019** — React component injection via createElement
- **SEC020** — SQL sink called with string interpolation or concatenation (SQL injection) ([docs](https://owasp.org/Top10/A03_2021-Injection))
- **SEC021** — Error stack / internal error serialised into HTTP response body ([docs](https://owasp.org/www-community/Improper_Error_Handling))
- **SEC022** — Silent `.catch(() => [] | {} | null)` after DB/fetch — swallows errors and returns stub data
- **SEC023** — Weak hash (`md5` / `sha1`) used for a password / token ([docs](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html))
- **SEC024** — `Math.random()` used to produce a token / session id / nonce ([docs](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID))
- **SEC025** — Open redirect: `redirect()` / `router.push()` / `NextResponse.redirect()` fed directly from user input ([docs](https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet))
- **SEC026** — `child_process.exec` / `execSync` / `spawn` called with a string containing user input ([docs](https://owasp.org/Top10/A03_2021-Injection))
- **SEC027** — File path built from user input without a path-traversal guard ([docs](https://owasp.org/www-community/attacks/Path_Traversal))
- **SEC028** — Auth token stored in `localStorage` / `sessionStorage` ([docs](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage))
- **SEC029** — Webhook route handler never verifies an incoming signature before mutating state ([docs](https://stripe.com/docs/webhooks/signatures))
- **SEC030** — fetch() target is user-controlled in a server route — potential SSRF ([docs](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery))
- **SEC031** — Password/token compared with `===` / `!==` instead of a timing-safe compare ([docs](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b))
## AI (LLM era)
- **AI001** — LLM provider API key hardcoded ([docs](https://platform.openai.com/docs/guides/production-best-practices/api-keys))
- **AI002** — User input flows into an LLM prompt without isolation (prompt injection sink) ([docs](https://owasp.org/www-project-top-10-for-large-language-model-applications/))
- **AI003** — System prompt or LLM config defined in client-side code ([docs](https://owasp.org/www-project-top-10-for-large-language-model-applications/))
- **AI004** — Vector database API key hardcoded
- **AI005** — MCP server configuration contains hardcoded secret ([docs](https://modelcontextprotocol.io/docs/concepts/configuration))
- **AI006** — LLM tool/function handler missing auth or capability allowlist ([docs](https://owasp.org/www-project-top-10-for-large-language-model-applications/))
- **AI007** — LLM streaming endpoint missing auth and/or rate limiting ([docs](https://vercel.com/docs/security/ddos-mitigation))
- **AI008** — LLM call without max_tokens / input length guard (cost-amplification risk)
## Agent tooling
- **CC001** — Secret literal inside `.claude/settings*.json` ([docs](https://docs.claude.com/en/docs/claude-code/settings))
- **CC002** — Claude Code hook shell script uses unquoted variable in a destructive command (`rm`/`mv`/`cp`/`eval`) ([docs](https://www.shellcheck.net/wiki/SC2086))
- **CC003** — Claude Code hook executes `curl | sh` or pipes remote content to a shell ([docs](https://docs.claude.com/en/docs/claude-code/hooks))
- **CC004** — Secret-shaped string inside `CLAUDE.md` / `.claude/agents/*.md` ([docs](https://docs.claude.com/en/docs/claude-code/memory))
- **CC005** — MCP server config exposes a secret in its `env` block ([docs](https://modelcontextprotocol.io/docs/concepts/configuration))
- **CC006** — Secret-shaped string inside `.cursorrules` / `.cursor/rules/*.mdc` / `.windsurfrules` / `.aiderconfig`
- **CC007** — Claude Code session transcripts or TODO state committed to the repo ([docs](https://docs.claude.com/en/docs/claude-code/overview))
- **CC008** — Prompt-injection marker inside agent rules / memory file ([docs](https://simonwillison.net/2023/May/2/prompt-injection-explained/))
- **CC009** — Cursor hooks config uses an unknown hook event
- **CC010** — Agent config grants broad autonomous write or network access
- **CC011** — Agent skill or command runs a dangerous shell pattern
## Next.js
- **NEXT001** — next/link used without anchor or child text (legacyBehavior)
- **NEXT002** — In-page <a> used for client navigation; prefer next/link
- **NEXT003** — Next.js API route uses request params without validation
- **NEXT004** — Dynamic import with user-controlled path
- **NEXT005** — External <img> used in Next.js app (consider next/image)
- **NEXT006** — Sensitive data exposed via getStaticProps/getServerSideProps
- **NEXT007** — JWT token exposed in Next.js API response
- **NEXT008** — Missing security headers in Next.js API route
- **NEXT009** — Unsafe redirect in Next.js API route ([docs](https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet))
- **NEXT010** — CORS configuration too permissive ([docs](https://developer.mozilla.org/docs/Web/HTTP/CORS))
- **NEXT011** — Environment variable leaked in client-side code ([docs](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser))
- **NEXT210** — Server secret serialized to client props (leak risk) ([docs](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#caveats))
- **NEXT212** — Server Action exported without an auth check ([docs](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#authentication-and-authorization))
- **NEXT213** — Server Action accepts FormData / JSON without input validation ([docs](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#validation))
- **NEXT214** — `use server` module imported from a `use client` file ([docs](https://react.dev/reference/rsc/use-server))
- **NEXT215** — Server Action passes user input directly to redirect()/revalidatePath() ([docs](https://nextjs.org/docs/app/api-reference/functions/redirect))
- **NEXT216** — App Router page/layout types `params`/`searchParams` as a plain object (Next 15 requires a Promise) ([docs](https://nextjs.org/docs/app/api-reference/file-conventions/page#params-optional))
- **NEXT217** — React hook used in a component file without the `'use client'` directive ([docs](https://nextjs.org/docs/app/building-your-application/rendering/client-components))
- **NEXT218** — `reactStrictMode: false` disables an important dev-time correctness check ([docs](https://react.dev/reference/react/StrictMode))
- **NEXT219** — `experimental.serverActions: true` is the Next 13 shape and is ignored in Next 14/15 ([docs](https://nextjs.org/docs/app/api-reference/next-config-js/serverActions))
- **NEXT220** — `typeof window !== "undefined"` / `window.*` access inside a Server Component ([docs](https://nextjs.org/docs/app/building-your-application/rendering/server-components))
- **NEXT221** — Client Component imports a server-only module (`fs`, `child_process`, `pg`, `better-sqlite3`, `@prisma/client`, …) ([docs](https://nextjs.org/docs/app/building-your-application/rendering/client-components))
- **NEXT222** — Server Component imports a client-only state library (`zustand` / `jotai` / `recoil` / `valtio`) ([docs](https://react.dev/reference/rsc/use-client))
- **NEXT223** — `app/**/route.ts` uses `export default` instead of a named `GET`/`POST`/… export ([docs](https://nextjs.org/docs/app/building-your-application/routing/route-handlers))
- **NEXT224** — `<a href="/internal">` used instead of `<Link>` for an internal route ([docs](https://nextjs.org/docs/app/api-reference/components/link))
- **NEXT225** — `<form method="POST" action="/api/…">` without a CSRF token or a Server Action ([docs](https://owasp.org/www-community/attacks/csrf))
## Next.js (experimental)
- **NEXT201** — Missing 404/not-found page ([docs](https://nextjs.org/docs/app/api-reference/file-conventions/not-found))
- **NEXT202** — Missing error boundary page ([docs](https://nextjs.org/docs/app/building-your-application/routing/error-handling))
- **NEXT203** — Missing _document.tsx while using next/head or next/script ([docs](https://nextjs.org/docs/pages/building-your-application/routing/custom-document))
- **NEXT205** — API route may be accessible without authentication ([docs](https://next-auth.js.org/configuration/nextjs#api-routes))
- **NEXT208** — router.push() to external URL ([docs](https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet))
- **NEXT209** — API route missing HTTP method validation ([docs](https://nextjs.org/docs/app/building-your-application/routing/route-handlers))
## Edge runtime
- **EDGE001** — Node-only API used in `runtime = "edge"` route ([docs](https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes))
- **EDGE002** — Long-lived response in edge runtime without streaming
- **EDGE003** — `process.env.X` read at module top-level inside edge route
## Modern frameworks (SvelteKit, Astro, Remix, Hono, Drizzle, Prisma)
- **ASTRO001** — Astro server endpoint without auth check ([docs](https://docs.astro.build/en/core-concepts/endpoints/))
- **DRIZZLE001** — Drizzle `sql``` template includes interpolated user input ([docs](https://orm.drizzle.team/docs/sql))
- **HONO001** — Hono route reads body without validator middleware ([docs](https://hono.dev/guides/validation))
- **PRISMA001** — Prisma `$queryRawUnsafe` / `$executeRawUnsafe` with untrusted input ([docs](https://www.prisma.io/docs/orm/prisma-client/queries/raw-database-access))
- **REMIX001** — Remix `action`/`loader` accesses DB without session check ([docs](https://remix.run/docs/en/main/guides/authentication))
- **SVELTE001** — SvelteKit `+page.server.ts` returns env/secret in `load` data ([docs](https://kit.svelte.dev/docs/load))
- **SVELTE002** — SvelteKit form action without CSRF/auth verification ([docs](https://kit.svelte.dev/docs/form-actions))
## Lovable / Supabase
- **LOVABLE001** — Supabase table accessed without RLS policy validation ([docs](https://supabase.com/docs/guides/auth/row-level-security))
- **LOVABLE002** — Supabase credentials hardcoded in source code ([docs](https://vitejs.dev/guide/env-and-mode.html))
- **LOVABLE003** — Anonymous authentication enabled without RLS policy validation ([docs](https://supabase.com/docs/guides/auth/auth-anonymous))
- **LOVABLE004** — Potential SQL injection in Supabase query - uses string interpolation ([docs](https://supabase.com/docs/reference/javascript/using-filters))
- **LOVABLE005** — Weak RLS policy pattern detected ([docs](https://supabase.com/docs/guides/auth/row-level-security#policies))
- **LOVABLE006** — Supabase storage access without proper validation ([docs](https://supabase.com/docs/guides/storage/security/access-control))
## Vite
- **VITE001** — Environment variable without VITE_ prefix may expose secrets to client ([docs](https://vitejs.dev/guide/env-and-mode.html#env-variables))
- **VITE002** — Development-only code without production fallback ([docs](https://vitejs.dev/guide/env-and-mode.html#modes))
- **VITE003** — Unsafe dynamic import with user input - potential path traversal ([docs](https://vitejs.dev/guide/features.html#dynamic-import))
## React / Tailwind
- **REACT001** — Array index used as React `key` — breaks reconciliation on reorder/insert ([docs](https://react.dev/learn/rendering-lists#why-does-react-need-keys))
- **REACT002** — Event handler invoked at render time (`onClick={fn()}` instead of `onClick={fn}`) ([docs](https://react.dev/learn/responding-to-events))
- **REACT003** — State mutated in place before setter call (React will not re-render) ([docs](https://react.dev/learn/updating-arrays-in-state))
- **REACT004** — `useEffect(cb, [])` closes over a state value without listing it as a dependency ([docs](https://react.dev/learn/you-might-not-need-an-effect))
- **REACT005** — `useEffect` starts a timer / listener without returning a cleanup function ([docs](https://react.dev/reference/react/useEffect#subscribing-to-events))
- **REACT006** — `useEffect` issues `fetch` without an AbortController signal ([docs](https://react.dev/reference/react/useEffect#fetching-data-with-effects))
- **REACT007** — Async function passed directly to `useEffect` (returns a Promise, not a cleanup) ([docs](https://react.dev/reference/react/useEffect))
- **REACT008** — `useState(expensive())` runs the initializer on every render ([docs](https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state))
- **REACT009** — React hook called conditionally (inside `if` / ternary / loop / early return) ([docs](https://react.dev/reference/rules/rules-of-hooks))
- **REACT010** — `ref.current = …` assigned during render ([docs](https://react.dev/reference/react/useRef#referencing-a-value-with-a-ref))
- **REACT011** — JWT / bearer token stored in `localStorage` / `sessionStorage` ([docs](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html))
- **TAILWIND001** — Dynamic className with unvalidated input - may allow CSS injection ([docs](https://tailwindcss.com/docs/content-configuration#dynamic-class-names))
## Vibe (AI hallucination signals)
- **VIBE001** — Import from non-existent package (possible hallucination)
- **VIBE002** — Repeated code block detected (possible copy-paste artifact)
- **VIBE003** — Incomplete implementation detected (placeholder or stub)
- **VIBE004** — Exported symbol appears unused in the codebase
## Development hygiene
- **DEV001** — TODO/FIXME comments detected
- **DEV002** — "Not implemented" stub found
- **DEV003** — Placeholder URL detected
- **DEV004** — Hardcoded mock/example data detected
- **DEV005** — Empty return or stubbed function detected
## Accessibility
- **A11Y001** — Image without alt attribute ([docs](https://webaim.org/techniques/alttext/))
- **A11Y002** — Input without label or aria-label ([docs](https://web.dev/labels-and-text-alternatives/))
- **A11Y003** — Empty button without aria-label ([docs](https://dequeuniversity.com/rules/axe/4.7/button-name))
- **A11Y004** — Div with onClick (not keyboard accessible) ([docs](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role))
- **A11Y005** — Link without href attribute ([docs](https://dequeuniversity.com/rules/axe/4.7/link-name))
- **A11Y006** — Image missing width/height attributes ([docs](https://web.dev/optimize-cls/))
- **A11Y007** — next/image used without width and height ([docs](https://nextjs.org/docs/pages/api-reference/components/image))
## Environment variables
- **ENV001** — .env file may not be in .gitignore
- **ENV002** — Potential API key in .env file
- **ENV004** — Secret value in .env file
- **ENV005** — Supabase credentials in .env
- **ENV006** — Missing .env.example file for documentation
- **ENV007** — Environment variable drift between .env and .env.example
- **ENV008** — Client-exposed env var carries a database/service connection URL (leaks to browser bundle) ([docs](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser))
## Links
- **LINK001** — Link checking requires puppeteer installation
- **LINK002** — External link unreachable or 4xx/5xx
- **LINK003** — Internal link or resource broken
## Docker / CI
- **DOCKER001** — Dockerfile runs as root (USER root or no USER)
- **DOCKER002** — Secrets defined via ENV in Dockerfile
- **DOCKER003** — Docker base image uses :latest tag
- **DOCKER004** — apt-get install without cleaning apt cache
- **GHA001** — Secrets may be echoed in GitHub Actions workflow
## Other
- **MOD001** — Module-level side effect (`fs.*Sync`, `db.exec`, `fetch`) runs at import time
- **MOD002** — `async function` body contains no `await` / `for await` (unnecessary wrapper)
- **MOD003** — Silent `.catch(() => …)` on a DB/fetch call hides real errors from the caller
- **MOD004** — High density of `: any` annotations in a single file