codesentry 0.1.10 → 0.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.
Files changed (3) hide show
  1. package/README.md +58 -3
  2. package/dist/index.js +932 -208
  3. package/package.json +7 -4
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync as readFileSync2 } from "fs";
5
- import { createRequire as createRequire4 } from "module";
4
+ import { readFileSync as readFileSync3 } from "fs";
5
+ import { createRequire as createRequire5 } from "module";
6
6
  import { Command } from "commander";
7
7
  import figlet from "figlet";
8
8
  import gradient from "gradient-string";
@@ -45,13 +45,13 @@ var isExecImport = (specifier) => {
45
45
  return specifier.type === "ImportSpecifier" && imported?.type === "Identifier" && EXEC_METHOD_NAMES.has(imported.name);
46
46
  };
47
47
  var isNamespaceImport = (specifier) => specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier";
48
+ var bindingSetForImportSpecifier = (specifier, bindings) => isExecImport(specifier) ? bindings.directCalls : isNamespaceImport(specifier) ? bindings.namespaces : void 0;
48
49
  var collectImportBinding = (specifier, bindings) => {
49
50
  const name = localName(specifier);
50
51
  if (!name) {
51
52
  return;
52
53
  }
53
- const bindingSet = isExecImport(specifier) ? bindings.directCalls : isNamespaceImport(specifier) ? bindings.namespaces : void 0;
54
- bindingSet?.add(name);
54
+ bindingSetForImportSpecifier(specifier, bindings)?.add(name);
55
55
  };
56
56
  var collectFromImportDeclaration = (node, bindings) => {
57
57
  if (!isChildProcessModuleSpecifier(node.source)) {
@@ -71,13 +71,15 @@ var collectDirectCallBindings = (id, bindings) => {
71
71
  }
72
72
  }
73
73
  };
74
+ var namespaceNameFromDeclaratorId = (id) => id?.type === "Identifier" ? id.name : void 0;
75
+ var destructuredBindingsFromDeclaratorId = (id) => id?.type === "ObjectPattern" ? id : void 0;
74
76
  var collectFromVariableDeclarator = (node, bindings) => {
75
77
  if (!isRequireCall(node.init)) {
76
78
  return;
77
79
  }
78
80
  const id = node.id;
79
- const namespaceName = id?.type === "Identifier" ? id.name : void 0;
80
- const destructuredBindings = id?.type === "ObjectPattern" ? id : void 0;
81
+ const namespaceName = namespaceNameFromDeclaratorId(id);
82
+ const destructuredBindings = destructuredBindingsFromDeclaratorId(id);
81
83
  namespaceName && bindings.namespaces.add(namespaceName);
82
84
  destructuredBindings && collectDirectCallBindings(destructuredBindings, bindings);
83
85
  };
@@ -92,11 +94,8 @@ var collectChildProcessBindings = (sourceFile) => {
92
94
  });
93
95
  return bindings;
94
96
  };
95
- var isKnownChildProcessCallee = (callee, bindings) => {
96
- if (callee?.type === "Identifier") {
97
- return bindings.directCalls.has(callee.name);
98
- }
99
- if (callee?.type !== "MemberExpression") {
97
+ var isKnownChildProcessMemberCallee = (callee, bindings) => {
98
+ if (callee.type !== "MemberExpression") {
100
99
  return false;
101
100
  }
102
101
  const object = callee.object;
@@ -104,6 +103,12 @@ var isKnownChildProcessCallee = (callee, bindings) => {
104
103
  const methodName = property?.type === "Identifier" ? property.name : void 0;
105
104
  return !!methodName && EXEC_METHOD_NAMES.has(methodName) && object?.type === "Identifier" && bindings.namespaces.has(object.name);
106
105
  };
106
+ var isKnownChildProcessCallee = (callee, bindings) => {
107
+ if (callee?.type === "Identifier") {
108
+ return bindings.directCalls.has(callee.name);
109
+ }
110
+ return callee !== void 0 && isKnownChildProcessMemberCallee(callee, bindings);
111
+ };
107
112
  var isDynamicCommandArgument = (argument) => argument?.type === "StringLiteral" ? false : argument?.type === "TemplateLiteral" ? (argument.expressions?.length ?? 0) > 0 : argument !== void 0;
108
113
  var isCommandInjectionCall = (node, bindings) => {
109
114
  if (node.type !== "CallExpression") {
@@ -141,9 +146,12 @@ var commandInjectionRule = {
141
146
  }
142
147
  };
143
148
 
149
+ // src/commands/scan/scan-options.ts
150
+ var withScanOptions = (command) => command.option("--json", "exibe o resultado em JSON").option("--tests", "inclui arquivos de teste na an\xE1lise (por padr\xE3o s\xE3o ignorados)");
151
+
144
152
  // src/commands/scan/scan-runner.ts
145
153
  import { writeFile } from "fs/promises";
146
- import { join as join2 } from "path";
154
+ import { join as join3 } from "path";
147
155
  import chalk2 from "chalk";
148
156
  import { Listr } from "listr2";
149
157
 
@@ -169,15 +177,53 @@ var formatErrorChain = (error) => {
169
177
  // src/reporters/console.reporter.ts
170
178
  import chalk from "chalk";
171
179
  import Table from "cli-table3";
180
+
181
+ // src/scanner/scan-result.ts
182
+ var DEPENDENCY_AUDIT_NOTE = 'Dependency audit n\xE3o foi inclu\xEDdo neste scan \u2014 rode "codesentry dependency-audit" separadamente.';
183
+ var ZERO_SEMGREP_COVERAGE_WARNING = "Semgrep n\xE3o analisou nenhum arquivo nesta execu\xE7\xE3o \u2014 verifique se o ruleset offline est\xE1 instalado/preparado.";
184
+ var mergeScanResults = (nativeResult, semgrepResult) => ({
185
+ scannedFiles: nativeResult.scannedFiles,
186
+ findings: [...nativeResult.findings, ...semgrepResult.findings],
187
+ durationMs: nativeResult.durationMs + semgrepResult.durationMs,
188
+ engines: {
189
+ codesentry: nativeResult.engines?.codesentry ?? nativeResult.scannedFiles,
190
+ semgrep: semgrepResult.engines?.semgrep ?? semgrepResult.scannedFiles
191
+ }
192
+ });
193
+ var finalizeScanResult = (result, dependencyAuditCoverage2 = false) => ({
194
+ ...result,
195
+ engines: { ...result.engines, dependencyAudit: dependencyAuditCoverage2 },
196
+ warnings: result.engines?.semgrep === 0 ? [...result.warnings ?? [], ZERO_SEMGREP_COVERAGE_WARNING] : result.warnings
197
+ });
198
+
199
+ // src/reporters/console.reporter.ts
172
200
  var SEVERITY_COLOR = {
173
201
  low: (text2) => chalk.gray(text2),
174
202
  medium: (text2) => chalk.yellow(text2),
175
203
  high: (text2) => chalk.red(text2),
176
204
  critical: (text2) => chalk.bgRed.white(text2)
177
205
  };
178
- var coverageText = (result) => result.engines?.semgrep === void 0 ? "" : ` CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s).`;
206
+ var semgrepCoverage = (result) => result.engines?.semgrep === void 0 ? void 0 : `CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s)`;
207
+ var dependencyAuditCoverage = (result) => typeof result.engines?.dependencyAudit === "number" ? `Dependency audit: ${result.engines.dependencyAudit} pacote(s) via npm audit` : void 0;
208
+ var osvCoverage = (result) => result.engines?.osv ? `OSV.dev: ${result.engines.osv.checked}/${result.engines.osv.total} verificados` : void 0;
209
+ var coverageParts = (result) => [semgrepCoverage(result), dependencyAuditCoverage(result), osvCoverage(result)].filter(
210
+ (part) => part !== void 0
211
+ );
212
+ var coverageText = (result) => {
213
+ const parts = coverageParts(result);
214
+ return parts.length ? ` ${parts.join("; ")}.` : "";
215
+ };
216
+ var printNotes = (result) => {
217
+ if (result.engines?.dependencyAudit === false) {
218
+ console.log(chalk.cyan(DEPENDENCY_AUDIT_NOTE));
219
+ }
220
+ for (const warning of result.warnings ?? []) {
221
+ console.log(chalk.yellow(`Aviso: ${warning}`));
222
+ }
223
+ };
179
224
  var printCleanReport = (result, coverage) => {
180
225
  console.log(chalk.green(`Nenhum problema encontrado (${result.scannedFiles} arquivos analisados).${coverage}`));
226
+ printNotes(result);
181
227
  };
182
228
  var findingsTable = (result) => {
183
229
  const table = new Table({ head: ["Severity", "Rule", "File", "Line", "Message"] });
@@ -201,6 +247,7 @@ var printFindingsReport = (result, coverage) => {
201
247
  ${result.findings.length} problema(s) encontrado(s) em ${result.scannedFiles} arquivo(s) (${result.durationMs}ms).${coverage}`
202
248
  )
203
249
  );
250
+ printNotes(result);
204
251
  };
205
252
  var printConsoleReport = (result) => {
206
253
  const coverage = coverageText(result);
@@ -261,8 +308,10 @@ var reportHeader = (result, generatedAt) => [
261
308
  ],
262
309
  `- **Dura\xE7\xE3o:** ${result.durationMs}ms`,
263
310
  `- **Total de problemas:** ${result.findings.length}`,
311
+ ...result.engines?.dependencyAudit === false ? [`- **Nota:** ${DEPENDENCY_AUDIT_NOTE}`] : [],
264
312
  ""
265
313
  ];
314
+ var warningsSection = (result) => (result.warnings ?? []).length === 0 ? [] : ["## Avisos", "", ...(result.warnings ?? []).map((warning) => `- ${warning}`), ""];
266
315
  var summaryTable = (bySeverity) => {
267
316
  const lines = ["## Resumo por severidade", "", "| Severidade | Quantidade |", "| --- | --- |"];
268
317
  for (const severity of SEVERITY_ORDER) {
@@ -281,76 +330,410 @@ var severitySections = (bySeverity) => {
281
330
  }
282
331
  return lines;
283
332
  };
333
+ var osvCheckedSection = (result) => {
334
+ const { osvCheckedPackages } = result;
335
+ if (!osvCheckedPackages || osvCheckedPackages.length === 0) {
336
+ return [];
337
+ }
338
+ const checked = result.engines?.osv?.checked ?? osvCheckedPackages.length;
339
+ const total = result.engines?.osv?.total ?? osvCheckedPackages.length;
340
+ return [
341
+ `## Depend\xEAncias verificadas no OSV.dev (${checked}/${total})`,
342
+ "",
343
+ ...osvCheckedPackages.map((pkg) => `- ${pkg}`),
344
+ ""
345
+ ];
346
+ };
284
347
  var toMarkdownReport = (result, generatedAt = /* @__PURE__ */ new Date()) => {
285
348
  const bySeverity = groupBy(result.findings, (f) => f.severity);
286
- return [...reportHeader(result, generatedAt), ...summaryTable(bySeverity), ...severitySections(bySeverity)].join(
287
- "\n"
288
- );
349
+ return [
350
+ ...reportHeader(result, generatedAt),
351
+ ...warningsSection(result),
352
+ ...summaryTable(bySeverity),
353
+ ...severitySections(bySeverity),
354
+ ...osvCheckedSection(result)
355
+ ].join("\n");
289
356
  };
290
357
 
291
- // src/scanner/scan-result.ts
292
- var mergeScanResults = (nativeResult, semgrepResult) => ({
293
- scannedFiles: nativeResult.scannedFiles,
294
- findings: [...nativeResult.findings, ...semgrepResult.findings],
295
- durationMs: nativeResult.durationMs + semgrepResult.durationMs,
296
- engines: {
297
- codesentry: nativeResult.engines?.codesentry ?? nativeResult.scannedFiles,
298
- semgrep: semgrepResult.engines?.semgrep ?? semgrepResult.scannedFiles
358
+ // src/scanner/dependency-audit.ts
359
+ import { execFile } from "child_process";
360
+ import { readFile } from "fs/promises";
361
+ import { join } from "path";
362
+ import { promisify } from "util";
363
+
364
+ // src/scanner/run-with-concurrency-limit.ts
365
+ import pLimit from "p-limit";
366
+ var runWithConcurrencyLimit = async (items, concurrency, task) => {
367
+ const limit = pLimit(concurrency);
368
+ return Promise.all(items.map((item) => limit(() => task(item))));
369
+ };
370
+
371
+ // src/scanner/osv-client.ts
372
+ var OSV_BATCH_CHUNK_SIZE = 100;
373
+ var OSV_DETAIL_CONCURRENCY = 10;
374
+ var OSV_API_BASE = "https://api.osv.dev/v1";
375
+ var SEVERITY_MAP = {
376
+ LOW: "low",
377
+ MODERATE: "medium",
378
+ HIGH: "high",
379
+ CRITICAL: "critical"
380
+ };
381
+ var chunk = (items, size) => {
382
+ const chunks = [];
383
+ for (let i = 0; i < items.length; i += size) {
384
+ chunks.push(items.slice(i, i + size));
299
385
  }
300
- });
386
+ return chunks;
387
+ };
388
+ var zipVulnIdsByPackage = (packages, body) => {
389
+ const vulnIdsByPackage = /* @__PURE__ */ new Map();
390
+ packages.forEach((pkg, index) => {
391
+ const ids = body.results?.[index]?.vulns?.map((v) => v.id);
392
+ if (ids?.length) {
393
+ vulnIdsByPackage.set(`${pkg.name}@${pkg.version}`, ids);
394
+ }
395
+ });
396
+ return vulnIdsByPackage;
397
+ };
398
+ var queryBatchChunk = async (packages, fetchImpl) => {
399
+ try {
400
+ const response = await fetchImpl(`${OSV_API_BASE}/querybatch`, {
401
+ method: "POST",
402
+ headers: { "Content-Type": "application/json" },
403
+ body: JSON.stringify({
404
+ queries: packages.map((p) => ({
405
+ package: { name: p.name, ecosystem: "npm" },
406
+ version: p.version
407
+ }))
408
+ })
409
+ });
410
+ if (!response.ok) {
411
+ return { vulnIdsByPackage: /* @__PURE__ */ new Map(), checkedPackages: [], failedCount: packages.length };
412
+ }
413
+ const body = await response.json();
414
+ return { vulnIdsByPackage: zipVulnIdsByPackage(packages, body), checkedPackages: packages, failedCount: 0 };
415
+ } catch {
416
+ return { vulnIdsByPackage: /* @__PURE__ */ new Map(), checkedPackages: [], failedCount: packages.length };
417
+ }
418
+ };
419
+ var queryOsvBatch = async (packages, fetchImpl = fetch) => {
420
+ const vulnIdsByPackage = /* @__PURE__ */ new Map();
421
+ const checkedPackages = [];
422
+ let failedCount = 0;
423
+ try {
424
+ for (const packageChunk of chunk(packages, OSV_BATCH_CHUNK_SIZE)) {
425
+ const result = await queryBatchChunk(packageChunk, fetchImpl);
426
+ result.vulnIdsByPackage.forEach((ids, key) => vulnIdsByPackage.set(key, ids));
427
+ checkedPackages.push(...result.checkedPackages);
428
+ failedCount += result.failedCount;
429
+ }
430
+ } catch {
431
+ failedCount += packages.length - checkedPackages.length;
432
+ }
433
+ return {
434
+ vulnIdsByPackage,
435
+ checkedPackages,
436
+ warning: failedCount > 0 ? `N\xE3o foi poss\xEDvel consultar o OSV.dev para ${failedCount} pacote(s).` : void 0
437
+ };
438
+ };
439
+ var fetchOneVulnerabilityDetail = async (id, fetchImpl, detailsById) => {
440
+ try {
441
+ const response = await fetchImpl(`${OSV_API_BASE}/vulns/${id}`);
442
+ if (!response.ok) {
443
+ return false;
444
+ }
445
+ detailsById.set(id, await response.json());
446
+ return true;
447
+ } catch {
448
+ return false;
449
+ }
450
+ };
451
+ var fetchOsvVulnerabilityDetails = async (ids, fetchImpl = fetch) => {
452
+ const uniqueIds = [...new Set(ids)];
453
+ const detailsById = /* @__PURE__ */ new Map();
454
+ let results;
455
+ try {
456
+ results = await runWithConcurrencyLimit(
457
+ uniqueIds,
458
+ OSV_DETAIL_CONCURRENCY,
459
+ (id) => fetchOneVulnerabilityDetail(id, fetchImpl, detailsById)
460
+ );
461
+ } catch (error) {
462
+ throw error;
463
+ }
464
+ const failedCount = results.filter((ok) => !ok).length;
465
+ return {
466
+ detailsById,
467
+ warning: failedCount > 0 ? `N\xE3o foi poss\xEDvel obter detalhes do OSV.dev para ${failedCount} advisory(s).` : void 0
468
+ };
469
+ };
470
+ var isMatchingNpmPackage = (affected, packageName) => affected.package.ecosystem === "npm" && affected.package.name === packageName;
471
+ var extractFixedVersions = (vuln, packageName) => {
472
+ const fixedVersions = (vuln.affected ?? []).filter((affected) => isMatchingNpmPackage(affected, packageName)).flatMap((affected) => affected.ranges ?? []).flatMap((range) => range.events).map((event) => event.fixed).filter((fixed) => Boolean(fixed));
473
+ return [...new Set(fixedVersions)];
474
+ };
475
+ var mapOsvSeverity = (vuln) => {
476
+ const severity = vuln.database_specific?.severity;
477
+ if (!severity) {
478
+ return "medium";
479
+ }
480
+ return SEVERITY_MAP[severity] ?? "medium";
481
+ };
482
+
483
+ // src/scanner/package-lock-parser.ts
484
+ var NODE_MODULES_SEGMENT = "node_modules/";
485
+ var isUnresolvableSource = (resolved) => resolved !== void 0 && (resolved.startsWith("file:") || resolved.startsWith("git"));
486
+ var nameFromKey = (key) => {
487
+ const lastIndex = key.lastIndexOf(NODE_MODULES_SEGMENT);
488
+ return lastIndex === -1 ? void 0 : key.slice(lastIndex + NODE_MODULES_SEGMENT.length);
489
+ };
490
+ var toLockedPackage = (key, entry) => {
491
+ if (entry.link || !entry.version || isUnresolvableSource(entry.resolved)) {
492
+ return void 0;
493
+ }
494
+ const name = entry.name ?? nameFromKey(key);
495
+ return name ? { name, version: entry.version } : void 0;
496
+ };
497
+ var assertSupportedLockfileVersion = (lockfileVersion) => {
498
+ if (lockfileVersion !== 2 && lockfileVersion !== 3) {
499
+ throw new Error("lockfileVersion 1 n\xE3o \xE9 suportado \u2014 regenere o lockfile com npm 7+.");
500
+ }
501
+ };
502
+ var parsePackageLock = (rawJson) => {
503
+ const parsed = JSON.parse(rawJson);
504
+ assertSupportedLockfileVersion(parsed.lockfileVersion);
505
+ const deduped = /* @__PURE__ */ new Map();
506
+ Object.entries(parsed.packages ?? {}).filter(([key]) => key.includes(NODE_MODULES_SEGMENT)).forEach(([key, entry]) => {
507
+ const locked = toLockedPackage(key, entry);
508
+ if (locked) {
509
+ deduped.set(`${locked.name}@${locked.version}`, locked);
510
+ }
511
+ });
512
+ return [...deduped.values()];
513
+ };
514
+
515
+ // src/scanner/dependency-audit.ts
516
+ var execFileAsync = promisify(execFile);
517
+ var SEVERITY_MAP2 = {
518
+ info: "low",
519
+ low: "low",
520
+ moderate: "medium",
521
+ high: "high",
522
+ critical: "critical"
523
+ };
524
+ var vulnerabilityTitle = (vulnerability) => {
525
+ const firstVia = vulnerability.via[0];
526
+ if (typeof firstVia === "object" && firstVia?.title) {
527
+ return firstVia.title;
528
+ }
529
+ return 'ver "npm audit" para detalhes';
530
+ };
531
+ var npmFixSuggestion = (vulnerability) => {
532
+ const { fixAvailable } = vulnerability;
533
+ if (fixAvailable === false) {
534
+ return "sem corre\xE7\xE3o dispon\xEDvel ainda";
535
+ }
536
+ if (fixAvailable === true) {
537
+ return `atualize para uma vers\xE3o fora do intervalo vulner\xE1vel (${vulnerability.range})`;
538
+ }
539
+ return `atualize para ${fixAvailable.name}@${fixAvailable.version}`;
540
+ };
541
+ var mapAuditReportToFindings = (report) => {
542
+ return Object.values(report.vulnerabilities).map((vulnerability) => ({
543
+ ruleId: "dependency-audit",
544
+ message: `Depend\xEAncia vulner\xE1vel: ${vulnerability.name} (${vulnerability.severity}) \u2014 ${vulnerabilityTitle(vulnerability)} \u2014 ${npmFixSuggestion(vulnerability)}`,
545
+ file: "package.json",
546
+ line: 1,
547
+ severity: SEVERITY_MAP2[vulnerability.severity]
548
+ }));
549
+ };
550
+ var osvFixSuggestion = (fixedVersions) => fixedVersions.length ? `atualize para ${fixedVersions.join(" ou ")}` : "nenhuma vers\xE3o corrigida publicada pelo OSV.dev ainda";
551
+ var buildOsvFinding = (pkg, vuln) => {
552
+ const fixedVersions = extractFixedVersions(vuln, pkg.name);
553
+ return {
554
+ ruleId: "dependency-audit",
555
+ message: `OSV ${vuln.id}: ${pkg.name}@${pkg.version} (${mapOsvSeverity(vuln)}) \u2014 ${vuln.summary ?? "ver OSV.dev para detalhes"} \u2014 ${osvFixSuggestion(fixedVersions)}`,
556
+ file: "package-lock.json",
557
+ line: 1,
558
+ severity: mapOsvSeverity(vuln)
559
+ };
560
+ };
561
+ var findingsForLockedPackage = (pkg, vulnIdsByPackage, detailsById) => {
562
+ const ids = vulnIdsByPackage.get(`${pkg.name}@${pkg.version}`) ?? [];
563
+ return ids.map((id) => detailsById.get(id)).filter((vuln) => vuln !== void 0).map((vuln) => buildOsvFinding(pkg, vuln));
564
+ };
565
+ var mapOsvFindingsToRuleFindings = (lockedPackages, vulnIdsByPackage, detailsById, npmFlaggedNames) => lockedPackages.filter((pkg) => !npmFlaggedNames.has(pkg.name)).flatMap((pkg) => findingsForLockedPackage(pkg, vulnIdsByPackage, detailsById));
566
+ var errorMessage = (error) => error instanceof Error ? error.message : "erro desconhecido";
567
+ var hasVulnerabilitiesRecord = (value) => typeof value === "object" && value !== null && typeof value.vulnerabilities === "object" && value.vulnerabilities !== null;
568
+ var normalizeNpmAuditReport = (raw) => {
569
+ if (hasVulnerabilitiesRecord(raw)) {
570
+ return { report: { vulnerabilities: raw.vulnerabilities } };
571
+ }
572
+ const errorSummary = typeof raw === "object" && raw !== null && "error" in raw ? raw.error?.summary ?? "formato de resposta inesperado" : "formato de resposta inesperado";
573
+ return {
574
+ report: { vulnerabilities: {} },
575
+ warning: `"npm audit" n\xE3o retornou um relat\xF3rio v\xE1lido: ${errorSummary}.`
576
+ };
577
+ };
578
+ var combineWarnings = (...warnings) => {
579
+ const present = warnings.filter((warning) => Boolean(warning));
580
+ return present.length ? present.join(" ") : void 0;
581
+ };
582
+ var fetchDetailsForIds = (ids, fetchImpl) => ids.length ? fetchOsvVulnerabilityDetails(ids, fetchImpl) : Promise.resolve({ detailsById: /* @__PURE__ */ new Map(), warning: void 0 });
583
+ var auditPackagesWithOsv = async (lockedPackages, npmFlaggedNames, fetchImpl = fetch) => {
584
+ try {
585
+ return await collectOsvAuditResult(lockedPackages, npmFlaggedNames, fetchImpl);
586
+ } catch (error) {
587
+ return {
588
+ findings: [],
589
+ checkedPackages: [],
590
+ warning: `N\xE3o foi poss\xEDvel consultar o OSV.dev: ${errorMessage(error)}.`
591
+ };
592
+ }
593
+ };
594
+ var collectOsvAuditResult = async (lockedPackages, npmFlaggedNames, fetchImpl) => {
595
+ try {
596
+ const {
597
+ vulnIdsByPackage,
598
+ checkedPackages,
599
+ warning: batchWarning
600
+ } = await queryOsvBatch(lockedPackages, fetchImpl);
601
+ const ids = [...new Set([...vulnIdsByPackage.values()].flat())];
602
+ const { detailsById, warning: detailsWarning } = await fetchDetailsForIds(ids, fetchImpl);
603
+ return {
604
+ findings: mapOsvFindingsToRuleFindings(
605
+ lockedPackages,
606
+ vulnIdsByPackage,
607
+ detailsById,
608
+ npmFlaggedNames
609
+ ),
610
+ checkedPackages,
611
+ warning: combineWarnings(batchWarning, detailsWarning)
612
+ };
613
+ } catch (error) {
614
+ throw new Error("N\xE3o foi poss\xEDvel consolidar os resultados do OSV.dev.", { cause: error });
615
+ }
616
+ };
617
+ var runNpmAudit = async (targetDir) => {
618
+ let stdout;
619
+ try {
620
+ ({ stdout } = await execFileAsync("npm", ["audit", "--json"], {
621
+ cwd: targetDir,
622
+ maxBuffer: 10 * 1024 * 1024
623
+ }));
624
+ } catch (error) {
625
+ const stdoutFromError = error.stdout;
626
+ if (!stdoutFromError) {
627
+ throw new Error(`N\xE3o foi poss\xEDvel executar "npm audit" em "${targetDir}".`, { cause: error });
628
+ }
629
+ stdout = stdoutFromError;
630
+ }
631
+ return JSON.parse(stdout);
632
+ };
633
+ var auditPackagesFromLockfile = async (targetDir, npmFlaggedNames, fetchImpl) => {
634
+ try {
635
+ const raw = await readFile(join(targetDir, "package-lock.json"), "utf-8");
636
+ const lockedPackages = parsePackageLock(raw);
637
+ const osvResult = await auditPackagesWithOsv(lockedPackages, npmFlaggedNames, fetchImpl);
638
+ return {
639
+ findings: osvResult.findings,
640
+ packagesAudited: lockedPackages.length,
641
+ osvChecked: { checked: osvResult.checkedPackages.length, total: lockedPackages.length },
642
+ osvCheckedPackages: osvResult.checkedPackages.map((pkg) => `${pkg.name}@${pkg.version}`).sort(),
643
+ warning: osvResult.warning
644
+ };
645
+ } catch (error) {
646
+ return {
647
+ findings: [],
648
+ packagesAudited: false,
649
+ warning: `N\xE3o foi poss\xEDvel checar o OSV.dev: ${errorMessage(error)}.`
650
+ };
651
+ }
652
+ };
653
+ var runDependencyAudit = async (targetDir, fetchImpl = fetch) => {
654
+ const startedAt = Date.now();
655
+ try {
656
+ const { report: npmReport, warning: npmWarning } = normalizeNpmAuditReport(await runNpmAudit(targetDir));
657
+ const npmFindings = mapAuditReportToFindings(npmReport);
658
+ const npmFlaggedNames = new Set(Object.keys(npmReport.vulnerabilities));
659
+ const osv = await auditPackagesFromLockfile(targetDir, npmFlaggedNames, fetchImpl);
660
+ const warnings = combineWarnings(npmWarning, osv.warning);
661
+ return {
662
+ scannedFiles: 1,
663
+ findings: [...npmFindings, ...osv.findings],
664
+ durationMs: Date.now() - startedAt,
665
+ engines: {
666
+ dependencyAudit: osv.packagesAudited === false ? npmFlaggedNames.size : osv.packagesAudited,
667
+ osv: osv.osvChecked
668
+ },
669
+ osvCheckedPackages: osv.osvCheckedPackages,
670
+ warnings: warnings ? [warnings] : void 0
671
+ };
672
+ } catch (error) {
673
+ throw new Error(`N\xE3o foi poss\xEDvel auditar depend\xEAncias em "${targetDir}".`, { cause: error });
674
+ }
675
+ };
301
676
 
302
677
  // src/scanner/scanner.ts
303
- import { readFile } from "fs/promises";
678
+ import { readFile as readFile2 } from "fs/promises";
304
679
  import { availableParallelism } from "os";
305
680
 
306
681
  // src/scanner/file-finder.ts
307
682
  import { readdir } from "fs/promises";
308
- import { join } from "path";
683
+ import { join as join2 } from "path";
684
+
685
+ // src/scanner/ignore-patterns.ts
686
+ var ALWAYS_IGNORED_DIR_NAMES = ["node_modules", ".git", "dist", ".next", ".angular"];
687
+ var TEST_DIR_NAMES = ["tests", "test", "__tests__"];
688
+ var TEST_FILE_GLOBS = ["*.spec.*", "*.test.*"];
689
+ var TEST_FILE_NAME_PATTERN = /\.(spec|test)\.[^./]+$/;
690
+ var isIgnoredDirName = (name, includeTests = false) => ALWAYS_IGNORED_DIR_NAMES.includes(name) || !includeTests && TEST_DIR_NAMES.includes(name);
691
+ var isTestFileName = (fileName) => TEST_FILE_NAME_PATTERN.test(fileName);
692
+
693
+ // src/scanner/file-finder.ts
309
694
  var SCANNABLE_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx"];
310
- var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "tests"]);
311
695
  var isScannable = (fileName) => SCANNABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
312
- var filesFromEntry = async (currentDir, entry) => {
696
+ var filesFromDirectory = async (currentDir, entry, includeTests) => {
697
+ return isIgnoredDirName(entry.name, includeTests) ? [] : walk(join2(currentDir, entry.name), includeTests);
698
+ };
699
+ var filesFromFile = (currentDir, entry, includeTests) => {
700
+ return entry.isFile() && isScannable(entry.name) && (includeTests || !isTestFileName(entry.name)) ? [join2(currentDir, entry.name)] : [];
701
+ };
702
+ var filesFromEntry = async (currentDir, entry, includeTests) => {
313
703
  if (entry.isDirectory()) {
314
- return IGNORED_DIRS.has(entry.name) ? [] : walk(join(currentDir, entry.name));
704
+ return filesFromDirectory(currentDir, entry, includeTests);
315
705
  }
316
- return entry.isFile() && isScannable(entry.name) ? [join(currentDir, entry.name)] : [];
706
+ return filesFromFile(currentDir, entry, includeTests);
317
707
  };
318
- var walk = async (currentDir) => {
708
+ var walk = async (currentDir, includeTests) => {
319
709
  try {
320
710
  const entries = await readdir(currentDir, { withFileTypes: true });
321
- return (await Promise.all(entries.map((entry) => filesFromEntry(currentDir, entry)))).flat();
711
+ return (await Promise.all(entries.map((entry) => filesFromEntry(currentDir, entry, includeTests)))).flat();
322
712
  } catch (error) {
323
713
  throw new Error(`N\xE3o foi poss\xEDvel listar os arquivos em "${currentDir}".`, { cause: error });
324
714
  }
325
715
  };
326
- var findFiles = async (targetDir) => {
716
+ var findFiles = async (targetDir, includeTests = false) => {
327
717
  try {
328
- return await walk(targetDir);
718
+ return await walk(targetDir, includeTests);
329
719
  } catch (error) {
330
720
  throw new Error(`N\xE3o foi poss\xEDvel listar os arquivos em "${targetDir}".`, { cause: error });
331
721
  }
332
722
  };
333
723
 
334
- // src/scanner/run-with-concurrency-limit.ts
335
- import pLimit from "p-limit";
336
- var runWithConcurrencyLimit = async (items, concurrency, task) => {
337
- const limit = pLimit(concurrency);
338
- return Promise.all(items.map((item) => limit(() => task(item))));
339
- };
340
-
341
724
  // src/scanner/scanner.ts
342
725
  var DEFAULT_SCAN_CONCURRENCY = Math.max(1, Math.min(8, availableParallelism()));
343
- var errorMessage = (error) => error instanceof Error ? error.message : "erro desconhecido";
726
+ var errorMessage2 = (error) => error instanceof Error ? error.message : "erro desconhecido";
344
727
  var parseErrorFinding = (filePath, error) => ({
345
728
  ruleId: "parse-error",
346
- message: `N\xE3o foi poss\xEDvel analisar este arquivo (erro de sintaxe): ${errorMessage(error)}`,
729
+ message: `N\xE3o foi poss\xEDvel analisar este arquivo (erro de sintaxe): ${errorMessage2(error)}`,
347
730
  file: filePath,
348
731
  line: 1,
349
732
  severity: "low"
350
733
  });
351
734
  var readFileContent = async (filePath) => {
352
735
  try {
353
- return await readFile(filePath, "utf-8");
736
+ return await readFile2(filePath, "utf-8");
354
737
  } catch (error) {
355
738
  throw new Error(`N\xE3o foi poss\xEDvel ler o arquivo "${filePath}".`, { cause: error });
356
739
  }
@@ -380,10 +763,10 @@ var scanFile = async (filePath, rules) => {
380
763
  throw new Error(`N\xE3o foi poss\xEDvel analisar o arquivo "${filePath}".`, { cause: error });
381
764
  }
382
765
  };
383
- var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY) => {
766
+ var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY, includeTests = false) => {
384
767
  try {
385
768
  const startedAt = Date.now();
386
- const files = await findFiles(targetDir);
769
+ const files = await findFiles(targetDir, includeTests);
387
770
  const findings = (await runWithConcurrencyLimit(files, concurrency, (filePath) => scanFile(filePath, rules))).flat();
388
771
  return {
389
772
  scannedFiles: files.length,
@@ -397,9 +780,9 @@ var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY) =
397
780
  };
398
781
 
399
782
  // src/scanner/semgrep.ts
400
- import { execFile } from "child_process";
783
+ import { execFile as execFile2 } from "child_process";
401
784
  import { delimiter, dirname as dirname2 } from "path";
402
- import { promisify } from "util";
785
+ import { promisify as promisify2 } from "util";
403
786
 
404
787
  // src/scanner/semgrep-runtime.ts
405
788
  import { existsSync, readFileSync } from "fs";
@@ -416,6 +799,7 @@ var packageForPlatform = (platform, architecture) => {
416
799
  return void 0;
417
800
  };
418
801
  var unsupportedRuntimeMessage = (platform, architecture) => `O runtime Semgrep embutido n\xE3o est\xE1 dispon\xEDvel para ${platform}-${architecture}. O CodeSentry oferece suporte a Linux x64 e Windows x64.`;
802
+ var errorReason = (error) => error instanceof Error ? error.message : "erro desconhecido";
419
803
  var resolveBundledSemgrepRuntime = (platform = process.platform, architecture = process.arch, resolveManifestPath = (packageName) => require2.resolve(`${packageName}/runtime.json`)) => {
420
804
  const packageName = packageForPlatform(platform, architecture);
421
805
  if (!packageName) {
@@ -432,8 +816,7 @@ var resolveBundledSemgrepRuntime = (platform = process.platform, architecture =
432
816
  }
433
817
  return runtime;
434
818
  } catch (error) {
435
- const reason = error instanceof Error ? error.message : "erro desconhecido";
436
- throw new Error(`N\xE3o foi poss\xEDvel carregar o runtime Semgrep embutido: ${reason}`, { cause: error });
819
+ throw new Error(`N\xE3o foi poss\xEDvel carregar o runtime Semgrep embutido: ${errorReason(error)}`, { cause: error });
437
820
  }
438
821
  };
439
822
 
@@ -455,7 +838,7 @@ var resolveBundledSemgrepRuleset = () => {
455
838
  };
456
839
 
457
840
  // src/scanner/semgrep.ts
458
- var execFileAsync = promisify(execFile);
841
+ var execFileAsync2 = promisify2(execFile2);
459
842
  var SEMGREP_SEVERITIES = {
460
843
  INFO: "low",
461
844
  WARNING: "medium",
@@ -482,23 +865,16 @@ var mapSemgrepReportToFindings = (report) => report.results.map((finding) => ({
482
865
  severity: SEMGREP_SEVERITIES[finding.extra.severity] ?? "medium"
483
866
  }));
484
867
  var outputFromError = (error) => typeof error.stdout === "string" ? error.stdout : void 0;
485
- var semgrepArgs = (ruleset) => [
868
+ var excludeArgs = (names) => names.flatMap((name) => ["--exclude", name]);
869
+ var semgrepArgs = (ruleset, includeTests) => [
486
870
  "scan",
487
871
  "--config",
488
872
  ruleset,
489
873
  "--metrics=off",
490
874
  "--json",
491
875
  "--quiet",
492
- "--exclude",
493
- "node_modules",
494
- "--exclude",
495
- ".git",
496
- "--exclude",
497
- "dist",
498
- "--exclude",
499
- ".next",
500
- "--exclude",
501
- "tests",
876
+ ...excludeArgs(ALWAYS_IGNORED_DIR_NAMES),
877
+ ...includeTests ? [] : excludeArgs([...TEST_DIR_NAMES, ...TEST_FILE_GLOBS]),
502
878
  // Não repetir targetDir aqui: o processo já roda com cwd = targetDir,
503
879
  // então o alvo relativo a esse cwd é o diretório atual.
504
880
  "."
@@ -511,9 +887,9 @@ var semgrepEnvironment = (runtime) => {
511
887
  PATH: `${semgrepDir}${delimiter}${dirname2(semgrepDir)}${delimiter}${process.env.PATH ?? ""}${delimiter}${systemPathFallback}`
512
888
  };
513
889
  };
514
- var executeSemgrep = async (targetDir, runtime, ruleset, execute) => {
890
+ var executeSemgrep = async (targetDir, runtime, ruleset, execute, includeTests) => {
515
891
  try {
516
- const { stdout } = await execute(runtime.semgrep, semgrepArgs(ruleset), {
892
+ const { stdout } = await execute(runtime.semgrep, semgrepArgs(ruleset, includeTests), {
517
893
  cwd: targetDir,
518
894
  maxBuffer: 20 * 1024 * 1024,
519
895
  env: semgrepEnvironment(runtime)
@@ -527,10 +903,10 @@ var executeSemgrep = async (targetDir, runtime, ruleset, execute) => {
527
903
  throw new Error("N\xE3o foi poss\xEDvel executar o Semgrep embutido.", { cause: error });
528
904
  }
529
905
  };
530
- var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync) => {
906
+ var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync2, includeTests = false) => {
531
907
  const startedAt = Date.now();
532
908
  try {
533
- const report = parseSemgrepReport(await executeSemgrep(targetDir, runtime, ruleset, execute));
909
+ const report = parseSemgrepReport(await executeSemgrep(targetDir, runtime, ruleset, execute, includeTests));
534
910
  const scannedFiles = report.paths?.scanned.length ?? 0;
535
911
  return {
536
912
  scannedFiles,
@@ -551,7 +927,7 @@ var generateMarkdownReportFilename = (date = /* @__PURE__ */ new Date()) => {
551
927
  };
552
928
 
553
929
  // src/commands/scan/scan-runner.ts
554
- var errorMessage2 = (error) => error instanceof Error ? error.message : "erro desconhecido";
930
+ var errorMessage3 = (error) => error instanceof Error ? error.message : "erro desconhecido";
555
931
  var reportScanFailure = (error) => {
556
932
  process.exitCode = 1;
557
933
  console.error(`Falha ao executar o scan: ${formatErrorChain(error)}`);
@@ -567,25 +943,53 @@ var writeMarkdownReportIfNeeded = async (result, targetDir) => {
567
943
  if (result.findings.length <= MARKDOWN_REPORT_FINDINGS_THRESHOLD) {
568
944
  return;
569
945
  }
570
- const filePath = join2(targetDir, generateMarkdownReportFilename());
946
+ const filePath = join3(targetDir, generateMarkdownReportFilename());
571
947
  try {
572
948
  await writeFile(filePath, toMarkdownReport(result), "utf-8");
573
949
  console.log(chalk2.cyan(`
574
950
  Relat\xF3rio detalhado gerado em: ${filePath}`));
575
951
  } catch (error) {
576
- console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${errorMessage2(error)}`));
952
+ console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${errorMessage3(error)}`));
953
+ }
954
+ };
955
+ var runNativeAndSemgrep = async (path, rules, options) => {
956
+ try {
957
+ const includeTests = options.tests ?? false;
958
+ const nativeResult = await runScan(path, rules, options.concurrency, includeTests);
959
+ return options.semgrep ? mergeScanResults(
960
+ nativeResult,
961
+ await runBundledSemgrep(path, void 0, options.config, void 0, includeTests)
962
+ ) : nativeResult;
963
+ } catch (error) {
964
+ throw new Error(`Falha durante a an\xE1lise: ${errorMessage3(error)}`, { cause: error });
965
+ }
966
+ };
967
+ var runOptionalDependencyAudit = async (path, merged) => {
968
+ try {
969
+ const auditResult = await runDependencyAudit(path);
970
+ return finalizeScanResult(
971
+ {
972
+ ...merged,
973
+ findings: [...merged.findings, ...auditResult.findings],
974
+ warnings: [...merged.warnings ?? [], ...auditResult.warnings ?? []],
975
+ engines: { ...merged.engines, osv: auditResult.engines?.osv },
976
+ osvCheckedPackages: auditResult.osvCheckedPackages
977
+ },
978
+ auditResult.engines?.dependencyAudit ?? false
979
+ );
980
+ } catch (error) {
981
+ return finalizeScanResult({
982
+ ...merged,
983
+ warnings: [...merged.warnings ?? [], `Auditoria de depend\xEAncias falhou: ${errorMessage3(error)}.`]
984
+ });
577
985
  }
578
986
  };
579
987
  var runScanEngines = async (path, rules, options) => {
580
988
  try {
581
- const nativeResult = await runScan(path, rules, options.concurrency);
582
- if (!options.semgrep) {
583
- return nativeResult;
584
- }
585
- const semgrepResult = await runBundledSemgrep(path, void 0, options.config);
586
- return mergeScanResults(nativeResult, semgrepResult);
989
+ const merged = await runNativeAndSemgrep(path, rules, options);
990
+ return options.deps ? runOptionalDependencyAudit(path, merged) : finalizeScanResult(merged);
587
991
  } catch (error) {
588
- throw new Error(`Falha durante a an\xE1lise: ${errorMessage2(error)}`, { cause: error });
992
+ throw error;
589
993
  }
590
994
  };
591
995
  var createScanTasks = (path, rules, taskTitle, options, onResult) => new Listr([
@@ -618,7 +1022,9 @@ var scanAndReport = async (path, rules, taskTitle, options) => {
618
1022
 
619
1023
  // src/commands/command-injection/command-injection.command.ts
620
1024
  var registerCommandInjectionCommand = (program) => {
621
- program.command("command-injection").description("Detecta child_process.exec()/execSync() recebendo entrada externa").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1025
+ withScanOptions(
1026
+ program.command("command-injection").description("Detecta child_process.exec()/execSync() recebendo entrada externa").argument("[path]", "diret\xF3rio a ser analisado", ".")
1027
+ ).action(
622
1028
  (path, options) => scanAndReport(path, [commandInjectionRule], "Checking command injection...", options)
623
1029
  );
624
1030
  };
@@ -689,8 +1095,11 @@ var walkNestingBlock = (node, depth, results) => {
689
1095
  checkDepth(newDepth, node.loc?.start.line, results);
690
1096
  walkChildren(node, newDepth, results);
691
1097
  };
1098
+ var selectNonFunctionWalker = (node) => {
1099
+ return node.type === "IfStatement" ? walkIfChain : NESTING_TYPES.has(node.type) ? walkNestingBlock : walkChildren;
1100
+ };
692
1101
  var selectWalker = (node) => {
693
- return FUNCTION_TYPES.has(node.type) ? walkFunction : node.type === "IfStatement" ? walkIfChain : NESTING_TYPES.has(node.type) ? walkNestingBlock : walkChildren;
1102
+ return FUNCTION_TYPES.has(node.type) ? walkFunction : selectNonFunctionWalker(node);
694
1103
  };
695
1104
  var walk2 = (node, depth, results) => {
696
1105
  selectWalker(node)(node, depth, results);
@@ -717,61 +1126,13 @@ var deepNestingRule = {
717
1126
 
718
1127
  // src/commands/deep-nesting/deep-nesting.command.ts
719
1128
  var registerDeepNestingCommand = (program) => {
720
- program.command("deep-nesting").description("Detecta blocos aninhados al\xE9m do limite recomendado").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1129
+ withScanOptions(
1130
+ program.command("deep-nesting").description("Detecta blocos aninhados al\xE9m do limite recomendado").argument("[path]", "diret\xF3rio a ser analisado", ".")
1131
+ ).action(
721
1132
  (path, options) => scanAndReport(path, [deepNestingRule], "Checking nesting depth...", options)
722
1133
  );
723
1134
  };
724
1135
 
725
- // src/scanner/dependency-audit.ts
726
- import { execFile as execFile2 } from "child_process";
727
- import { promisify as promisify2 } from "util";
728
- var execFileAsync2 = promisify2(execFile2);
729
- var SEVERITY_MAP = {
730
- info: "low",
731
- low: "low",
732
- moderate: "medium",
733
- high: "high",
734
- critical: "critical"
735
- };
736
- var vulnerabilityTitle = (vulnerability) => {
737
- const firstVia = vulnerability.via[0];
738
- if (typeof firstVia === "object" && firstVia?.title) {
739
- return firstVia.title;
740
- }
741
- return 'ver "npm audit" para detalhes';
742
- };
743
- var mapAuditReportToFindings = (report) => {
744
- return Object.values(report.vulnerabilities).map((vulnerability) => ({
745
- ruleId: "dependency-audit",
746
- message: `Depend\xEAncia vulner\xE1vel: ${vulnerability.name} (${vulnerability.severity}) \u2014 ${vulnerabilityTitle(vulnerability)}`,
747
- file: "package.json",
748
- line: 1,
749
- severity: SEVERITY_MAP[vulnerability.severity]
750
- }));
751
- };
752
- var runDependencyAudit = async (targetDir) => {
753
- const startedAt = Date.now();
754
- let stdout;
755
- try {
756
- ({ stdout } = await execFileAsync2("npm", ["audit", "--json"], {
757
- cwd: targetDir,
758
- maxBuffer: 10 * 1024 * 1024
759
- }));
760
- } catch (error) {
761
- const stdoutFromError = error.stdout;
762
- if (!stdoutFromError) {
763
- throw new Error(`N\xE3o foi poss\xEDvel executar "npm audit" em "${targetDir}".`, { cause: error });
764
- }
765
- stdout = stdoutFromError;
766
- }
767
- const report = JSON.parse(stdout);
768
- return {
769
- scannedFiles: 1,
770
- findings: mapAuditReportToFindings(report),
771
- durationMs: Date.now() - startedAt
772
- };
773
- };
774
-
775
1136
  // src/commands/dependency-audit/dependency-audit.command.ts
776
1137
  var printAuditResult = (result, json) => {
777
1138
  if (json) {
@@ -791,7 +1152,7 @@ var auditAndReport = async (path, options) => {
791
1152
  };
792
1153
  var registerDependencyAuditCommand = (program) => {
793
1154
  program.command("dependency-audit").description(
794
- 'Audita as depend\xEAncias do projeto contra vulnerabilidades conhecidas (via "npm audit"; requer npm no PATH e acesso \xE0 rede)'
1155
+ 'Audita as depend\xEAncias do projeto contra vulnerabilidades conhecidas (via "npm audit" e OSV.dev; requer npm no PATH e acesso \xE0 rede)'
795
1156
  ).argument("[path]", "diret\xF3rio do projeto a ser auditado", ".").option("--json", "exibe o resultado em JSON").action((path, options) => auditAndReport(path, options));
796
1157
  };
797
1158
 
@@ -829,7 +1190,9 @@ var emptyCatchRule = {
829
1190
 
830
1191
  // src/commands/empty-catch/empty-catch.command.ts
831
1192
  var registerEmptyCatchCommand = (program) => {
832
- program.command("empty-catch").description("Detecta blocos catch vazios").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1193
+ withScanOptions(
1194
+ program.command("empty-catch").description("Detecta blocos catch vazios").argument("[path]", "diret\xF3rio a ser analisado", ".")
1195
+ ).action(
833
1196
  (path, options) => scanAndReport(path, [emptyCatchRule], "Checking empty catch blocks...", options)
834
1197
  );
835
1198
  };
@@ -847,12 +1210,24 @@ var hasLimitOption = (options) => {
847
1210
  return key?.type === "Identifier" && key.name === "limit";
848
1211
  }) ?? false;
849
1212
  };
1213
+ var getMemberCallParts = (node) => {
1214
+ if (node.type !== "CallExpression") {
1215
+ return { object: void 0, property: void 0, args: void 0 };
1216
+ }
1217
+ const callee = node.callee;
1218
+ const args = node.arguments;
1219
+ if (callee?.type !== "MemberExpression") {
1220
+ return { object: void 0, property: void 0, args };
1221
+ }
1222
+ return {
1223
+ object: callee.object,
1224
+ property: callee.property,
1225
+ args
1226
+ };
1227
+ };
850
1228
  var isBodyParserWithoutLimit = (node) => {
851
- const callee = node.type === "CallExpression" ? node.callee : void 0;
852
- const object = callee?.type === "MemberExpression" ? callee.object : void 0;
853
- const property = callee?.type === "MemberExpression" ? callee.property : void 0;
1229
+ const { object, property, args } = getMemberCallParts(node);
854
1230
  const isBodyParserCall = object?.type === "Identifier" && BODY_PARSER_OBJECTS.has(object.name) && property?.type === "Identifier" && BODY_PARSER_METHODS.has(property.name);
855
- const args = node.type === "CallExpression" ? node.arguments : void 0;
856
1231
  return isBodyParserCall && !hasLimitOption(args?.[0]);
857
1232
  };
858
1233
  var findMissingBodyLimitLines = (filePath, content) => {
@@ -881,11 +1256,85 @@ var expressMissingBodyLimitRule = {
881
1256
 
882
1257
  // src/commands/express-missing-body-limit/express-missing-body-limit.command.ts
883
1258
  var registerExpressMissingBodyLimitCommand = (program) => {
884
- program.command("express-missing-body-limit").description("Detecta middlewares de body parsing do Express sem limite de tamanho de requisi\xE7\xE3o").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1259
+ withScanOptions(
1260
+ program.command("express-missing-body-limit").description("Detecta middlewares de body parsing do Express sem limite de tamanho de requisi\xE7\xE3o").argument("[path]", "diret\xF3rio a ser analisado", ".")
1261
+ ).action(
885
1262
  (path, options) => scanAndReport(path, [expressMissingBodyLimitRule], "Checking Express body limits...", options)
886
1263
  );
887
1264
  };
888
1265
 
1266
+ // src/rules/hardcoded-authorization-value.rule.ts
1267
+ var HEADER_OR_COOKIE_NAME_PATTERN = /headers|cookies?/i;
1268
+ var accessObjectName = (object) => {
1269
+ const objectProperty = object?.type === "MemberExpression" ? object.property : object;
1270
+ return objectProperty?.type === "Identifier" ? objectProperty.name : void 0;
1271
+ };
1272
+ var isHeaderOrCookieAccessCall = (node) => {
1273
+ const callee = node?.callee;
1274
+ const property = callee?.property;
1275
+ const object = callee?.object;
1276
+ return node?.type === "CallExpression" && callee?.type === "MemberExpression" && property?.type === "Identifier" && property.name === "get" && HEADER_OR_COOKIE_NAME_PATTERN.test(accessObjectName(object) ?? "");
1277
+ };
1278
+ var collectStringConstants = (sourceFile) => {
1279
+ const constants = /* @__PURE__ */ new Map();
1280
+ visitSourceNodes(sourceFile, (node) => {
1281
+ if (node.type !== "VariableDeclarator") {
1282
+ return;
1283
+ }
1284
+ const id = node.id;
1285
+ const init = node.init;
1286
+ if (id?.type === "Identifier" && init?.type === "StringLiteral") {
1287
+ constants.set(id.name, init.value);
1288
+ }
1289
+ });
1290
+ return constants;
1291
+ };
1292
+ var isConstantStringSide = (node, constants) => {
1293
+ if (node?.type === "StringLiteral") {
1294
+ return true;
1295
+ }
1296
+ return node?.type === "Identifier" && constants.has(node.name);
1297
+ };
1298
+ var isHardcodedAuthorizationComparison = (node, constants) => {
1299
+ const left = node.left;
1300
+ const right = node.right;
1301
+ const isEquality = node.operator === "===" || node.operator === "==";
1302
+ return node.type === "BinaryExpression" && isEquality && (isHeaderOrCookieAccessCall(left) && isConstantStringSide(right, constants) || isHeaderOrCookieAccessCall(right) && isConstantStringSide(left, constants));
1303
+ };
1304
+ var findHardcodedAuthorizationLines = (filePath, content) => {
1305
+ const lines = /* @__PURE__ */ new Set();
1306
+ const sourceFile = parseSourceFile(filePath, content);
1307
+ const constants = collectStringConstants(sourceFile);
1308
+ visitSourceNodes(sourceFile, (node) => {
1309
+ if (isHardcodedAuthorizationComparison(node, constants) && node.loc) {
1310
+ lines.add(node.loc.start.line);
1311
+ }
1312
+ });
1313
+ return [...lines].sort((a, b) => a - b);
1314
+ };
1315
+ var hardcodedAuthorizationValueRule = {
1316
+ id: "hardcoded-authorization-value",
1317
+ description: "Detecta um header/cookie de requisi\xE7\xE3o comparado com um valor fixo no c\xF3digo para conceder acesso",
1318
+ check(filePath, content) {
1319
+ return findHardcodedAuthorizationLines(filePath, content).map((line) => ({
1320
+ ruleId: "hardcoded-authorization-value",
1321
+ message: "Autoriza\xE7\xE3o baseada em compara\xE7\xE3o de header/cookie com valor fixo no c\xF3digo-fonte",
1322
+ file: filePath,
1323
+ line,
1324
+ severity: "critical"
1325
+ }));
1326
+ }
1327
+ };
1328
+
1329
+ // src/commands/hardcoded-authorization-value/hardcoded-authorization-value.command.ts
1330
+ var registerHardcodedAuthorizationValueCommand = (program) => {
1331
+ withScanOptions(
1332
+ program.command("hardcoded-authorization-value").description("Detecta compara\xE7\xE3o de headers/cookies de autoriza\xE7\xE3o com um valor fixo no c\xF3digo").argument("[path]", "diret\xF3rio a ser analisado", ".")
1333
+ ).action(
1334
+ (path, options) => scanAndReport(path, [hardcodedAuthorizationValueRule], "Checking hardcoded authorization values...", options)
1335
+ );
1336
+ };
1337
+
889
1338
  // src/commands/help/help.command.ts
890
1339
  import Table2 from "cli-table3";
891
1340
  var buildHelpTable = (commands) => {
@@ -1023,9 +1472,10 @@ var createFunctionStatementCountRule = (config) => ({
1023
1472
  // src/rules/high-complexity.rule.ts
1024
1473
  var highComplexityRule = createFunctionStatementCountRule({
1025
1474
  id: "high-complexity",
1026
- description: "Detecta fun\xE7\xF5es com muitos condicionais/loops (complexidade alta)",
1475
+ description: "Detecta fun\xE7\xF5es com muitos condicionais/loops/tern\xE1rios (complexidade alta)",
1027
1476
  statementTypes: /* @__PURE__ */ new Set([
1028
1477
  "IfStatement",
1478
+ "ConditionalExpression",
1029
1479
  "ForStatement",
1030
1480
  "ForInStatement",
1031
1481
  "ForOfStatement",
@@ -1035,12 +1485,14 @@ var highComplexityRule = createFunctionStatementCountRule({
1035
1485
  "CatchClause"
1036
1486
  ]),
1037
1487
  maxCount: 5,
1038
- unitLabel: "condicionais/loops"
1488
+ unitLabel: "condicionais/loops/tern\xE1rios"
1039
1489
  });
1040
1490
 
1041
1491
  // src/commands/high-complexity/high-complexity.command.ts
1042
1492
  var registerHighComplexityCommand = (program) => {
1043
- program.command("high-complexity").description("Detecta fun\xE7\xF5es com muitos condicionais/loops (complexidade alta)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1493
+ withScanOptions(
1494
+ program.command("high-complexity").description("Detecta fun\xE7\xF5es com muitos condicionais/loops (complexidade alta)").argument("[path]", "diret\xF3rio a ser analisado", ".")
1495
+ ).action(
1044
1496
  (path, options) => scanAndReport(path, [highComplexityRule], "Checking function complexity...", options)
1045
1497
  );
1046
1498
  };
@@ -1104,13 +1556,16 @@ var isMathRandomCall = (node) => {
1104
1556
  return object?.type === "Identifier" && object.name === "Math" && property?.type === "Identifier" && property.name === "random";
1105
1557
  };
1106
1558
  var HASH_LIKE_NAME_PATTERN = /hash|md5|sha1/i;
1559
+ var memberPropertyName = (callee) => {
1560
+ const property = callee.property;
1561
+ return property?.type === "Identifier" ? property.name : void 0;
1562
+ };
1107
1563
  var calleeName = (callee) => {
1108
1564
  if (callee?.type === "Identifier") {
1109
1565
  return callee.name;
1110
1566
  }
1111
1567
  if (callee?.type === "MemberExpression") {
1112
- const property = callee.property;
1113
- return property?.type === "Identifier" ? property.name : void 0;
1568
+ return memberPropertyName(callee);
1114
1569
  }
1115
1570
  return void 0;
1116
1571
  };
@@ -1190,7 +1645,9 @@ var insecureRandomTokenRule = {
1190
1645
 
1191
1646
  // src/commands/insecure-random-token/insecure-random-token.command.ts
1192
1647
  var registerInsecureRandomTokenCommand = (program) => {
1193
- program.command("insecure-random-token").description("Detecta o uso de Math.random() para gerar tokens/segredos previs\xEDveis").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1648
+ withScanOptions(
1649
+ program.command("insecure-random-token").description("Detecta o uso de Math.random() para gerar tokens/segredos previs\xEDveis").argument("[path]", "diret\xF3rio a ser analisado", ".")
1650
+ ).action(
1194
1651
  (path, options) => scanAndReport(path, [insecureRandomTokenRule], "Checking insecure random tokens...", options)
1195
1652
  );
1196
1653
  };
@@ -1274,13 +1731,12 @@ var jwtDecodeWithoutVerifyRule = {
1274
1731
 
1275
1732
  // src/commands/jwt-decode-without-verify/jwt-decode-without-verify.command.ts
1276
1733
  var registerJwtDecodeWithoutVerifyCommand = (program) => {
1277
- program.command("jwt-decode-without-verify").description("Detecta decodifica\xE7\xE3o manual de um token (base64 + JSON.parse) sem verificar a assinatura").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1278
- (path, options) => scanAndReport(
1279
- path,
1280
- [jwtDecodeWithoutVerifyRule],
1281
- "Checking JWT decode without verification...",
1282
- options
1283
- )
1734
+ withScanOptions(
1735
+ program.command("jwt-decode-without-verify").description(
1736
+ "Detecta decodifica\xE7\xE3o manual de um token (base64 + JSON.parse) sem verificar a assinatura"
1737
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
1738
+ ).action(
1739
+ (path, options) => scanAndReport(path, [jwtDecodeWithoutVerifyRule], "Checking JWT decode without verification...", options)
1284
1740
  );
1285
1741
  };
1286
1742
 
@@ -1295,12 +1751,24 @@ var objectHasProperty = (object, propertyName) => {
1295
1751
  return key?.type === "Identifier" && key.name === propertyName;
1296
1752
  }) ?? false;
1297
1753
  };
1298
- var isJwtSignWithoutExpiration = (node) => {
1754
+ var getCallExpressionParts = (node) => {
1299
1755
  const callee = node.type === "CallExpression" ? node.callee : void 0;
1756
+ const args = node.type === "CallExpression" ? node.arguments : void 0;
1757
+ return { callee, args };
1758
+ };
1759
+ var getMemberExpressionParts = (callee) => {
1300
1760
  const object = callee?.type === "MemberExpression" ? callee.object : void 0;
1301
1761
  const property = callee?.type === "MemberExpression" ? callee.property : void 0;
1762
+ return { object, property };
1763
+ };
1764
+ var getJwtSignCallParts = (node) => {
1765
+ const { callee, args } = getCallExpressionParts(node);
1766
+ const { object, property } = getMemberExpressionParts(callee);
1767
+ return { object, property, args };
1768
+ };
1769
+ var isJwtSignWithoutExpiration = (node) => {
1770
+ const { object, property, args } = getJwtSignCallParts(node);
1302
1771
  const isJwtSignCall = object?.type === "Identifier" && object.name === "jwt" && property?.type === "Identifier" && property.name === "sign";
1303
- const args = node.type === "CallExpression" ? node.arguments : void 0;
1304
1772
  const payload = args?.[0];
1305
1773
  const options = args?.[2];
1306
1774
  const hasExpInPayload = objectHasProperty(payload, "exp");
@@ -1333,7 +1801,9 @@ var jwtNoExpirationRule = {
1333
1801
 
1334
1802
  // src/commands/jwt-no-expiration/jwt-no-expiration.command.ts
1335
1803
  var registerJwtNoExpirationCommand = (program) => {
1336
- program.command("jwt-no-expiration").description("Detecta jwt.sign() sem expira\xE7\xE3o configurada").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1804
+ withScanOptions(
1805
+ program.command("jwt-no-expiration").description("Detecta jwt.sign() sem expira\xE7\xE3o configurada").argument("[path]", "diret\xF3rio a ser analisado", ".")
1806
+ ).action(
1337
1807
  (path, options) => scanAndReport(path, [jwtNoExpirationRule], "Checking JWT expiration...", options)
1338
1808
  );
1339
1809
  };
@@ -1386,7 +1856,9 @@ var longFunctionRule = {
1386
1856
 
1387
1857
  // src/commands/long-functions/long-functions.command.ts
1388
1858
  var registerLongFunctionsCommand = (program) => {
1389
- program.command("long-functions").description("Detecta fun\xE7\xF5es com mais de 30 linhas").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1859
+ withScanOptions(
1860
+ program.command("long-functions").description("Detecta fun\xE7\xF5es com mais de 30 linhas").argument("[path]", "diret\xF3rio a ser analisado", ".")
1861
+ ).action(
1390
1862
  (path, options) => scanAndReport(path, [longFunctionRule], "Checking function length...", options)
1391
1863
  );
1392
1864
  };
@@ -1418,7 +1890,9 @@ var noAnyRule = {
1418
1890
 
1419
1891
  // src/commands/no-any/no-any.command.ts
1420
1892
  var registerNoAnyCommand = (program) => {
1421
- program.command("no-any").description('Detecta o uso do tipo "any" no TypeScript').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1893
+ withScanOptions(
1894
+ program.command("no-any").description('Detecta o uso do tipo "any" no TypeScript').argument("[path]", "diret\xF3rio a ser analisado", ".")
1895
+ ).action(
1422
1896
  (path, options) => scanAndReport(path, [noAnyRule], "Checking any usage...", options)
1423
1897
  );
1424
1898
  };
@@ -1466,7 +1940,9 @@ var noEvalRule = {
1466
1940
 
1467
1941
  // src/commands/no-eval/no-eval.command.ts
1468
1942
  var registerNoEvalCommand = (program) => {
1469
- program.command("no-eval").description("Detecta o uso de eval() ou new Function()").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
1943
+ withScanOptions(
1944
+ program.command("no-eval").description("Detecta o uso de eval() ou new Function()").argument("[path]", "diret\xF3rio a ser analisado", ".")
1945
+ ).action(
1470
1946
  (path, options) => scanAndReport(path, [noEvalRule], "Checking eval/new Function usage...", options)
1471
1947
  );
1472
1948
  };
@@ -1534,7 +2010,9 @@ var noHardcodedSecretRule = {
1534
2010
 
1535
2011
  // src/commands/no-hardcoded-secret/no-hardcoded-secret.command.ts
1536
2012
  var registerNoHardcodedSecretCommand = (program) => {
1537
- program.command("no-hardcoded-secret").description("Detecta segredos/credenciais hardcoded no c\xF3digo-fonte").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2013
+ withScanOptions(
2014
+ program.command("no-hardcoded-secret").description("Detecta segredos/credenciais hardcoded no c\xF3digo-fonte").argument("[path]", "diret\xF3rio a ser analisado", ".")
2015
+ ).action(
1538
2016
  (path, options) => scanAndReport(path, [noHardcodedSecretRule], "Checking hardcoded secrets...", options)
1539
2017
  );
1540
2018
  };
@@ -1563,12 +2041,26 @@ var isPermissiveCorsCall = (node) => {
1563
2041
  const args = node.arguments;
1564
2042
  return (args?.length ?? 0) === 0 || isWildcardOriginOption(args?.[0]);
1565
2043
  };
2044
+ var callExpressionParts = (node) => {
2045
+ if (node.type !== "CallExpression") {
2046
+ return void 0;
2047
+ }
2048
+ return {
2049
+ callee: node.callee,
2050
+ args: node.arguments
2051
+ };
2052
+ };
2053
+ var memberCalleeProperty = (callee) => {
2054
+ if (callee?.type !== "MemberExpression") {
2055
+ return void 0;
2056
+ }
2057
+ return callee.property;
2058
+ };
1566
2059
  var isWildcardOriginHeader = (node) => {
1567
- const callee = node.type === "CallExpression" ? node.callee : void 0;
1568
- const property = callee?.type === "MemberExpression" ? callee.property : void 0;
1569
- const args = node.type === "CallExpression" ? node.arguments : void 0;
1570
- const headerName = args?.[0];
1571
- const headerValue = args?.[1];
2060
+ const parts = callExpressionParts(node);
2061
+ const property = memberCalleeProperty(parts?.callee);
2062
+ const headerName = parts?.args?.[0];
2063
+ const headerValue = parts?.args?.[1];
1572
2064
  return property?.type === "Identifier" && HEADER_SETTER_METHODS.has(property.name) && headerName?.type === "StringLiteral" && headerName.value === "Access-Control-Allow-Origin" && headerValue?.type === "StringLiteral" && headerValue.value === "*";
1573
2065
  };
1574
2066
  var findPermissiveCorsLines = (filePath, content) => {
@@ -1597,7 +2089,9 @@ var permissiveCorsRule = {
1597
2089
 
1598
2090
  // src/commands/permissive-cors/permissive-cors.command.ts
1599
2091
  var registerPermissiveCorsCommand = (program) => {
1600
- program.command("permissive-cors").description("Detecta CORS configurado para permitir qualquer origem").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2092
+ withScanOptions(
2093
+ program.command("permissive-cors").description("Detecta CORS configurado para permitir qualquer origem").argument("[path]", "diret\xF3rio a ser analisado", ".")
2094
+ ).action(
1601
2095
  (path, options) => scanAndReport(path, [permissiveCorsRule], "Checking permissive CORS...", options)
1602
2096
  );
1603
2097
  };
@@ -1642,7 +2136,11 @@ var publicEnvVarSecretRule = {
1642
2136
 
1643
2137
  // src/commands/public-env-var-secret/public-env-var-secret.command.ts
1644
2138
  var registerPublicEnvVarSecretCommand = (program) => {
1645
- program.command("public-env-var-secret").description("Detecta uma vari\xE1vel de ambiente p\xFAblica (NEXT_PUBLIC_/VITE_/REACT_APP_) com nome de segredo").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2139
+ withScanOptions(
2140
+ program.command("public-env-var-secret").description(
2141
+ "Detecta uma vari\xE1vel de ambiente p\xFAblica (NEXT_PUBLIC_/VITE_/REACT_APP_) com nome de segredo"
2142
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
2143
+ ).action(
1646
2144
  (path, options) => scanAndReport(path, [publicEnvVarSecretRule], "Checking public env var secrets...", options)
1647
2145
  );
1648
2146
  };
@@ -1716,13 +2214,26 @@ var awaitNoTryCatchRule = {
1716
2214
 
1717
2215
  // src/rules/floating-promise.rule.ts
1718
2216
  var PROMISE_STATIC_METHODS = /* @__PURE__ */ new Set(["all", "race", "allSettled", "any"]);
1719
- var asyncFunctionName = (node) => {
2217
+ var functionDeclarationName = (node) => {
1720
2218
  const declarationId = node.type === "FunctionDeclaration" ? node.id : void 0;
1721
- const declarationName = declarationId?.type === "Identifier" ? declarationId.name : void 0;
1722
- const id = node.type === "VariableDeclarator" ? node.id : void 0;
1723
- const init = node.type === "VariableDeclarator" ? node.init : void 0;
1724
- const hasAsyncFunctionValue = (init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression") && init.async;
1725
- return declarationName ?? (id?.type === "Identifier" && hasAsyncFunctionValue ? id.name : void 0);
2219
+ return declarationId?.type === "Identifier" ? declarationId.name : void 0;
2220
+ };
2221
+ var isAsyncFunctionValue = (init) => {
2222
+ return (init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression") && init.async;
2223
+ };
2224
+ var asyncVariableDeclaratorId = (node) => {
2225
+ return node.type === "VariableDeclarator" ? node.id : void 0;
2226
+ };
2227
+ var asyncVariableDeclaratorInit = (node) => {
2228
+ return node.type === "VariableDeclarator" ? node.init : void 0;
2229
+ };
2230
+ var asyncVariableDeclaratorName = (node) => {
2231
+ const id = asyncVariableDeclaratorId(node);
2232
+ const init = asyncVariableDeclaratorInit(node);
2233
+ return id?.type === "Identifier" && isAsyncFunctionValue(init) ? id.name : void 0;
2234
+ };
2235
+ var asyncFunctionName = (node) => {
2236
+ return functionDeclarationName(node) ?? asyncVariableDeclaratorName(node);
1726
2237
  };
1727
2238
  var collectAsyncFunctionNames = (sourceFile) => {
1728
2239
  const names = /* @__PURE__ */ new Set();
@@ -1734,16 +2245,20 @@ var collectAsyncFunctionNames = (sourceFile) => {
1734
2245
  });
1735
2246
  return names;
1736
2247
  };
2248
+ var isKnownFunctionCall = (callee, asyncFunctionNames) => {
2249
+ return callee.type === "Identifier" && (callee.name === "fetch" || asyncFunctionNames.has(callee.name));
2250
+ };
2251
+ var isPromiseStaticMethodCall = (callee) => {
2252
+ const object = callee.type === "MemberExpression" ? callee.object : void 0;
2253
+ const property = callee.type === "MemberExpression" ? callee.property : void 0;
2254
+ return object?.type === "Identifier" && object.name === "Promise" && property?.type === "Identifier" && PROMISE_STATIC_METHODS.has(property.name);
2255
+ };
1737
2256
  var isKnownPromiseReturningCall = (call, asyncFunctionNames) => {
1738
2257
  const callee = call.callee;
1739
2258
  if (!callee) {
1740
2259
  return false;
1741
2260
  }
1742
- const isKnownFunction = callee.type === "Identifier" && (callee.name === "fetch" || asyncFunctionNames.has(callee.name));
1743
- const object = callee.type === "MemberExpression" ? callee.object : void 0;
1744
- const property = callee.type === "MemberExpression" ? callee.property : void 0;
1745
- const isPromiseStaticMethod = object?.type === "Identifier" && object.name === "Promise" && property?.type === "Identifier" && PROMISE_STATIC_METHODS.has(property.name);
1746
- return isKnownFunction || isPromiseStaticMethod;
2261
+ return isKnownFunctionCall(callee, asyncFunctionNames) || isPromiseStaticMethodCall(callee);
1747
2262
  };
1748
2263
  var findFloatingPromiseLines = (filePath, content) => {
1749
2264
  const lines = /* @__PURE__ */ new Set();
@@ -1773,7 +2288,7 @@ var floatingPromiseRule = {
1773
2288
  };
1774
2289
 
1775
2290
  // src/rules/promise-no-catch.rule.ts
1776
- var memberPropertyName = (member) => {
2291
+ var memberPropertyName2 = (member) => {
1777
2292
  const property = member.property;
1778
2293
  return property?.type === "Identifier" ? property.name : void 0;
1779
2294
  };
@@ -1786,7 +2301,11 @@ var isThenCall = (node) => {
1786
2301
  }
1787
2302
  const callee = node.callee;
1788
2303
  const args = node.arguments;
1789
- return callee?.type === "MemberExpression" && memberPropertyName(callee) === "then" && (args?.length ?? 0) < 2;
2304
+ return callee?.type === "MemberExpression" && memberPropertyName2(callee) === "then" && (args?.length ?? 0) < 2;
2305
+ };
2306
+ var chainMethodName = (currentCall, member, nextCall) => {
2307
+ const isChainMember = member !== void 0 && member.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
2308
+ return isChainMember ? memberPropertyName2(member) : void 0;
1790
2309
  };
1791
2310
  var chainReachesCatch = (thenCall, ancestors) => {
1792
2311
  let currentCall = thenCall;
@@ -1794,8 +2313,7 @@ var chainReachesCatch = (thenCall, ancestors) => {
1794
2313
  while (i < ancestors.length) {
1795
2314
  const member = ancestors.at(i);
1796
2315
  const nextCall = ancestors.at(i + 1);
1797
- const isChainMember = member !== void 0 && member.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
1798
- const methodName = isChainMember ? memberPropertyName(member) : void 0;
2316
+ const methodName = chainMethodName(currentCall, member, nextCall);
1799
2317
  const continuesChain = methodName === "then" || methodName === "finally";
1800
2318
  if (methodName === "catch") {
1801
2319
  return true;
@@ -2037,10 +2555,10 @@ var tooManyForLoopsRule = createFunctionStatementCountRule({
2037
2555
  // src/rules/too-many-ifs.rule.ts
2038
2556
  var tooManyIfsRule = createFunctionStatementCountRule({
2039
2557
  id: "too-many-ifs",
2040
- description: 'Detecta fun\xE7\xF5es com muitos "if" (incluindo "else if")',
2041
- statementTypes: /* @__PURE__ */ new Set(["IfStatement"]),
2558
+ description: 'Detecta fun\xE7\xF5es com muitos "if" ou tern\xE1rios (incluindo "else if")',
2559
+ statementTypes: /* @__PURE__ */ new Set(["IfStatement", "ConditionalExpression"]),
2042
2560
  maxCount: 2,
2043
- unitLabel: "declara\xE7\xF5es if (incluindo else if)"
2561
+ unitLabel: "declara\xE7\xF5es if e express\xF5es tern\xE1rias (incluindo else if)"
2044
2562
  });
2045
2563
 
2046
2564
  // src/rules/too-many-switch-cases.rule.ts
@@ -2143,14 +2661,61 @@ var unsafeSqlRule = {
2143
2661
  }
2144
2662
  };
2145
2663
 
2664
+ // src/rules/weak-cipher-mode.rule.ts
2665
+ var CIPHER_METHOD_NAMES = /* @__PURE__ */ new Set(["createCipheriv", "createDecipheriv"]);
2666
+ var WEAK_MODE_PATTERN = /-(cbc|ecb)$/i;
2667
+ var isWeakCipherModeCall = (node) => {
2668
+ const callee = node.callee;
2669
+ const property = callee?.property;
2670
+ const args = node.arguments;
2671
+ const algorithm = args?.[0];
2672
+ return node.type === "CallExpression" && callee?.type === "MemberExpression" && property?.type === "Identifier" && CIPHER_METHOD_NAMES.has(property.name) && algorithm?.type === "StringLiteral" && WEAK_MODE_PATTERN.test(algorithm.value);
2673
+ };
2674
+ var findWeakCipherModeLines = (filePath, content) => {
2675
+ const lines = /* @__PURE__ */ new Set();
2676
+ const sourceFile = parseSourceFile(filePath, content);
2677
+ visitSourceNodes(sourceFile, (node) => {
2678
+ if (isWeakCipherModeCall(node) && node.loc) {
2679
+ lines.add(node.loc.start.line);
2680
+ }
2681
+ });
2682
+ return [...lines].sort((a, b) => a - b);
2683
+ };
2684
+ var weakCipherModeRule = {
2685
+ id: "weak-cipher-mode",
2686
+ description: "Detecta cifragem em modo n\xE3o autenticado (CBC/ECB) sem AEAD/HMAC associado",
2687
+ check(filePath, content) {
2688
+ return findWeakCipherModeLines(filePath, content).map((line) => ({
2689
+ ruleId: "weak-cipher-mode",
2690
+ message: "Modo de cifra sem autentica\xE7\xE3o (CBC/ECB) \u2014 considere um modo AEAD como GCM",
2691
+ file: filePath,
2692
+ line,
2693
+ severity: "high"
2694
+ }));
2695
+ }
2696
+ };
2697
+
2146
2698
  // src/rules/weak-hash-algorithm.rule.ts
2147
2699
  var WEAK_ALGORITHMS = /^(md5|sha1)$/i;
2700
+ var getCallExpressionCallee = (node) => {
2701
+ return node.type === "CallExpression" ? node.callee : void 0;
2702
+ };
2703
+ var getCallExpressionArgs = (node) => {
2704
+ return node.type === "CallExpression" ? node.arguments : void 0;
2705
+ };
2706
+ var getCreateHashProperty = (callee) => {
2707
+ return callee?.type === "MemberExpression" ? callee.property : void 0;
2708
+ };
2709
+ var isCreateHashProperty = (property) => {
2710
+ return property?.type === "Identifier" && property.name === "createHash";
2711
+ };
2712
+ var isWeakAlgorithmLiteral = (algorithm) => {
2713
+ return algorithm?.type === "StringLiteral" && WEAK_ALGORITHMS.test(algorithm.value);
2714
+ };
2148
2715
  var isWeakCreateHashCall = (node) => {
2149
- const callee = node.type === "CallExpression" ? node.callee : void 0;
2150
- const property = callee?.type === "MemberExpression" ? callee.property : void 0;
2151
- const args = node.type === "CallExpression" ? node.arguments : void 0;
2152
- const algorithm = args?.[0];
2153
- return property?.type === "Identifier" && property.name === "createHash" && algorithm?.type === "StringLiteral" && WEAK_ALGORITHMS.test(algorithm.value);
2716
+ const property = getCreateHashProperty(getCallExpressionCallee(node));
2717
+ const algorithm = getCallExpressionArgs(node)?.[0];
2718
+ return isCreateHashProperty(property) && isWeakAlgorithmLiteral(algorithm);
2154
2719
  };
2155
2720
  var findWeakHashLines = (filePath, content) => {
2156
2721
  const lines = /* @__PURE__ */ new Set();
@@ -2352,7 +2917,9 @@ var allRules = [
2352
2917
  jwtDecodeWithoutVerifyRule,
2353
2918
  xxeUnsafeXmlParsingRule,
2354
2919
  sensitiveDataInLogsRule,
2355
- publicEnvVarSecretRule
2920
+ publicEnvVarSecretRule,
2921
+ weakCipherModeRule,
2922
+ hardcodedAuthorizationValueRule
2356
2923
  ];
2357
2924
 
2358
2925
  // src/commands/rules/rules.command.ts
@@ -2382,70 +2949,93 @@ var parseLocalSemgrepConfig = (value) => {
2382
2949
  return resolve2(value);
2383
2950
  };
2384
2951
  var registerScanCommand = (program) => {
2385
- program.command("scan").description("Analisa um diret\xF3rio em busca de vulnerabilidades e problemas de qualidade").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").option("--concurrency <n>", "limita arquivos processados em paralelo", parseConcurrency).option("--config <file>", "usa um ruleset Semgrep YAML local", parseLocalSemgrepConfig).action(
2952
+ withScanOptions(
2953
+ program.command("scan").description("Analisa um diret\xF3rio em busca de vulnerabilidades e problemas de qualidade").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--concurrency <n>", "limita arquivos processados em paralelo", parseConcurrency).option("--config <file>", "usa um ruleset Semgrep YAML local", parseLocalSemgrepConfig).option(
2954
+ "--no-deps",
2955
+ "n\xE3o inclui a auditoria de depend\xEAncias (npm audit + OSV.dev) neste scan \u2014 permite rodar offline"
2956
+ )
2957
+ ).action(
2386
2958
  (path, options) => scanAndReport(path, allRules, "Scanning files...", { ...options, semgrep: true })
2387
2959
  );
2388
2960
  };
2389
2961
 
2390
2962
  // src/commands/security-lint/security-lint.command.ts
2391
2963
  var registerSecurityLintCommand = (program) => {
2392
- program.command("security-lint").description("Detecta padr\xF5es de seguran\xE7a gen\xE9ricos via eslint-plugin-security").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2964
+ withScanOptions(
2965
+ program.command("security-lint").description("Detecta padr\xF5es de seguran\xE7a gen\xE9ricos via eslint-plugin-security").argument("[path]", "diret\xF3rio a ser analisado", ".")
2966
+ ).action(
2393
2967
  (path, options) => scanAndReport(path, [securityLintRule], "Checking generic security patterns...", options)
2394
2968
  );
2395
2969
  };
2396
2970
 
2397
2971
  // src/commands/sensitive-data-in-logs/sensitive-data-in-logs.command.ts
2398
2972
  var registerSensitiveDataInLogsCommand = (program) => {
2399
- program.command("sensitive-data-in-logs").description("Detecta senhas/segredos/tokens sendo passados para chamadas de log").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2973
+ withScanOptions(
2974
+ program.command("sensitive-data-in-logs").description("Detecta senhas/segredos/tokens sendo passados para chamadas de log").argument("[path]", "diret\xF3rio a ser analisado", ".")
2975
+ ).action(
2400
2976
  (path, options) => scanAndReport(path, [sensitiveDataInLogsRule], "Checking sensitive data in logs...", options)
2401
2977
  );
2402
2978
  };
2403
2979
 
2404
2980
  // src/commands/tls-validation-disabled/tls-validation-disabled.command.ts
2405
2981
  var registerTlsValidationDisabledCommand = (program) => {
2406
- program.command("tls-validation-disabled").description("Detecta a desativa\xE7\xE3o da valida\xE7\xE3o de certificados TLS").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2982
+ withScanOptions(
2983
+ program.command("tls-validation-disabled").description("Detecta a desativa\xE7\xE3o da valida\xE7\xE3o de certificados TLS").argument("[path]", "diret\xF3rio a ser analisado", ".")
2984
+ ).action(
2407
2985
  (path, options) => scanAndReport(path, [tlsValidationDisabledRule], "Checking TLS validation...", options)
2408
2986
  );
2409
2987
  };
2410
2988
 
2411
2989
  // src/commands/too-many-for-loops/too-many-for-loops.command.ts
2412
2990
  var registerTooManyForLoopsCommand = (program) => {
2413
- program.command("too-many-for-loops").description('Detecta fun\xE7\xF5es com muitos loops "for"/"for-in"/"for-of"').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
2991
+ withScanOptions(
2992
+ program.command("too-many-for-loops").description('Detecta fun\xE7\xF5es com muitos loops "for"/"for-in"/"for-of"').argument("[path]", "diret\xF3rio a ser analisado", ".")
2993
+ ).action(
2414
2994
  (path, options) => scanAndReport(path, [tooManyForLoopsRule], "Checking for-loop count...", options)
2415
2995
  );
2416
2996
  };
2417
2997
 
2418
2998
  // src/commands/too-many-ifs/too-many-ifs.command.ts
2419
2999
  var registerTooManyIfsCommand = (program) => {
2420
- program.command("too-many-ifs").description('Detecta fun\xE7\xF5es com muitos "if" (incluindo "else if")').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3000
+ withScanOptions(
3001
+ program.command("too-many-ifs").description('Detecta fun\xE7\xF5es com muitos "if" (incluindo "else if")').argument("[path]", "diret\xF3rio a ser analisado", ".")
3002
+ ).action(
2421
3003
  (path, options) => scanAndReport(path, [tooManyIfsRule], "Checking if count...", options)
2422
3004
  );
2423
3005
  };
2424
3006
 
2425
3007
  // src/commands/too-many-switch-cases/too-many-switch-cases.command.ts
2426
3008
  var registerTooManySwitchCasesCommand = (program) => {
2427
- program.command("too-many-switch-cases").description('Detecta "switch" com muitos "case" (considere um mapa/lookup)').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3009
+ withScanOptions(
3010
+ program.command("too-many-switch-cases").description('Detecta "switch" com muitos "case" (considere um mapa/lookup)').argument("[path]", "diret\xF3rio a ser analisado", ".")
3011
+ ).action(
2428
3012
  (path, options) => scanAndReport(path, [tooManySwitchCasesRule], "Checking switch cases...", options)
2429
3013
  );
2430
3014
  };
2431
3015
 
2432
3016
  // src/commands/too-many-try-catch/too-many-try-catch.command.ts
2433
3017
  var registerTooManyTryCatchCommand = (program) => {
2434
- program.command("too-many-try-catch").description('Detecta fun\xE7\xF5es com muitos blocos "try/catch"').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3018
+ withScanOptions(
3019
+ program.command("too-many-try-catch").description('Detecta fun\xE7\xF5es com muitos blocos "try/catch"').argument("[path]", "diret\xF3rio a ser analisado", ".")
3020
+ ).action(
2435
3021
  (path, options) => scanAndReport(path, [tooManyTryCatchRule], "Checking try/catch count...", options)
2436
3022
  );
2437
3023
  };
2438
3024
 
2439
3025
  // src/commands/too-many-while-loops/too-many-while-loops.command.ts
2440
3026
  var registerTooManyWhileLoopsCommand = (program) => {
2441
- program.command("too-many-while-loops").description('Detecta fun\xE7\xF5es com muitos loops "while"/"do-while"').argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3027
+ withScanOptions(
3028
+ program.command("too-many-while-loops").description('Detecta fun\xE7\xF5es com muitos loops "while"/"do-while"').argument("[path]", "diret\xF3rio a ser analisado", ".")
3029
+ ).action(
2442
3030
  (path, options) => scanAndReport(path, [tooManyWhileLoopsRule], "Checking while-loop count...", options)
2443
3031
  );
2444
3032
  };
2445
3033
 
2446
3034
  // src/commands/unhandled-promises/unhandled-promises.command.ts
2447
3035
  var registerUnhandledPromisesCommand = (program) => {
2448
- program.command("unhandled-promises").description("Detecta promises sem tratamento (sem .catch, await sem try/catch ou promises soltas)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3036
+ withScanOptions(
3037
+ program.command("unhandled-promises").description("Detecta promises sem tratamento (sem .catch, await sem try/catch ou promises soltas)").argument("[path]", "diret\xF3rio a ser analisado", ".")
3038
+ ).action(
2449
3039
  (path, options) => scanAndReport(
2450
3040
  path,
2451
3041
  [promiseNoCatchRule, awaitNoTryCatchRule, floatingPromiseRule],
@@ -2457,37 +3047,168 @@ var registerUnhandledPromisesCommand = (program) => {
2457
3047
 
2458
3048
  // src/commands/unsafe-sql/unsafe-sql.command.ts
2459
3049
  var registerUnsafeSqlCommand = (program) => {
2460
- program.command("unsafe-sql").description("Detecta concatena\xE7\xE3o insegura de SQL (risco de SQL injection)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3050
+ withScanOptions(
3051
+ program.command("unsafe-sql").description("Detecta concatena\xE7\xE3o insegura de SQL (risco de SQL injection)").argument("[path]", "diret\xF3rio a ser analisado", ".")
3052
+ ).action(
2461
3053
  (path, options) => scanAndReport(path, [unsafeSqlRule], "Checking unsafe SQL...", options)
2462
3054
  );
2463
3055
  };
2464
3056
 
3057
+ // src/engines/engine-metadata.ts
3058
+ import { readFileSync as readFileSync2 } from "fs";
3059
+ import { createRequire as createRequire4 } from "module";
3060
+ var require5 = createRequire4(import.meta.url);
3061
+ var packageForPlatform2 = (platform, architecture) => {
3062
+ if (platform === "linux" && architecture === "x64") {
3063
+ return "codesentry-semgrep-linux-x64";
3064
+ }
3065
+ if (platform === "win32" && architecture === "x64") {
3066
+ return "codesentry-semgrep-win32-x64";
3067
+ }
3068
+ return void 0;
3069
+ };
3070
+ var unsupportedRuntimeMessage2 = (platform, architecture) => `O runtime Semgrep embutido n\xE3o est\xE1 dispon\xEDvel para ${platform}-${architecture}. O CodeSentry oferece suporte a Linux x64 e Windows x64.`;
3071
+ var readJsonFile = (path) => {
3072
+ return JSON.parse(readFileSync2(path, "utf-8"));
3073
+ };
3074
+ var nonEmptyString = (value) => typeof value === "string" && value.length > 0;
3075
+ var errorReason2 = (error) => error instanceof Error ? error.message : "erro desconhecido";
3076
+ var validRuntimeLock = (lock) => lock.schemaVersion === 1 && nonEmptyString(lock.semgrepVersion) && nonEmptyString(lock.pythonVersion) && /^[a-f0-9]{64}$/i.test(lock.runtimeSha256);
3077
+ var validRulesetLock = (lock) => lock.schemaVersion === 1 && nonEmptyString(lock.packageVersion) && nonEmptyString(lock.ruleset) && nonEmptyString(lock.source) && /^[a-f0-9]{64}$/i.test(lock.sha256) && nonEmptyString(lock.capturedAt) && !Number.isNaN(Date.parse(lock.capturedAt)) && (lock.upstreamRevision === void 0 || nonEmptyString(lock.upstreamRevision));
3078
+ var loadBundledSemgrepRuntimeMetadata = (platform = process.platform, architecture = process.arch, resolveLockPath = (packageName) => require5.resolve(`${packageName}/runtime.lock.json`)) => {
3079
+ const packageName = packageForPlatform2(platform, architecture);
3080
+ if (!packageName) {
3081
+ throw new Error(unsupportedRuntimeMessage2(platform, architecture));
3082
+ }
3083
+ try {
3084
+ const lock = readJsonFile(resolveLockPath(packageName));
3085
+ if (!validRuntimeLock(lock)) {
3086
+ throw new Error("manifesto de vers\xE3o inv\xE1lido");
3087
+ }
3088
+ return {
3089
+ packageName,
3090
+ semgrepVersion: lock.semgrepVersion,
3091
+ pythonVersion: lock.pythonVersion,
3092
+ runtimeSha256: lock.runtimeSha256
3093
+ };
3094
+ } catch (error) {
3095
+ throw new Error(`N\xE3o foi poss\xEDvel carregar os metadados do runtime Semgrep embutido: ${errorReason2(error)}`, {
3096
+ cause: error
3097
+ });
3098
+ }
3099
+ };
3100
+ var loadBundledOwaspRulesetMetadata = (resolveLockPath = () => require5.resolve("codesentry-semgrep-rules/rules/ruleset.lock.json")) => {
3101
+ try {
3102
+ const lock = readJsonFile(resolveLockPath());
3103
+ if (!validRulesetLock(lock)) {
3104
+ throw new Error("manifesto de vers\xE3o inv\xE1lido");
3105
+ }
3106
+ return {
3107
+ packageVersion: lock.packageVersion,
3108
+ ruleset: lock.ruleset,
3109
+ source: lock.source,
3110
+ sha256: lock.sha256,
3111
+ capturedAt: lock.capturedAt,
3112
+ ...lock.upstreamRevision ? { upstreamRevision: lock.upstreamRevision } : {}
3113
+ };
3114
+ } catch (error) {
3115
+ throw new Error(`N\xE3o foi poss\xEDvel carregar os metadados do ruleset OWASP embutido: ${errorReason2(error)}`, {
3116
+ cause: error
3117
+ });
3118
+ }
3119
+ };
3120
+ var loadEngineMetadata = () => ({
3121
+ semgrep: loadBundledSemgrepRuntimeMetadata(),
3122
+ owaspRuleset: loadBundledOwaspRulesetMetadata()
3123
+ });
3124
+
3125
+ // src/commands/version/version.command.ts
3126
+ var buildVersionReport = (codesentryVersion, engines) => ({
3127
+ codesentryVersion,
3128
+ engines
3129
+ });
3130
+ var formatEngineVersions = (report) => {
3131
+ const { semgrep, owaspRuleset } = report.engines;
3132
+ return [
3133
+ `CodeSentry: ${report.codesentryVersion}`,
3134
+ "",
3135
+ "Runtime Semgrep CE",
3136
+ ` Vers\xE3o: ${semgrep.semgrepVersion}`,
3137
+ ` Python: ${semgrep.pythonVersion}`,
3138
+ ` Pacote: ${semgrep.packageName}`,
3139
+ ` SHA-256: ${semgrep.runtimeSha256}`,
3140
+ "",
3141
+ "Ruleset OWASP",
3142
+ ` Snapshot: ${owaspRuleset.ruleset}`,
3143
+ ` Vers\xE3o do pacote: ${owaspRuleset.packageVersion}`,
3144
+ ` Origem: ${owaspRuleset.source}`,
3145
+ ` Capturado em: ${owaspRuleset.capturedAt}`,
3146
+ ...owaspRuleset.upstreamRevision ? [` Revis\xE3o upstream: ${owaspRuleset.upstreamRevision}`] : [],
3147
+ ` SHA-256: ${owaspRuleset.sha256}`
3148
+ ].join("\n");
3149
+ };
3150
+ var errorReason3 = (error) => error instanceof Error ? error.message : "erro desconhecido";
3151
+ var formatVersionOutput = (options, report) => options.json ? JSON.stringify(report, null, 2) : formatEngineVersions(report);
3152
+ var registerVersionCommand = (program, readPackageVersion2, readEngineMetadata = loadEngineMetadata) => {
3153
+ program.command("version").description("Exibe a vers\xE3o do CodeSentry e dos motores embutidos").option("--engines", "inclui vers\xF5es e proveni\xEAncia do Semgrep e ruleset OWASP").option("--json", "emite o relat\xF3rio de vers\xF5es em JSON").action((options) => {
3154
+ const codesentryVersion = readPackageVersion2();
3155
+ if (!options.engines && !options.json) {
3156
+ console.log(`CodeSentry: ${codesentryVersion}`);
3157
+ return;
3158
+ }
3159
+ try {
3160
+ const report = buildVersionReport(codesentryVersion, readEngineMetadata());
3161
+ console.log(formatVersionOutput(options, report));
3162
+ } catch (error) {
3163
+ process.exitCode = 1;
3164
+ console.error(`N\xE3o foi poss\xEDvel auditar as vers\xF5es dos motores: ${errorReason3(error)}`);
3165
+ }
3166
+ });
3167
+ };
3168
+
3169
+ // src/commands/weak-cipher-mode/weak-cipher-mode.command.ts
3170
+ var registerWeakCipherModeCommand = (program) => {
3171
+ withScanOptions(
3172
+ program.command("weak-cipher-mode").description("Detecta o uso de modos de cifra fracos (CBC, ECB) em createCipheriv/createDecipheriv").argument("[path]", "diret\xF3rio a ser analisado", ".")
3173
+ ).action(
3174
+ (path, options) => scanAndReport(path, [weakCipherModeRule], "Checking weak cipher modes...", options)
3175
+ );
3176
+ };
3177
+
2465
3178
  // src/commands/weak-hash-algorithm/weak-hash-algorithm.command.ts
2466
3179
  var registerWeakHashAlgorithmCommand = (program) => {
2467
- program.command("weak-hash-algorithm").description("Detecta o uso de algoritmos de hash fracos (MD5, SHA-1)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3180
+ withScanOptions(
3181
+ program.command("weak-hash-algorithm").description("Detecta o uso de algoritmos de hash fracos (MD5, SHA-1)").argument("[path]", "diret\xF3rio a ser analisado", ".")
3182
+ ).action(
2468
3183
  (path, options) => scanAndReport(path, [weakHashAlgorithmRule], "Checking weak hash algorithms...", options)
2469
3184
  );
2470
3185
  };
2471
3186
 
2472
3187
  // src/commands/weak-secret-fallback/weak-secret-fallback.command.ts
2473
3188
  var registerWeakSecretFallbackCommand = (program) => {
2474
- program.command("weak-secret-fallback").description(
2475
- "Detecta uma vari\xE1vel de ambiente de segredo/chave com um valor hardcoded como fallback (|| ou ??)"
2476
- ).argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3189
+ withScanOptions(
3190
+ program.command("weak-secret-fallback").description(
3191
+ "Detecta uma vari\xE1vel de ambiente de segredo/chave com um valor hardcoded como fallback (|| ou ??)"
3192
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
3193
+ ).action(
2477
3194
  (path, options) => scanAndReport(path, [weakSecretFallbackRule], "Checking weak secret fallbacks...", options)
2478
3195
  );
2479
3196
  };
2480
3197
 
2481
3198
  // src/commands/xss/xss.command.ts
2482
3199
  var registerXssCommand = (program) => {
2483
- program.command("xss").description("Detecta sinks perigosos de XSS (innerHTML, document.write, etc.)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3200
+ withScanOptions(
3201
+ program.command("xss").description("Detecta sinks perigosos de XSS (innerHTML, document.write, etc.)").argument("[path]", "diret\xF3rio a ser analisado", ".")
3202
+ ).action(
2484
3203
  (path, options) => scanAndReport(path, [xssRule], "Checking XSS sinks...", options)
2485
3204
  );
2486
3205
  };
2487
3206
 
2488
3207
  // src/commands/xxe-unsafe-xml-parsing/xxe-unsafe-xml-parsing.command.ts
2489
3208
  var registerXxeUnsafeXmlParsingCommand = (program) => {
2490
- program.command("xxe-unsafe-xml-parsing").description("Detecta parsing de XML com noent/dtdload habilitados (risco de XXE)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
3209
+ withScanOptions(
3210
+ program.command("xxe-unsafe-xml-parsing").description("Detecta parsing de XML com noent/dtdload habilitados (risco de XXE)").argument("[path]", "diret\xF3rio a ser analisado", ".")
3211
+ ).action(
2491
3212
  (path, options) => scanAndReport(path, [xxeUnsafeXmlParsingRule], "Checking unsafe XML parsing...", options)
2492
3213
  );
2493
3214
  };
@@ -2537,17 +3258,20 @@ var registerSecurityCommands = (program) => {
2537
3258
  registerXxeUnsafeXmlParsingCommand(program);
2538
3259
  registerSensitiveDataInLogsCommand(program);
2539
3260
  registerPublicEnvVarSecretCommand(program);
3261
+ registerWeakCipherModeCommand(program);
3262
+ registerHardcodedAuthorizationValueCommand(program);
2540
3263
  };
2541
- var require5 = createRequire4(import.meta.url);
3264
+ var require6 = createRequire5(import.meta.url);
2542
3265
  var readPackageVersion = () => {
2543
- const packageJsonPath = require5.resolve("../package.json");
2544
- const { version } = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
3266
+ const packageJsonPath = require6.resolve("../package.json");
3267
+ const { version } = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
2545
3268
  return version;
2546
3269
  };
2547
3270
  var createCli = () => {
2548
3271
  printBanner();
2549
3272
  const program = new Command().name("codesentry").description("CLI de verifica\xE7\xE3o de vulnerabilidades e qualidade de c\xF3digo").version(readPackageVersion()).helpCommand(false);
2550
3273
  registerInitCommand(program);
3274
+ registerVersionCommand(program, readPackageVersion);
2551
3275
  registerAnalysisCommands(program);
2552
3276
  registerQualityCommands(program);
2553
3277
  registerSecurityCommands(program);