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
JavaScript
"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