UNPKG

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.

174 lines 18.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.securityRules = void 0; // Import modular rule files with patterns const SEC001_1 = __importDefault(require("./SEC001")); const SEC002_1 = __importDefault(require("./SEC002")); const SEC003_1 = __importDefault(require("./SEC003")); const SEC004_1 = __importDefault(require("./SEC004")); const SEC005_1 = __importDefault(require("./SEC005")); const SEC006_1 = __importDefault(require("./SEC006")); const SEC007_1 = __importDefault(require("./SEC007")); const SEC008_1 = __importDefault(require("./SEC008")); const SEC009_1 = __importDefault(require("./SEC009")); const SEC010_1 = __importDefault(require("./SEC010")); const SEC011_1 = __importDefault(require("./SEC011")); const SEC012_1 = __importDefault(require("./SEC012")); const SEC013_1 = __importDefault(require("./SEC013")); const SEC014_1 = __importDefault(require("./SEC014")); const SEC015_1 = __importDefault(require("./SEC015")); const SEC016_1 = __importDefault(require("./SEC016")); const SEC017_1 = __importDefault(require("./SEC017")); const makeRule = (meta, fileTypes) => ({ meta, impl: { fileTypes: fileTypes || ['js', 'jsx', 'ts', 'tsx', 'svelte', 'astro', 'html', 'env', 'md', 'mdx', 'yml', 'yaml', 'json', 'dockerfile'] } }); exports.securityRules = { // Core secrets/security - modular rules with patterns SEC001: SEC001_1.default, SEC002: SEC002_1.default, SEC003: SEC003_1.default, SEC004: SEC004_1.default, SEC005: SEC005_1.default, SEC006: SEC006_1.default, SEC007: SEC007_1.default, SEC008: SEC008_1.default, SEC009: SEC009_1.default, SEC010: SEC010_1.default, SEC011: SEC011_1.default, SEC012: SEC012_1.default, SEC013: SEC013_1.default, SEC014: SEC014_1.default, SEC015: SEC015_1.default, SEC016: SEC016_1.default, SEC017: SEC017_1.default, // Additional security rules (metadata only, detection in scanners) SEC018: makeRule({ id: 'SEC018', category: 'security', severity: 'high', message: 'High-entropy string literal (possible secret)', fix: 'Move secrets to environment variables; rotate credentials', impact: 'High-entropy strings often contain API keys or tokens that can be misused', helpUri: 'https://owasp.org/Top10/A02_2021-Cryptographic_Failures' }), SEC019: makeRule({ id: 'SEC019', category: 'security', severity: 'high', message: 'React component injection via createElement', fix: 'Disallow dynamic element types from untrusted input', impact: 'Component injection can lead to XSS attacks and arbitrary code execution' }), SEC020: makeRule({ id: 'SEC020', category: 'security', severity: 'high', message: 'SQL sink called with string interpolation or concatenation (SQL injection)', fix: 'Use parameterised queries: `.prepare("... ?").run(value)`, tagged `sql`` ` templates with placeholders, or the ORM\'s query builder.', impact: 'String interpolation in SQL is the textbook injection vector — attackers can read, modify, or drop the database.', helpUri: 'https://owasp.org/Top10/A03_2021-Injection' }), LOG001: makeRule({ id: 'LOG001', category: 'security', severity: 'medium', message: 'Potential secret logged to console/logger', fix: 'Avoid logging secrets; redact values before logging', impact: 'Secrets in logs can be exposed through log aggregation systems or error tracking', helpUri: 'https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html' }), OSV001: makeRule({ id: 'OSV001', category: 'security', severity: 'high', message: 'Vulnerable dependency detected', fix: 'Upgrade to a patched version' }, ['json', 'txt', 'lock']), // Networking (JS) JSNET001: makeRule({ id: 'JSNET001', category: 'security', severity: 'medium', message: 'HTTP request without timeout/retry policy', fix: 'Add timeout/backoff to axios/fetch calls', helpUri: 'https://developer.mozilla.org/docs/Web/API/AbortController' }), // Cookies / headers COOKIE001: makeRule({ id: 'COOKIE001', category: 'security', severity: 'medium', message: 'Set-Cookie missing HttpOnly/Secure/SameSite', fix: 'Add HttpOnly; Secure; SameSite=Lax (or Strict) to cookies', impact: 'Insecure cookies can be stolen via XSS attacks or intercepted over insecure connections', helpUri: 'https://owasp.org/www-community/controls/SecureCookieAttributes' }), COOKIE002: makeRule({ id: 'COOKIE002', category: 'security', severity: 'high', message: 'JWT token exposed in client-side cookie without security flags', fix: 'Add HttpOnly and Secure flags to JWT cookies', impact: 'JWT tokens in insecure cookies can be stolen and used to impersonate users', helpUri: 'https://owasp.org/www-community/controls/SecureCookieAttributes' }), COOKIE003: makeRule({ id: 'COOKIE003', category: 'security', severity: 'high', message: 'Sensitive data returned in JSON response (potential token leak)', fix: 'Avoid returning tokens/secrets in API responses; use secure cookies instead', impact: 'Tokens in JSON responses are visible to client-side JavaScript and can be stolen' }), COOKIE004: makeRule({ id: 'COOKIE004', category: 'security', severity: 'medium', message: 'Cookie used without domain/path restrictions', fix: 'Set appropriate domain and path attributes for cookies', impact: 'Overly broad cookie scope can lead to unintended exposure to subdomains' }), // Env ENV001: makeRule({ id: 'ENV001', category: 'security', severity: 'high', message: '.env file may not be in .gitignore', fix: 'Add .env files to .gitignore to prevent accidental commits' }, ['env', 'gitignore']), ENV002: makeRule({ id: 'ENV002', category: 'security', severity: 'high', message: 'Potential API key in .env file', fix: 'Ensure this .env file is in .gitignore and not committed' }, ['env']), ENV004: makeRule({ id: 'ENV004', category: 'security', severity: 'high', message: 'Secret value in .env file', fix: 'Verify .env is in .gitignore and use .env.example for documentation' }, ['env']), ENV005: makeRule({ id: 'ENV005', category: 'security', severity: 'medium', message: 'Supabase credentials in .env', fix: 'Ensure this .env file is not committed to version control' }, ['env']), ENV006: makeRule({ id: 'ENV006', category: 'security', severity: 'low', message: 'Missing .env.example file for documentation', fix: 'Create .env.example with placeholder values for team setup' }, ['env']), ENV007: makeRule({ id: 'ENV007', category: 'security', severity: 'low', message: 'Environment variable drift between .env and .env.example', fix: 'Align keys in .env and .env.example' }, ['env']), ENV008: makeRule({ id: 'ENV008', category: 'security', severity: 'high', message: 'Client-exposed env var carries a database/service connection URL (leaks to browser bundle)', fix: 'Rename without the NEXT_PUBLIC_/VITE_/PUBLIC_ prefix so the value stays server-only; rotate the credential if it has already shipped.', impact: 'NEXT_PUBLIC_/VITE_/PUBLIC_ values are inlined into the client bundle; a DB or Redis URL in one is an immediate credential leak.', helpUri: 'https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser' }, ['env']), // Docker / CI DOCKER001: makeRule({ id: 'DOCKER001', category: 'security', severity: 'medium', message: 'Dockerfile runs as root (USER root or no USER)', fix: 'Add a non-root USER' }, ['dockerfile']), DOCKER002: makeRule({ id: 'DOCKER002', category: 'security', severity: 'high', message: 'Secrets defined via ENV in Dockerfile', fix: 'Avoid embedding secrets in Docker images' }, ['dockerfile']), DOCKER003: makeRule({ id: 'DOCKER003', category: 'security', severity: 'low', message: 'Docker base image uses :latest tag', fix: 'Pin to a specific version tag' }, ['dockerfile']), DOCKER004: makeRule({ id: 'DOCKER004', category: 'security', severity: 'low', message: 'apt-get install without cleaning apt cache', fix: 'Run rm -rf /var/lib/apt/lists/* after apt-get' }, ['dockerfile']), // CI GHA001: makeRule({ id: 'GHA001', category: 'security', severity: 'high', message: 'Secrets may be echoed in GitHub Actions workflow', fix: 'Do not print secrets to logs; remove echo/printf of secrets' }, ['yml', 'yaml']), // Next.js security NEXT003: makeRule({ id: 'NEXT003', category: 'security', severity: 'medium', message: 'Next.js API route uses request params without validation', fix: 'Validate input with zod/yup/ajv before usage' }), NEXT004: makeRule({ id: 'NEXT004', category: 'security', severity: 'high', message: 'Dynamic import with user-controlled path', fix: 'Avoid dynamic import sources from untrusted input' }), NEXT006: makeRule({ id: 'NEXT006', category: 'security', severity: 'high', message: 'Sensitive data exposed via getStaticProps/getServerSideProps', fix: 'Do not include secrets/env in returned props' }), NEXT007: makeRule({ id: 'NEXT007', category: 'security', severity: 'high', message: 'JWT token exposed in Next.js API response', fix: 'Use httpOnly cookies instead of returning tokens in JSON', impact: 'JWT tokens in API responses can be stolen via XSS and used for session hijacking' }), NEXT008: makeRule({ id: 'NEXT008', category: 'security', severity: 'medium', message: 'Missing security headers in Next.js API route', fix: 'Add security headers like X-Content-Type-Options, X-Frame-Options', impact: 'Missing security headers expose the application to various client-side attacks' }), NEXT009: makeRule({ id: 'NEXT009', category: 'security', severity: 'high', message: 'Unsafe redirect in Next.js API route', fix: 'Validate redirect URLs against allowlist', impact: 'Open redirects can be used for phishing attacks and credential theft', helpUri: 'https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet' }), NEXT010: makeRule({ id: 'NEXT010', category: 'security', severity: 'medium', message: 'CORS configuration too permissive', fix: 'Restrict CORS to specific origins instead of using wildcard', impact: 'Overly permissive CORS allows malicious sites to make requests on behalf of users', helpUri: 'https://developer.mozilla.org/docs/Web/HTTP/CORS' }), NEXT011: makeRule({ id: 'NEXT011', category: 'security', severity: 'high', message: 'Environment variable leaked in client-side code', fix: 'Use NEXT_PUBLIC_ prefix only for truly public variables', impact: 'Server-side environment variables exposed to client reveal sensitive configuration', helpUri: 'https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser' }), NEXT201: makeRule({ id: 'NEXT201', category: 'security', severity: 'low', message: 'Missing 404/not-found page', fix: 'Add app/not-found.tsx or pages/404.tsx' }), NEXT202: makeRule({ id: 'NEXT202', category: 'security', severity: 'low', message: 'Missing error boundary page', fix: 'Add app/error.tsx or pages/_error.tsx' }), NEXT203: makeRule({ id: 'NEXT203', category: 'security', severity: 'low', message: 'Missing _document.tsx when customizing head/scripts', fix: 'Add pages/_document.tsx for custom document structure' }), NEXT205: makeRule({ id: 'NEXT205', category: 'security', severity: 'medium', message: 'API route may be accessible without authentication', fix: 'Require auth (NextAuth getServerSession/JWT/cookie checks) for sensitive endpoints', helpUri: 'https://next-auth.js.org/configuration/nextjs#api-routes', impact: 'Unauthenticated access to sensitive APIs can leak data or allow abuse' }), NEXT208: makeRule({ id: 'NEXT208', category: 'security', severity: 'medium', message: 'router.push() to external URL', fix: 'Validate and restrict redirect targets to an allowlist or same-origin', helpUri: 'https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet', impact: 'Open redirects facilitate phishing and credential theft' }), NEXT209: makeRule({ id: 'NEXT209', category: 'security', severity: 'medium', message: 'API route missing HTTP method validation', fix: 'Validate req.method in Pages API or export method handlers (GET/POST/...) in App Router', helpUri: 'https://nextjs.org/docs/app/building-your-application/routing/route-handlers', impact: 'Accepting unintended methods broadens attack surface and leads to undefined behavior' }), NEXT210: makeRule({ id: 'NEXT210', category: 'security', severity: 'high', message: 'Server secret serialized to client props (leak risk)', fix: 'Do not pass secrets in getServerSideProps/getStaticProps props; keep server-only or use secure cookies', helpUri: 'https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#caveats', impact: 'Secrets sent via props are exposed to the browser and can be exfiltrated' }), // 3.1 security extensions — high-signal patterns common in AI-generated // code. Detection lives in AstSecurityScanner / FrameworkScanner; // metadata is centralised here so `ubon explain <id>` works for every rule. SEC021: makeRule({ id: 'SEC021', category: 'security', severity: 'medium', message: 'Error stack / internal error serialised into HTTP response body', fix: 'Return a generic message to the client; log the stack server-side only.', impact: 'Stack traces expose file paths, framework versions and sometimes secrets; invaluable reconnaissance for an attacker.', helpUri: 'https://owasp.org/www-community/Improper_Error_Handling' }), SEC022: makeRule({ id: 'SEC022', category: 'security', severity: 'medium', message: 'Silent `.catch(() => [] | {} | null)` after DB/fetch — swallows errors and returns stub data', fix: 'Log the error and either rethrow or surface a real failure state.', impact: 'Stub-on-error makes outages invisible: the UI renders "empty" and monitoring never fires.' }), SEC023: makeRule({ id: 'SEC023', category: 'security', severity: 'high', message: 'Weak hash (`md5` / `sha1`) used for a password / token', fix: 'Use `bcrypt`/`argon2`/`scrypt` for passwords and `crypto.randomBytes` + HMAC-SHA-256 for tokens.', impact: 'md5/sha1 are broken for authentication: trivially cracked with rainbow tables or collisions.', helpUri: 'https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html' }), SEC024: makeRule({ id: 'SEC024', category: 'security', severity: 'high', message: '`Math.random()` used to produce a token / session id / nonce', fix: 'Use `crypto.randomUUID()` or `crypto.randomBytes(32).toString(\'hex\')`.', impact: 'Math.random is a predictable PRNG — attackers can guess the next value from a handful of samples.', helpUri: 'https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID' }), SEC025: makeRule({ id: 'SEC025', category: 'security', severity: 'high', message: 'Open redirect: `redirect()` / `router.push()` / `NextResponse.redirect()` fed directly from user input', fix: 'Validate the target against a same-origin allowlist before redirecting.', impact: 'Open redirects are weaponised for phishing: attackers craft links on your domain that bounce users to a credential-harvesting page.', helpUri: 'https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards_Cheat_Sheet' }), SEC026: makeRule({ id: 'SEC026', category: 'security', severity: 'high', message: '`child_process.exec` / `execSync` / `spawn` called with a string containing user input', fix: 'Use the array form (`execFile(cmd, [arg1, arg2])`) and validate inputs against an allowlist.', impact: 'String concatenation in shell commands is the textbook command-injection sink.', helpUri: 'https://owasp.org/Top10/A03_2021-Injection' }), SEC027: makeRule({ id: 'SEC027', category: 'security', severity: 'high', message: 'File path built from user input without a path-traversal guard', fix: 'Resolve the joined path and assert it starts with your allowed root (`resolved.startsWith(rootDir)`).', impact: '`../` sequences in the path let attackers read `/etc/passwd`, the `.env`, or anywhere else the process can read.', helpUri: 'https://owasp.org/www-community/attacks/Path_Traversal' }), SEC028: makeRule({ id: 'SEC028', category: 'security', severity: 'high', message: 'Auth token stored in `localStorage` / `sessionStorage`', fix: 'Use an HttpOnly cookie set by the server. localStorage is reachable by any XSS.', impact: 'A single XSS can exfiltrate every token from localStorage. HttpOnly cookies are invisible to JavaScript.', helpUri: 'https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage' }), SEC029: makeRule({ id: 'SEC029', category: 'security', severity: 'high', message: 'Webhook route handler never verifies an incoming signature before mutating state', fix: 'Call `stripe.webhooks.constructEvent(body, sig, secret)` / `svix.verify()` / `timingSafeEqual(createHmac(...))` before trusting the payload.', impact: 'Without signature verification anyone who can POST to the endpoint can replay/forge events — flipping subscriptions, granting roles, draining inventory.', helpUri: 'https://stripe.com/docs/webhooks/signatures' }), SEC030: makeRule({ id: 'SEC030', category: 'security', severity: 'high', message: 'fetch() target is user-controlled in a server route — potential SSRF', fix: 'Resolve the URL against an allowlist of hostnames (and IP ranges) before calling `fetch()`. Reject `localhost`, private ranges, and link-local addresses.', impact: 'The server can be turned into a proxy to cloud metadata (169.254.169.254), internal services, or arbitrary hosts on the private network.', helpUri: 'https://owasp.org/www-community/attacks/Server_Side_Request_Forgery' }), SEC031: makeRule({ id: 'SEC031', category: 'security', severity: 'medium', message: 'Password/token compared with `===` / `!==` instead of a timing-safe compare', fix: 'Use `crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))` so the comparison time doesn\'t leak the prefix.', impact: 'A constant-time compare is the standard mitigation; `===` short-circuits on the first mismatch and leaks information byte-by-byte under a timing attack.', helpUri: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b' }) }; //# sourceMappingURL=index.js.map