correctover-scan 3.2.0 → 3.2.1

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.
@@ -195,7 +195,11 @@ const METADATA_HOSTS = /metadata\.google(?:\.internal)?|169\.254\.|fd00:ec2/i;
195
195
 
196
196
  // Context that proves a 169.254/private address is a cloud SDK credential
197
197
  // provider (normal) rather than business-code SSRF (finding).
198
- const SDK_CTX = /(aws|amazon|ec2|ecs|eks|imds|metadata\s*(service|endpoint|host)|credentialprovider|container\s*credentials|ecr|sts\.)/i;
198
+ // Strong signals only: the metadata address must actually sit inside a cloud
199
+ // SDK credential-provider call. A bare AWS_/GCP env-var name nearby is NOT
200
+ // proof of SDK usage — it often co-occurs with a hand-rolled SSRF and used to
201
+ // wrongly suppress it. Keep this narrow to avoid false negatives.
202
+ const SDK_CTX = /(aws-sdk|@aws-sdk|amazon-cognito|aws4fetch|fromNodeProviderChain|fromIni|ECSCredentials|EC2MetadataClient|instanceMetadataService|metadataService\b|[A-Za-z_$][\w$]*[Mm]etadata\s*[=(]|metadata\s*(?:service|endpoint)\s*[:=]|credentialprovider|credential[ _-]?provider|credentials?\s*(?:chain|from)|container\s*credentials|sts\.|gcp-metadata|google-auth-library)/i;
199
203
  // Context that proves the address sits inside an SSRF guard / blocklist.
200
204
  // Actual guard constructs (a bare "ssrf" word in a comment is not a guard).
201
205
  const GUARD_CTX = /(no_?proxy|blocklist|block_list|denylist|deny_list|allowlist|preflight|isPrivate|private[_-]?ip|loopback|metadata.{0,30}(block|deny|filter|redirect))/i;
@@ -391,7 +395,9 @@ function checkMetadataIntranet(file) {
391
395
  seen.add(target + ':' + i);
392
396
  const ctx = lineWindow(file.lines, i);
393
397
  const isMetadata = /^169\.254\./.test(target) || /metadata\.google/i.test(target) || /fd00:ec2/i.test(target);
394
- const inSdk = SDK_CTX.test(ctx) || /SECONDARY_HOST_ADDRESS|gcp|google/i.test(ctx);
398
+ // Bare "gcp"/"google" words are too weak and suppress real SSRFs; require
399
+ // concrete SDK/host markers instead (SDK_CTX already covers the strong ones).
400
+ const inSdk = SDK_CTX.test(ctx) || /SECONDARY_HOST_ADDRESS|gcp-metadata|metadata\.google(?:\.internal)?/.test(ctx);
395
401
  const inGuard = GUARD_CTX.test(ctx);
396
402
  let sev, msg, suppressed = false;
397
403
  if (inSdk) {
@@ -417,7 +423,7 @@ function checkMetadataIntranet(file) {
417
423
  function checkCommandExec(file) {
418
424
  const findings = [];
419
425
  const shellTrue = /shell\s*:\s*(?:!0|true)/;
420
- const execCall = /(^|[^.\w$])(?:exec|execSync)\s*\(/;
426
+ const execCall = /(?<![\w$])(?:exec|execSync)\s*\(/;
421
427
  const gatedRe = /(permission[A-Z]|permission[ _-]?(?:check|mode|behavior|behaviour|prompt|decision|system|gate)|allowedTools|disallowedTools|shell-quote|shellQuote|\.quote\(|PreToolUse|PostToolUse|hookEventName|"Bash"|Bash tool|isCommandAllowed|canUseBash|checkPermissions|permissionRule|toolPermission)/i;
422
428
  const buildRe = /(node-gyp|npm install|install script|spawnSync\(\s*["'`])(?:[a-z\-]+ )?[a-z\-]+(?:\s|["'`])/i;
423
429
  // call site where the command arg is a fixed literal ("cmd ...")
@@ -456,9 +462,14 @@ function checkCommandExec(file) {
456
462
  if (seen.has('exec:' + i)) return;
457
463
  seen.add('exec:' + i);
458
464
  const ctx = lineWindow(file.lines, i);
459
- const looksDynamic = dynamicCmd.test(line) || /\$\{/.test(ctx);
465
+ // Variable passed straight through as the command arg, e.g. exec(userInput)
466
+ // or execSync(cmd, opts) — no template/concat syntax but still fully
467
+ // attacker-controllable. String literals are excluded by the identifier
468
+ // pattern; method chains like obj.execute(x) are not matched (execCall).
469
+ const varArg = /(?:exec|execSync)\s*\(\s*[A-Za-z_$][\w$]*\s*[,\)]/.test(line);
470
+ const looksDynamic = dynamicCmd.test(line) || /\$\{/.test(ctx) || varArg;
460
471
  findings.push(mkFinding(file, i + 1, looksDynamic ? 'warn' : 'info',
461
- `child_process exec/execSync 调用(始终经 shell)${looksDynamic ? ',参数疑似动态拼接 — 检查注入面' : ',参数为静态/库内字符串'}`,
472
+ `child_process exec/execSync 调用(始终经 shell)${looksDynamic ? ',命令参数疑似动态/外部输入 — 检查命令注入面' : ',参数为静态/库内字符串'}`,
462
473
  { suppressed: !looksDynamic, snippet: snippet(line), checkId: 'command-exec' }));
463
474
  }
464
475
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "correctover-scan",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Correctover Security Scanner — CCS audit for MCP configurations AND published JS bundles/npm packages. Detects hardcoded secrets, RCE, SSRF, credential hijacking against OWASP AISVS 1.0.",
5
5
  "main": "index.js",
6
6
  "bin": {