/** * Built-in Semgrep taint rules. * * Semgrep's free community rulesets (p/owasp-top-ten, p/javascript, …) * do NOT include the taint-tracking rules for SQL injection, command * injection, and SSRF — those require `semgrep login` (a registry * account). That left a major detection gap: the product could not catch * three of the most important vulnerability classes offline. * * These embedded taint rules close that gap with zero login/network — * they ship with the package as a string and are written to a temp file * at scan time, then passed to semgrep with `--config`. Verified against * the eval fixtures. * * @module scanners/builtin-rules */ /** Embedded Semgrep taint rules (YAML). */ export declare const BUILTIN_SEMGREP_RULES = "rules:\n - id: vaspera-sql-injection\n mode: taint\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Untrusted request input flows into a SQL query without\n parameterization (SQL injection).\n metadata:\n category: security\n cwe: \"CWE-89\"\n owasp: \"A03:2021\"\n vaspera: builtin\n pattern-sources:\n - pattern: req.query\n - pattern: req.params\n - pattern: req.body\n - pattern: req.headers\n pattern-sinks:\n - patterns:\n - pattern: $X.query($SQL, ...)\n - focus-metavariable: $SQL\n - patterns:\n - pattern: $X.execute($SQL, ...)\n - focus-metavariable: $SQL\n - pattern: $X.raw(...)\n pattern-sanitizers:\n # Drizzle ORM / postgres.js / slonik sql`` tagged template: interpolated\n # values become bound parameters, not raw SQL fragments. sql.raw(...) is\n # intentionally NOT listed here \u2014 it bypasses parameterization and IS unsafe.\n - pattern: sql`...`\n - id: vaspera-command-injection\n mode: taint\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Untrusted request input flows into a shell command\n (command injection).\n metadata:\n category: security\n cwe: \"CWE-78\"\n owasp: \"A03:2021\"\n vaspera: builtin\n pattern-sources:\n - pattern: req.query\n - pattern: req.params\n - pattern: req.body\n # exec/execSync run a shell string (dangerous with interpolation).\n # Array-form spawn(cmd, [args]) is the SAFE alternative, so it is\n # intentionally NOT a sink \u2014 flagging it would be a false positive.\n pattern-sinks:\n - pattern: exec(...)\n - pattern: execSync(...)\n - pattern: $CP.exec(...)\n - pattern: $CP.execSync(...)\n - patterns:\n - pattern: spawn($CMD, ...)\n - focus-metavariable: $CMD\n - patterns:\n - pattern: $CP.spawn($CMD, ...)\n - focus-metavariable: $CMD\n - id: vaspera-ssrf\n mode: taint\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Untrusted request input flows into an outbound request URL\n (server-side request forgery).\n metadata:\n category: security\n cwe: \"CWE-918\"\n owasp: \"A10:2021\"\n vaspera: builtin\n pattern-sources:\n - pattern: req.query\n - pattern: req.params\n - pattern: req.body\n # Focus on the URL argument \u2014 taint in a request body/payload\n # argument is not SSRF, so flagging it would be a false positive.\n pattern-sinks:\n - patterns:\n - pattern: fetch($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: axios.get($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: axios.post($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: axios($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: http.get($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: https.get($URL, ...)\n - focus-metavariable: $URL\n - patterns:\n - pattern: got($URL, ...)\n - focus-metavariable: $URL\n - id: vaspera-path-traversal\n mode: taint\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Untrusted request input flows into a filesystem path\n (path traversal / CWE-22). An attacker can read or write arbitrary\n files on the server by supplying a path like ../../etc/passwd.\n Sanitize with path.basename() and resolve against a fixed root, or\n use an explicit allowlist of permitted paths.\n metadata:\n category: security\n cwe: \"CWE-22\"\n owasp: \"A01:2021\"\n vaspera: builtin\n pattern-sources:\n - pattern: req.query\n - pattern: req.params\n - pattern: req.body\n - pattern: req.headers\n # Sinks cover both the fs.-prefixed module form AND the bare/destructured\n # form (import { readFile } from \"fs/promises\"; readFile(path)).\n # Semgrep taint mode follows dataflow through intermediate variables, so\n # readFile(join(\"/data\", req.query.file)) is caught because the tainted\n # value reaches the first argument of readFile regardless of join().\n # focus-metavariable: $PATH ensures we flag taint in the path argument\n # only, not in the options/encoding/callback arguments (precision).\n pattern-sinks:\n - patterns:\n - pattern: readFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: readFileSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: writeFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: writeFileSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: appendFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: appendFileSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: createReadStream($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: createWriteStream($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: unlink($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: unlinkSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: rm($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: rmSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: readdir($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: readdirSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.readFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.readFileSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.writeFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.writeFileSync($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.appendFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.createReadStream($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.createWriteStream($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.unlink($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.rm($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.readdir($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.promises.readFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: fs.promises.writeFile($PATH, ...)\n - focus-metavariable: $PATH\n - patterns:\n - pattern: res.sendFile($PATH, ...)\n - focus-metavariable: $PATH\n # path.basename / basename strips directory components \u2014 a basenamed value\n # cannot traverse outside the current directory. path.resolve used as a\n # sanitizer only when anchored to a constant root; basename is the key one.\n pattern-sanitizers:\n - pattern: path.basename(...)\n - pattern: basename(...)\n - id: vaspera-insecure-deserialization\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Insecure deserialization (CWE-502): untrusted input is executed as code\n via eval, the Function constructor, or an unsafe yaml.load. Use JSON.parse\n or a safe schema (yaml.load(x, { schema: JSON_SCHEMA })).\n metadata:\n category: security\n cwe: \"CWE-502\"\n owasp: \"A08:2021\"\n vaspera: builtin\n # Not taint-tracked: these sinks execute their argument as code regardless\n # of provenance, so the construct itself is the vulnerability.\n pattern-either:\n - pattern: eval(...)\n - pattern: new Function(...)\n # Single-arg yaml.load (no safe schema). A 1-arg pattern (no \"...\")\n # naturally excludes the safe 2-arg form yaml.load(x, { schema }).\n # Constrain the receiver to yaml-ish names so it doesn't match every\n # unrelated .load() call.\n - patterns:\n - pattern: $YAML.load($X)\n - metavariable-regex:\n metavariable: $YAML\n regex: (?i)(yaml|jsyaml)\n - id: vaspera-xxe\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n XML external entity injection (XXE, CWE-611): an XML parser is constructed\n without disabling external entity resolution. Disable DTD/entity expansion\n or use a parser that is safe by default.\n metadata:\n category: security\n cwe: \"CWE-611\"\n owasp: \"A05:2021\"\n vaspera: builtin\n # xmldom's DOMParser resolves external entities by default; fast-xml-parser's\n # XMLParser is safe by default and is intentionally NOT flagged.\n pattern-either:\n - pattern: new DOMParser()\n - pattern: new DOMParser(...).parseFromString(...)\n - id: vaspera-mass-assignment\n # Not taint-tracked: structural patterns are used instead of taint mode.\n # Taint mode propagates req.body taint to req.body.email (member access),\n # which would flag the safe allowlist form values({ email: req.body.email }).\n # Structural patterns match only when the whole-body object or a spread of\n # it reaches a write sink \u2014 the critical precision line.\n #\n # Safe case \u2014 NOT flagged by any pattern below:\n # db.insert(users).values({ email: req.body.email, name: req.body.name })\n # The arg is neither req.body (different expression) nor { ...req.body }\n # (no object-spread of the whole body), so no pattern fires.\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Mass assignment / over-posting (CWE-915): the entire request body (or a\n spread of it) flows directly into an ORM write. An attacker can set\n arbitrary fields such as isAdmin or role. Fix: pick only the scalar fields\n you intend to write (e.g. email and name from req.body), or use a\n validated DTO / pick() / allowlist.\n metadata:\n category: security\n cwe: \"CWE-915\"\n owasp: \"A08:2021 A04:2021\"\n vaspera: builtin\n pattern-either:\n # ---- Drizzle / generic: .values(req.body) --------------------------------\n - pattern: $DB.values(req.body)\n # ---- Drizzle / generic: .values({ ...req.body }) -------------------------\n - pattern: \"$DB.values({ ...req.body })\"\n # ---- Drizzle / generic: .set(req.body) -----------------------------------\n - pattern: $DB.set(req.body)\n # ---- Drizzle / generic: .set({ ...req.body }) ----------------------------\n - pattern: \"$DB.set({ ...req.body })\"\n # ---- Prisma: .create({ data: req.body }) ---------------------------------\n - pattern: \"$PRISMA.create({ data: req.body })\"\n # ---- Prisma: .create({ data: { ...req.body } }) --------------------------\n - pattern: \"$PRISMA.create({ data: { ...req.body } })\"\n # ---- Prisma: .update/.upsert with data: req.body -------------------------\n - pattern: \"$PRISMA.$METHOD({ ..., data: req.body })\"\n # ---- Mongoose / generic: Model.create(req.body) --------------------------\n - pattern: $MODEL.create(req.body)\n # ---- Mongoose: .updateOne(filter, req.body) / .findByIdAndUpdate ----------\n - pattern: $DOC.updateOne($FILTER, req.body)\n - pattern: $DOC.findByIdAndUpdate($ID, req.body)\n # ---- Generic: .insert(req.body) ------------------------------------------\n - pattern: $DB.insert(req.body)\n - id: vaspera-prototype-pollution\n # Structural patterns (not taint-tracked) targeting the two highest-precision\n # prototype pollution forms: bracket-assignment with a request-derived key,\n # and Object.assign() with the entire request body/query as a source.\n # Recursive-merge loops are not included \u2014 they cannot be expressed at the\n # required precision level without an unacceptable FP rate.\n #\n # Safe cases \u2014 NOT flagged:\n # Object.assign({}, defaults) \u2014 no req.* as second arg\n # profile.name = req.body.name \u2014 static key, not bracket-assignment\n severity: ERROR\n languages: [javascript, typescript]\n message: >-\n Prototype pollution (CWE-1321): user-controlled input used as an object\n key or merged via Object.assign can pollute Object.prototype. Fix: use\n Object.create(null) for lookup maps, validate / allowlist keys before\n assignment, and replace Object.assign(target, body) with a validated DTO.\n metadata:\n category: security\n cwe: \"CWE-1321\"\n owasp: \"A08:2021\"\n vaspera: builtin\n pattern-either:\n # ---- Dynamic bracket-assignment with a request-derived key ---------------\n # obj[req.body.key] = val or obj[req.body.$PROP] = val\n # Static-key writes (obj.knownField = val) use dot notation so they do\n # NOT match the bracket form \u2014 zero false positives on that pattern.\n - pattern: $OBJ[req.body.$PROP] = $VAL\n - pattern: $OBJ[req.query.$PROP] = $VAL\n - pattern: $OBJ[req.params.$PROP] = $VAL\n # ---- Object.assign with the entire request body/query as second arg ------\n # Object.assign({}, defaults) is safe and will NOT match because\n # 'defaults' is not req.body / req.query / req.params.\n - pattern: Object.assign($TARGET, req.body)\n - pattern: Object.assign($TARGET, req.query)\n - pattern: Object.assign($TARGET, req.params)\n"; /** * Write the built-in rules to a temp file (once per process) and return * the path, for passing to `semgrep --config`. */ export declare function getBuiltinRulesPath(): Promise; //# sourceMappingURL=builtin-rules.d.ts.map