xploitscan-shared-rules 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -269,26 +269,42 @@ var sqlInjection = {
269
269
  description: "String concatenation or template literals in SQL queries allow attackers to execute arbitrary database commands.",
270
270
  check(content, filePath) {
271
271
  const patterns = [
272
- // Template literals in SQL
273
- /(?:query|execute|raw|sql)\s*\(\s*`[^`]*\$\{/gi,
274
- // String concatenation in SQL
275
- /(?:query|execute)\s*\(\s*["'][^"']*["']\s*\+/gi,
276
- // Direct variable interpolation
272
+ // Template literal passed as first arg to query/execute/raw/sql/
273
+ // queryRaw/queryRawUnsafe/execute. Examples that SHOULD fire:
274
+ // db.query(`SELECT ... ${x}`)
275
+ // prisma.$queryRawUnsafe(`... ${x}`)
276
+ // knex.raw(`... ${x}`)
277
+ /(?:query|execute|raw|sql|queryRaw|queryRawUnsafe|executeRaw|executeRawUnsafe)\s*\(\s*`[^`]*\$\{/gi,
278
+ // String concatenation in SQL — now includes raw() and sql() and
279
+ // Drizzle's sql.raw() / Prisma's $queryRawUnsafe() variants. Previous
280
+ // version only covered query()/execute() and missed knex.raw("..." + x).
281
+ //
282
+ // The string literal part uses a proper "quoted string" regex that
283
+ // allows the opposite quote type inside (common in SQL — "WHERE x = 'a'").
284
+ // The older `[^"']*` class bailed out at the first inner quote and
285
+ // missed every realistic SQL-containing concat.
286
+ /(?:query|execute|raw|sql|queryRaw|queryRawUnsafe|executeRaw|executeRawUnsafe)\s*\(\s*(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*\+/gi,
287
+ // Direct variable interpolation in a SQL verb context
277
288
  /(?:SELECT|INSERT|UPDATE|DELETE|WHERE)\s+.*\$\{(?!.*parameterized)/gi
278
289
  ];
279
290
  const matches = [];
280
291
  const usesParams = /\?\s*,|\$\d+|:[\w]+|\bprepare\b|\bplaceholder\b/i.test(content);
281
292
  if (usesParams) return [];
282
293
  for (const pattern of patterns) {
283
- matches.push(
284
- ...findMatches(
285
- content,
286
- pattern,
287
- sqlInjection,
288
- filePath,
289
- () => "Use parameterized queries or prepared statements instead of string interpolation. Example: db.query('SELECT * FROM users WHERE id = ?', [userId])"
290
- )
294
+ const raw = findMatches(
295
+ content,
296
+ pattern,
297
+ sqlInjection,
298
+ filePath,
299
+ () => "Use parameterized queries or prepared statements instead of string interpolation. Example: db.query('SELECT * FROM users WHERE id = ?', [userId])"
291
300
  );
301
+ for (const m of raw) {
302
+ const lineText = content.split("\n")[m.line - 1] || "";
303
+ if (/\bPrisma\.sql\s*`/.test(lineText)) continue;
304
+ if (/\bsql\s*`/.test(lineText) && !/\bsql\.raw\s*\(/.test(lineText)) continue;
305
+ if (/\$queryRaw\s*\(\s*Prisma\.sql|\$executeRaw\s*\(\s*Prisma\.sql/.test(lineText)) continue;
306
+ matches.push(m);
307
+ }
292
308
  }
293
309
  return matches;
294
310
  }
@@ -2829,13 +2845,22 @@ var commandInjection = {
2829
2845
  // Node.js — require standalone exec/execSync, not db.exec() or conn.exec()
2830
2846
  /(?<![.\w])(?:exec|execSync)\s*\(\s*(?:`[^`]*\$\{|["'][^"']*\+\s*(?:req\.|body\.|input|params|args|user))/gi,
2831
2847
  /child_process.*exec\s*\(\s*(?!["'`])/g,
2848
+ // spawn / execFile / exec with shell: true AND a template literal
2849
+ // first arg. `shell: true` converts the first argument into a string
2850
+ // passed to `/bin/sh -c`, so any ${} interpolation is a shell injection
2851
+ // opportunity. Without shell: true, spawn/execFile are safe because
2852
+ // the command and args are kept separate. Previously this class of
2853
+ // bug was missed — the "hasSafe" skip below assumed any spawn in the
2854
+ // file was fine, which is the opposite of true with shell: true.
2855
+ /(?<![.\w])(?:spawn|spawnSync|execFile|execFileSync|exec|execSync)\s*\(\s*`[^`]*\$\{[\s\S]*?shell\s*:\s*(?:true|["'][^"']+["'])/gi,
2832
2856
  // Python
2833
2857
  /os\.system\s*\(\s*(?!["'`].*["'`]\s*\))/g,
2834
2858
  /subprocess\.(?:call|run|Popen)\s*\([^)]*shell\s*=\s*True/gi,
2835
2859
  // Ruby
2836
2860
  /system\s*\(\s*["'].*#\{/g
2837
2861
  ];
2838
- const hasSafe = /execFile|spawn|escapeshellarg|shlex\.quote|shellEscape/i.test(content);
2862
+ const hasShellTrue = /shell\s*:\s*(?:true|["'][^"']+["'])/i.test(content);
2863
+ const hasSafe = !hasShellTrue && /execFile|spawn|escapeshellarg|shlex\.quote|shellEscape/i.test(content);
2839
2864
  if (hasSafe) return [];
2840
2865
  for (const p of patterns) {
2841
2866
  const raw = findMatches(
@@ -4336,7 +4361,7 @@ var hardcodedSupabaseServiceRole = {
4336
4361
  if (isTestFile(filePath)) return [];
4337
4362
  if (!SECRET_FILE_EXT.test(filePath)) return [];
4338
4363
  if (LOCK_FILE_RE.test(filePath)) return [];
4339
- const pattern = /(?:service[_-]?role[_-]?key|SUPABASE_SERVICE_ROLE_KEY|supabaseServiceRole)\s*[:=]\s*["'`](eyJ[A-Za-z0-9_\-]{50,})["'`]/gi;
4364
+ const pattern = /(?:service[_-]?role[_-]?key|SUPABASE_SERVICE_ROLE_KEY|supabaseServiceRole)\s*[:=]\s*["'`](eyJ[A-Za-z0-9_\-.]{50,})["'`]/gi;
4340
4365
  const findings = [];
4341
4366
  let m;
4342
4367
  while ((m = pattern.exec(content)) !== null) {