codesentry 0.1.9 → 0.1.11

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 +103 -18
  2. package/dist/index.js +465 -249
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -36,38 +36,33 @@ var visitSourceNodes = (node, visitor, parent) => {
36
36
  var EXEC_METHOD_NAMES = /* @__PURE__ */ new Set(["exec", "execSync"]);
37
37
  var CHILD_PROCESS_MODULE_NAMES = /* @__PURE__ */ new Set(["child_process", "node:child_process"]);
38
38
  var isChildProcessModuleSpecifier = (node) => node?.type === "StringLiteral" && CHILD_PROCESS_MODULE_NAMES.has(node.value);
39
+ var localName = (specifier) => {
40
+ const local = specifier.local;
41
+ return local?.type === "Identifier" ? local.name : void 0;
42
+ };
43
+ var isExecImport = (specifier) => {
44
+ const imported = specifier.imported;
45
+ return specifier.type === "ImportSpecifier" && imported?.type === "Identifier" && EXEC_METHOD_NAMES.has(imported.name);
46
+ };
47
+ var isNamespaceImport = (specifier) => specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier";
48
+ var collectImportBinding = (specifier, bindings) => {
49
+ const name = localName(specifier);
50
+ if (!name) {
51
+ return;
52
+ }
53
+ const bindingSet = isExecImport(specifier) ? bindings.directCalls : isNamespaceImport(specifier) ? bindings.namespaces : void 0;
54
+ bindingSet?.add(name);
55
+ };
39
56
  var collectFromImportDeclaration = (node, bindings) => {
40
57
  if (!isChildProcessModuleSpecifier(node.source)) {
41
58
  return;
42
59
  }
43
60
  for (const specifier of node.specifiers ?? []) {
44
- const local = specifier.local;
45
- if (local?.type !== "Identifier") {
46
- continue;
47
- }
48
- if (specifier.type === "ImportSpecifier") {
49
- const imported = specifier.imported;
50
- if (imported?.type === "Identifier" && EXEC_METHOD_NAMES.has(imported.name)) {
51
- bindings.directCalls.add(local.name);
52
- }
53
- } else if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
54
- bindings.namespaces.add(local.name);
55
- }
61
+ collectImportBinding(specifier, bindings);
56
62
  }
57
63
  };
58
64
  var isRequireCall = (node) => node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "require" && isChildProcessModuleSpecifier(node.arguments?.[0]);
59
- var collectFromVariableDeclarator = (node, bindings) => {
60
- if (!isRequireCall(node.init)) {
61
- return;
62
- }
63
- const id = node.id;
64
- if (id?.type === "Identifier") {
65
- bindings.namespaces.add(id.name);
66
- return;
67
- }
68
- if (id?.type !== "ObjectPattern") {
69
- return;
70
- }
65
+ var collectDirectCallBindings = (id, bindings) => {
71
66
  for (const property of id.properties ?? []) {
72
67
  const key = property.key;
73
68
  const value = property.value;
@@ -76,6 +71,16 @@ var collectFromVariableDeclarator = (node, bindings) => {
76
71
  }
77
72
  }
78
73
  };
74
+ var collectFromVariableDeclarator = (node, bindings) => {
75
+ if (!isRequireCall(node.init)) {
76
+ return;
77
+ }
78
+ const id = node.id;
79
+ const namespaceName = id?.type === "Identifier" ? id.name : void 0;
80
+ const destructuredBindings = id?.type === "ObjectPattern" ? id : void 0;
81
+ namespaceName && bindings.namespaces.add(namespaceName);
82
+ destructuredBindings && collectDirectCallBindings(destructuredBindings, bindings);
83
+ };
79
84
  var collectChildProcessBindings = (sourceFile) => {
80
85
  const bindings = { directCalls: /* @__PURE__ */ new Set(), namespaces: /* @__PURE__ */ new Set() };
81
86
  visitSourceNodes(sourceFile, (node) => {
@@ -136,6 +141,9 @@ var commandInjectionRule = {
136
141
  }
137
142
  };
138
143
 
144
+ // src/commands/scan/scan-options.ts
145
+ 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)");
146
+
139
147
  // src/commands/scan/scan-runner.ts
140
148
  import { writeFile } from "fs/promises";
141
149
  import { join as join2 } from "path";
@@ -164,23 +172,47 @@ var formatErrorChain = (error) => {
164
172
  // src/reporters/console.reporter.ts
165
173
  import chalk from "chalk";
166
174
  import Table from "cli-table3";
175
+
176
+ // src/scanner/scan-result.ts
177
+ var DEPENDENCY_AUDIT_NOTE = 'Dependency audit n\xE3o foi inclu\xEDdo neste scan \u2014 rode "codesentry dependency-audit" separadamente.';
178
+ 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.";
179
+ var mergeScanResults = (nativeResult, semgrepResult) => ({
180
+ scannedFiles: nativeResult.scannedFiles,
181
+ findings: [...nativeResult.findings, ...semgrepResult.findings],
182
+ durationMs: nativeResult.durationMs + semgrepResult.durationMs,
183
+ engines: {
184
+ codesentry: nativeResult.engines?.codesentry ?? nativeResult.scannedFiles,
185
+ semgrep: semgrepResult.engines?.semgrep ?? semgrepResult.scannedFiles
186
+ }
187
+ });
188
+ var finalizeScanResult = (result) => ({
189
+ ...result,
190
+ engines: { ...result.engines, dependencyAudit: false },
191
+ warnings: result.engines?.semgrep === 0 ? [...result.warnings ?? [], ZERO_SEMGREP_COVERAGE_WARNING] : result.warnings
192
+ });
193
+
194
+ // src/reporters/console.reporter.ts
167
195
  var SEVERITY_COLOR = {
168
196
  low: (text2) => chalk.gray(text2),
169
197
  medium: (text2) => chalk.yellow(text2),
170
198
  high: (text2) => chalk.red(text2),
171
199
  critical: (text2) => chalk.bgRed.white(text2)
172
200
  };
173
- var printConsoleReport = (result) => {
174
- const coverage = result.engines?.semgrep === void 0 ? "" : ` CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s).`;
175
- if (result.findings.length === 0) {
176
- console.log(
177
- chalk.green(`Nenhum problema encontrado (${result.scannedFiles} arquivos analisados).${coverage}`)
178
- );
179
- return;
201
+ var coverageText = (result) => result.engines?.semgrep === void 0 ? "" : ` CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s).`;
202
+ var printNotes = (result) => {
203
+ if (result.engines?.dependencyAudit === false) {
204
+ console.log(chalk.cyan(DEPENDENCY_AUDIT_NOTE));
180
205
  }
181
- const table = new Table({
182
- head: ["Severity", "Rule", "File", "Line", "Message"]
183
- });
206
+ for (const warning of result.warnings ?? []) {
207
+ console.log(chalk.yellow(`Aviso: ${warning}`));
208
+ }
209
+ };
210
+ var printCleanReport = (result, coverage) => {
211
+ console.log(chalk.green(`Nenhum problema encontrado (${result.scannedFiles} arquivos analisados).${coverage}`));
212
+ printNotes(result);
213
+ };
214
+ var findingsTable = (result) => {
215
+ const table = new Table({ head: ["Severity", "Rule", "File", "Line", "Message"] });
184
216
  for (const finding of result.findings) {
185
217
  const colorize = SEVERITY_COLOR[finding.severity];
186
218
  table.push([
@@ -191,13 +223,25 @@ var printConsoleReport = (result) => {
191
223
  finding.message
192
224
  ]);
193
225
  }
194
- console.log(table.toString());
226
+ return table;
227
+ };
228
+ var printFindingsReport = (result, coverage) => {
229
+ console.log(findingsTable(result).toString());
195
230
  console.log(
196
231
  chalk.bold(
197
232
  `
198
233
  ${result.findings.length} problema(s) encontrado(s) em ${result.scannedFiles} arquivo(s) (${result.durationMs}ms).${coverage}`
199
234
  )
200
235
  );
236
+ printNotes(result);
237
+ };
238
+ var printConsoleReport = (result) => {
239
+ const coverage = coverageText(result);
240
+ if (result.findings.length === 0) {
241
+ printCleanReport(result, coverage);
242
+ return;
243
+ }
244
+ printFindingsReport(result, coverage);
201
245
  };
202
246
 
203
247
  // src/reporters/json.reporter.ts
@@ -207,12 +251,13 @@ var toJsonReport = (result) => {
207
251
 
208
252
  // src/reporters/markdown.reporter.ts
209
253
  var SEVERITY_ORDER = ["critical", "high", "medium", "low"];
210
- var SEVERITY_LABEL = {
211
- critical: "Critical",
212
- high: "High",
213
- medium: "Medium",
214
- low: "Low"
215
- };
254
+ var SEVERITY_LABEL = /* @__PURE__ */ new Map([
255
+ ["critical", "Critical"],
256
+ ["high", "High"],
257
+ ["medium", "Medium"],
258
+ ["low", "Low"]
259
+ ]);
260
+ var severityLabel = (severity) => SEVERITY_LABEL.get(severity) ?? severity;
216
261
  var escapeCell = (text2) => text2.replaceAll("|", "\\|");
217
262
  var groupBy = (items, keyOf) => {
218
263
  const map = /* @__PURE__ */ new Map();
@@ -222,7 +267,7 @@ var groupBy = (items, keyOf) => {
222
267
  }
223
268
  return map;
224
269
  };
225
- var findingsTable = (findings) => {
270
+ var findingsTable2 = (findings) => {
226
271
  const sorted = [...findings].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
227
272
  const lines = ["| Arquivo | Linha | Mensagem |", "| --- | --- | --- |"];
228
273
  for (const f of sorted) {
@@ -231,11 +276,11 @@ var findingsTable = (findings) => {
231
276
  return lines;
232
277
  };
233
278
  var severitySection = (severity, findings) => {
234
- const lines = [`## ${SEVERITY_LABEL[severity]} (${findings.length})`, ""];
279
+ const lines = [`## ${severityLabel(severity)} (${findings.length})`, ""];
235
280
  const byRule = groupBy(findings, (f) => f.ruleId);
236
281
  for (const ruleId of [...byRule.keys()].sort()) {
237
282
  const ruleFindings = byRule.get(ruleId) ?? [];
238
- lines.push(`### ${ruleId} (${ruleFindings.length})`, "", ...findingsTable(ruleFindings), "");
283
+ lines.push(`### ${ruleId} (${ruleFindings.length})`, "", ...findingsTable2(ruleFindings), "");
239
284
  }
240
285
  return lines;
241
286
  };
@@ -249,12 +294,14 @@ var reportHeader = (result, generatedAt) => [
249
294
  ],
250
295
  `- **Dura\xE7\xE3o:** ${result.durationMs}ms`,
251
296
  `- **Total de problemas:** ${result.findings.length}`,
297
+ ...result.engines?.dependencyAudit === false ? [`- **Nota:** ${DEPENDENCY_AUDIT_NOTE}`] : [],
252
298
  ""
253
299
  ];
300
+ var warningsSection = (result) => (result.warnings ?? []).length === 0 ? [] : ["## Avisos", "", ...(result.warnings ?? []).map((warning) => `- ${warning}`), ""];
254
301
  var summaryTable = (bySeverity) => {
255
302
  const lines = ["## Resumo por severidade", "", "| Severidade | Quantidade |", "| --- | --- |"];
256
303
  for (const severity of SEVERITY_ORDER) {
257
- lines.push(`| ${SEVERITY_LABEL[severity]} | ${(bySeverity.get(severity) ?? []).length} |`);
304
+ lines.push(`| ${severityLabel(severity)} | ${(bySeverity.get(severity) ?? []).length} |`);
258
305
  }
259
306
  lines.push("");
260
307
  return lines;
@@ -271,22 +318,14 @@ var severitySections = (bySeverity) => {
271
318
  };
272
319
  var toMarkdownReport = (result, generatedAt = /* @__PURE__ */ new Date()) => {
273
320
  const bySeverity = groupBy(result.findings, (f) => f.severity);
274
- return [...reportHeader(result, generatedAt), ...summaryTable(bySeverity), ...severitySections(bySeverity)].join(
275
- "\n"
276
- );
321
+ return [
322
+ ...reportHeader(result, generatedAt),
323
+ ...warningsSection(result),
324
+ ...summaryTable(bySeverity),
325
+ ...severitySections(bySeverity)
326
+ ].join("\n");
277
327
  };
278
328
 
279
- // src/scanner/scan-result.ts
280
- var mergeScanResults = (nativeResult, semgrepResult) => ({
281
- scannedFiles: nativeResult.scannedFiles,
282
- findings: [...nativeResult.findings, ...semgrepResult.findings],
283
- durationMs: nativeResult.durationMs + semgrepResult.durationMs,
284
- engines: {
285
- codesentry: nativeResult.engines?.codesentry ?? nativeResult.scannedFiles,
286
- semgrep: semgrepResult.engines?.semgrep ?? semgrepResult.scannedFiles
287
- }
288
- });
289
-
290
329
  // src/scanner/scanner.ts
291
330
  import { readFile } from "fs/promises";
292
331
  import { availableParallelism } from "os";
@@ -294,25 +333,35 @@ import { availableParallelism } from "os";
294
333
  // src/scanner/file-finder.ts
295
334
  import { readdir } from "fs/promises";
296
335
  import { join } from "path";
336
+
337
+ // src/scanner/ignore-patterns.ts
338
+ var ALWAYS_IGNORED_DIR_NAMES = ["node_modules", ".git", "dist", ".next"];
339
+ var TEST_DIR_NAMES = ["tests", "test", "__tests__"];
340
+ var TEST_FILE_GLOBS = ["*.spec.*", "*.test.*"];
341
+ var TEST_FILE_NAME_PATTERN = /\.(spec|test)\.[^./]+$/;
342
+ var isIgnoredDirName = (name, includeTests = false) => ALWAYS_IGNORED_DIR_NAMES.includes(name) || !includeTests && TEST_DIR_NAMES.includes(name);
343
+ var isTestFileName = (fileName) => TEST_FILE_NAME_PATTERN.test(fileName);
344
+
345
+ // src/scanner/file-finder.ts
297
346
  var SCANNABLE_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx"];
298
- var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"]);
299
347
  var isScannable = (fileName) => SCANNABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
300
- var walk = async (currentDir) => {
301
- const entries = await readdir(currentDir, { withFileTypes: true });
302
- const files = [];
303
- for (const entry of entries) {
304
- if (entry.isDirectory()) {
305
- if (IGNORED_DIRS.has(entry.name)) continue;
306
- files.push(...await walk(join(currentDir, entry.name)));
307
- } else if (entry.isFile() && isScannable(entry.name)) {
308
- files.push(join(currentDir, entry.name));
309
- }
348
+ var filesFromEntry = async (currentDir, entry, includeTests) => {
349
+ if (entry.isDirectory()) {
350
+ return isIgnoredDirName(entry.name, includeTests) ? [] : walk(join(currentDir, entry.name), includeTests);
310
351
  }
311
- return files;
352
+ return entry.isFile() && isScannable(entry.name) && (includeTests || !isTestFileName(entry.name)) ? [join(currentDir, entry.name)] : [];
312
353
  };
313
- var findFiles = async (targetDir) => {
354
+ var walk = async (currentDir, includeTests) => {
314
355
  try {
315
- return await walk(targetDir);
356
+ const entries = await readdir(currentDir, { withFileTypes: true });
357
+ return (await Promise.all(entries.map((entry) => filesFromEntry(currentDir, entry, includeTests)))).flat();
358
+ } catch (error) {
359
+ throw new Error(`N\xE3o foi poss\xEDvel listar os arquivos em "${currentDir}".`, { cause: error });
360
+ }
361
+ };
362
+ var findFiles = async (targetDir, includeTests = false) => {
363
+ try {
364
+ return await walk(targetDir, includeTests);
316
365
  } catch (error) {
317
366
  throw new Error(`N\xE3o foi poss\xEDvel listar os arquivos em "${targetDir}".`, { cause: error });
318
367
  }
@@ -342,26 +391,35 @@ var readFileContent = async (filePath) => {
342
391
  throw new Error(`N\xE3o foi poss\xEDvel ler o arquivo "${filePath}".`, { cause: error });
343
392
  }
344
393
  };
394
+ var checkRule = (rule, filePath, content) => {
395
+ try {
396
+ return { findings: rule.check(filePath, content) };
397
+ } catch (error) {
398
+ return { findings: [], error };
399
+ }
400
+ };
345
401
  var scanFile = async (filePath, rules) => {
346
- const content = await readFileContent(filePath);
347
- const findings = [];
348
- let hasParseError = false;
349
- for (const rule of rules) {
350
- try {
351
- findings.push(...rule.check(filePath, content));
352
- } catch (error) {
353
- if (!hasParseError) {
354
- findings.push(parseErrorFinding(filePath, error));
402
+ try {
403
+ const content = await readFileContent(filePath);
404
+ const findings = [];
405
+ let hasParseError = false;
406
+ for (const rule of rules) {
407
+ const result = checkRule(rule, filePath, content);
408
+ findings.push(...result.findings);
409
+ if (result.error && !hasParseError) {
410
+ findings.push(parseErrorFinding(filePath, result.error));
355
411
  hasParseError = true;
356
412
  }
357
413
  }
414
+ return findings;
415
+ } catch (error) {
416
+ throw new Error(`N\xE3o foi poss\xEDvel analisar o arquivo "${filePath}".`, { cause: error });
358
417
  }
359
- return findings;
360
418
  };
361
- var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY) => {
419
+ var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY, includeTests = false) => {
362
420
  try {
363
421
  const startedAt = Date.now();
364
- const files = await findFiles(targetDir);
422
+ const files = await findFiles(targetDir, includeTests);
365
423
  const findings = (await runWithConcurrencyLimit(files, concurrency, (filePath) => scanFile(filePath, rules))).flat();
366
424
  return {
367
425
  scannedFiles: files.length,
@@ -460,51 +518,58 @@ var mapSemgrepReportToFindings = (report) => report.results.map((finding) => ({
460
518
  severity: SEMGREP_SEVERITIES[finding.extra.severity] ?? "medium"
461
519
  }));
462
520
  var outputFromError = (error) => typeof error.stdout === "string" ? error.stdout : void 0;
463
- var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync) => {
464
- const startedAt = Date.now();
465
- const args = [
466
- "scan",
467
- "--config",
468
- ruleset,
469
- "--metrics=off",
470
- "--json",
471
- "--quiet",
472
- "--exclude",
473
- "node_modules",
474
- "--exclude",
475
- ".git",
476
- "--exclude",
477
- "dist",
478
- "--exclude",
479
- ".next",
480
- // Não repetir targetDir aqui: o processo já roda com cwd = targetDir
481
- // (abaixo), então o alvo relativo a esse cwd é o diretório atual.
482
- "."
483
- ];
484
- let stdout;
521
+ var excludeArgs = (names) => names.flatMap((name) => ["--exclude", name]);
522
+ var semgrepArgs = (ruleset, includeTests) => [
523
+ "scan",
524
+ "--config",
525
+ ruleset,
526
+ "--metrics=off",
527
+ "--json",
528
+ "--quiet",
529
+ ...excludeArgs(ALWAYS_IGNORED_DIR_NAMES),
530
+ ...includeTests ? [] : excludeArgs([...TEST_DIR_NAMES, ...TEST_FILE_GLOBS]),
531
+ // Não repetir targetDir aqui: o processo já roda com cwd = targetDir,
532
+ // então o alvo relativo a esse cwd é o diretório atual.
533
+ "."
534
+ ];
535
+ var semgrepEnvironment = (runtime) => {
485
536
  const semgrepDir = dirname2(runtime.semgrep);
486
- const SYSTEM_PATH_FALLBACK = process.platform === "win32" ? "C:\\Windows\\System32;C:\\Windows" : "/usr/local/bin:/usr/bin:/bin";
487
- const env = {
537
+ const systemPathFallback = process.platform === "win32" ? "C:\\Windows\\System32;C:\\Windows" : "/usr/local/bin:/usr/bin:/bin";
538
+ return {
488
539
  ...process.env,
489
- PATH: `${semgrepDir}${delimiter}${dirname2(semgrepDir)}${delimiter}${process.env.PATH ?? ""}${delimiter}${SYSTEM_PATH_FALLBACK}`
540
+ PATH: `${semgrepDir}${delimiter}${dirname2(semgrepDir)}${delimiter}${process.env.PATH ?? ""}${delimiter}${systemPathFallback}`
490
541
  };
542
+ };
543
+ var executeSemgrep = async (targetDir, runtime, ruleset, execute, includeTests) => {
491
544
  try {
492
- ({ stdout } = await execute(runtime.semgrep, args, { cwd: targetDir, maxBuffer: 20 * 1024 * 1024, env }));
545
+ const { stdout } = await execute(runtime.semgrep, semgrepArgs(ruleset, includeTests), {
546
+ cwd: targetDir,
547
+ maxBuffer: 20 * 1024 * 1024,
548
+ env: semgrepEnvironment(runtime)
549
+ });
550
+ return stdout;
493
551
  } catch (error) {
494
552
  const output = outputFromError(error);
495
- if (!output) {
496
- throw new Error("N\xE3o foi poss\xEDvel executar o Semgrep embutido.", { cause: error });
553
+ if (output) {
554
+ return output;
497
555
  }
498
- stdout = output;
556
+ throw new Error("N\xE3o foi poss\xEDvel executar o Semgrep embutido.", { cause: error });
557
+ }
558
+ };
559
+ var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync, includeTests = false) => {
560
+ const startedAt = Date.now();
561
+ try {
562
+ const report = parseSemgrepReport(await executeSemgrep(targetDir, runtime, ruleset, execute, includeTests));
563
+ const scannedFiles = report.paths?.scanned.length ?? 0;
564
+ return {
565
+ scannedFiles,
566
+ findings: mapSemgrepReportToFindings(report),
567
+ durationMs: Date.now() - startedAt,
568
+ engines: { semgrep: scannedFiles }
569
+ };
570
+ } catch (error) {
571
+ throw new Error("N\xE3o foi poss\xEDvel processar o resultado do Semgrep.", { cause: error });
499
572
  }
500
- const report = parseSemgrepReport(stdout);
501
- const scannedFiles = report.paths?.scanned.length ?? 0;
502
- return {
503
- scannedFiles,
504
- findings: mapSemgrepReportToFindings(report),
505
- durationMs: Date.now() - startedAt,
506
- engines: { semgrep: scannedFiles }
507
- };
508
573
  };
509
574
 
510
575
  // src/commands/scan/report-filename.ts
@@ -540,22 +605,29 @@ Relat\xF3rio detalhado gerado em: ${filePath}`));
540
605
  console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${errorMessage2(error)}`));
541
606
  }
542
607
  };
608
+ var runScanEngines = async (path, rules, options) => {
609
+ try {
610
+ const includeTests = options.tests ?? false;
611
+ const nativeResult = await runScan(path, rules, options.concurrency, includeTests);
612
+ if (!options.semgrep) {
613
+ return finalizeScanResult(nativeResult);
614
+ }
615
+ const semgrepResult = await runBundledSemgrep(path, void 0, options.config, void 0, includeTests);
616
+ return finalizeScanResult(mergeScanResults(nativeResult, semgrepResult));
617
+ } catch (error) {
618
+ throw new Error(`Falha durante a an\xE1lise: ${errorMessage2(error)}`, { cause: error });
619
+ }
620
+ };
543
621
  var createScanTasks = (path, rules, taskTitle, options, onResult) => new Listr([
544
622
  {
545
623
  title: taskTitle,
546
- task: () => runScan(path, rules, options.concurrency).then((nativeResult) => {
547
- if (!options.semgrep) {
548
- onResult(nativeResult);
549
- return void 0;
624
+ task: async () => {
625
+ try {
626
+ onResult(await runScanEngines(path, rules, options));
627
+ } catch (error) {
628
+ throw error;
550
629
  }
551
- return runBundledSemgrep(path, void 0, options.config).then((semgrepResult) => {
552
- onResult(mergeScanResults(nativeResult, semgrepResult));
553
- });
554
- }).catch((error) => {
555
- throw new Error(`Falha durante a an\xE1lise: ${errorMessage2(error)}`, {
556
- cause: error
557
- });
558
- })
630
+ }
559
631
  }
560
632
  ]);
561
633
  var scanAndReport = async (path, rules, taskTitle, options) => {
@@ -576,7 +648,9 @@ var scanAndReport = async (path, rules, taskTitle, options) => {
576
648
 
577
649
  // src/commands/command-injection/command-injection.command.ts
578
650
  var registerCommandInjectionCommand = (program) => {
579
- 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(
651
+ withScanOptions(
652
+ program.command("command-injection").description("Detecta child_process.exec()/execSync() recebendo entrada externa").argument("[path]", "diret\xF3rio a ser analisado", ".")
653
+ ).action(
580
654
  (path, options) => scanAndReport(path, [commandInjectionRule], "Checking command injection...", options)
581
655
  );
582
656
  };
@@ -675,7 +749,9 @@ var deepNestingRule = {
675
749
 
676
750
  // src/commands/deep-nesting/deep-nesting.command.ts
677
751
  var registerDeepNestingCommand = (program) => {
678
- 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(
752
+ withScanOptions(
753
+ program.command("deep-nesting").description("Detecta blocos aninhados al\xE9m do limite recomendado").argument("[path]", "diret\xF3rio a ser analisado", ".")
754
+ ).action(
679
755
  (path, options) => scanAndReport(path, [deepNestingRule], "Checking nesting depth...", options)
680
756
  );
681
757
  };
@@ -787,7 +863,9 @@ var emptyCatchRule = {
787
863
 
788
864
  // src/commands/empty-catch/empty-catch.command.ts
789
865
  var registerEmptyCatchCommand = (program) => {
790
- program.command("empty-catch").description("Detecta blocos catch vazios").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
866
+ withScanOptions(
867
+ program.command("empty-catch").description("Detecta blocos catch vazios").argument("[path]", "diret\xF3rio a ser analisado", ".")
868
+ ).action(
791
869
  (path, options) => scanAndReport(path, [emptyCatchRule], "Checking empty catch blocks...", options)
792
870
  );
793
871
  };
@@ -839,11 +917,85 @@ var expressMissingBodyLimitRule = {
839
917
 
840
918
  // src/commands/express-missing-body-limit/express-missing-body-limit.command.ts
841
919
  var registerExpressMissingBodyLimitCommand = (program) => {
842
- 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(
920
+ withScanOptions(
921
+ 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", ".")
922
+ ).action(
843
923
  (path, options) => scanAndReport(path, [expressMissingBodyLimitRule], "Checking Express body limits...", options)
844
924
  );
845
925
  };
846
926
 
927
+ // src/rules/hardcoded-authorization-value.rule.ts
928
+ var HEADER_OR_COOKIE_NAME_PATTERN = /headers|cookies?/i;
929
+ var accessObjectName = (object) => {
930
+ const objectProperty = object?.type === "MemberExpression" ? object.property : object;
931
+ return objectProperty?.type === "Identifier" ? objectProperty.name : void 0;
932
+ };
933
+ var isHeaderOrCookieAccessCall = (node) => {
934
+ const callee = node?.callee;
935
+ const property = callee?.property;
936
+ const object = callee?.object;
937
+ return node?.type === "CallExpression" && callee?.type === "MemberExpression" && property?.type === "Identifier" && property.name === "get" && HEADER_OR_COOKIE_NAME_PATTERN.test(accessObjectName(object) ?? "");
938
+ };
939
+ var collectStringConstants = (sourceFile) => {
940
+ const constants = /* @__PURE__ */ new Map();
941
+ visitSourceNodes(sourceFile, (node) => {
942
+ if (node.type !== "VariableDeclarator") {
943
+ return;
944
+ }
945
+ const id = node.id;
946
+ const init = node.init;
947
+ if (id?.type === "Identifier" && init?.type === "StringLiteral") {
948
+ constants.set(id.name, init.value);
949
+ }
950
+ });
951
+ return constants;
952
+ };
953
+ var isConstantStringSide = (node, constants) => {
954
+ if (node?.type === "StringLiteral") {
955
+ return true;
956
+ }
957
+ return node?.type === "Identifier" && constants.has(node.name);
958
+ };
959
+ var isHardcodedAuthorizationComparison = (node, constants) => {
960
+ const left = node.left;
961
+ const right = node.right;
962
+ const isEquality = node.operator === "===" || node.operator === "==";
963
+ return node.type === "BinaryExpression" && isEquality && (isHeaderOrCookieAccessCall(left) && isConstantStringSide(right, constants) || isHeaderOrCookieAccessCall(right) && isConstantStringSide(left, constants));
964
+ };
965
+ var findHardcodedAuthorizationLines = (filePath, content) => {
966
+ const lines = /* @__PURE__ */ new Set();
967
+ const sourceFile = parseSourceFile(filePath, content);
968
+ const constants = collectStringConstants(sourceFile);
969
+ visitSourceNodes(sourceFile, (node) => {
970
+ if (isHardcodedAuthorizationComparison(node, constants) && node.loc) {
971
+ lines.add(node.loc.start.line);
972
+ }
973
+ });
974
+ return [...lines].sort((a, b) => a - b);
975
+ };
976
+ var hardcodedAuthorizationValueRule = {
977
+ id: "hardcoded-authorization-value",
978
+ description: "Detecta um header/cookie de requisi\xE7\xE3o comparado com um valor fixo no c\xF3digo para conceder acesso",
979
+ check(filePath, content) {
980
+ return findHardcodedAuthorizationLines(filePath, content).map((line) => ({
981
+ ruleId: "hardcoded-authorization-value",
982
+ message: "Autoriza\xE7\xE3o baseada em compara\xE7\xE3o de header/cookie com valor fixo no c\xF3digo-fonte",
983
+ file: filePath,
984
+ line,
985
+ severity: "critical"
986
+ }));
987
+ }
988
+ };
989
+
990
+ // src/commands/hardcoded-authorization-value/hardcoded-authorization-value.command.ts
991
+ var registerHardcodedAuthorizationValueCommand = (program) => {
992
+ withScanOptions(
993
+ 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", ".")
994
+ ).action(
995
+ (path, options) => scanAndReport(path, [hardcodedAuthorizationValueRule], "Checking hardcoded authorization values...", options)
996
+ );
997
+ };
998
+
847
999
  // src/commands/help/help.command.ts
848
1000
  import Table2 from "cli-table3";
849
1001
  var buildHelpTable = (commands) => {
@@ -884,14 +1036,14 @@ var nodeName = (node) => {
884
1036
  }
885
1037
  return nameValue(node) ?? literalValue(node) ?? nodeName(node.id);
886
1038
  };
887
- var PARENT_NAME_KEYS = {
888
- VariableDeclarator: "id",
889
- ObjectProperty: "key",
890
- AssignmentExpression: "left"
891
- };
1039
+ var PARENT_NAME_READERS = /* @__PURE__ */ new Map([
1040
+ ["VariableDeclarator", (parent) => parent.id],
1041
+ ["ObjectProperty", (parent) => parent.key],
1042
+ ["AssignmentExpression", (parent) => parent.left]
1043
+ ]);
892
1044
  var parentFunctionName = (parent) => {
893
- const nameKey = parent ? PARENT_NAME_KEYS[parent.type] : void 0;
894
- return nameKey ? nodeName(parent?.[nameKey]) : void 0;
1045
+ const readName = parent ? PARENT_NAME_READERS.get(parent.type) : void 0;
1046
+ return readName && parent ? nodeName(readName(parent)) : void 0;
895
1047
  };
896
1048
  var isConstructor = (node) => {
897
1049
  return node.type === "ClassMethod" && node.kind === "constructor";
@@ -998,7 +1150,9 @@ var highComplexityRule = createFunctionStatementCountRule({
998
1150
 
999
1151
  // src/commands/high-complexity/high-complexity.command.ts
1000
1152
  var registerHighComplexityCommand = (program) => {
1001
- 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(
1153
+ withScanOptions(
1154
+ program.command("high-complexity").description("Detecta fun\xE7\xF5es com muitos condicionais/loops (complexidade alta)").argument("[path]", "diret\xF3rio a ser analisado", ".")
1155
+ ).action(
1002
1156
  (path, options) => scanAndReport(path, [highComplexityRule], "Checking function complexity...", options)
1003
1157
  );
1004
1158
  };
@@ -1073,20 +1227,11 @@ var calleeName = (callee) => {
1073
1227
  return void 0;
1074
1228
  };
1075
1229
  var isDateNowOrGetTimeCall = (node) => {
1076
- if (node.type !== "CallExpression") {
1077
- return false;
1078
- }
1079
1230
  const callee = node.callee;
1080
- if (callee?.type !== "MemberExpression") {
1081
- return false;
1082
- }
1083
- const property = callee.property;
1231
+ const property = callee?.property;
1084
1232
  const propertyName = property?.type === "Identifier" ? property.name : void 0;
1085
- if (propertyName === "getTime") {
1086
- return true;
1087
- }
1088
- const object = callee.object;
1089
- return object?.type === "Identifier" && object.name === "Date" && propertyName === "now";
1233
+ const object = callee?.object;
1234
+ return node.type === "CallExpression" && callee?.type === "MemberExpression" && (propertyName === "getTime" || object?.type === "Identifier" && object.name === "Date" && propertyName === "now");
1090
1235
  };
1091
1236
  var TIMESTAMP_NAME_PATTERN = /timestamp|^now$/i;
1092
1237
  var containsPredictableTimestamp = (node) => {
@@ -1157,7 +1302,9 @@ var insecureRandomTokenRule = {
1157
1302
 
1158
1303
  // src/commands/insecure-random-token/insecure-random-token.command.ts
1159
1304
  var registerInsecureRandomTokenCommand = (program) => {
1160
- 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(
1305
+ withScanOptions(
1306
+ 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", ".")
1307
+ ).action(
1161
1308
  (path, options) => scanAndReport(path, [insecureRandomTokenRule], "Checking insecure random tokens...", options)
1162
1309
  );
1163
1310
  };
@@ -1208,13 +1355,12 @@ var analyzeFile = (sourceFile) => {
1208
1355
  hasSignatureVerification: false
1209
1356
  };
1210
1357
  visitSourceNodes(sourceFile, (node) => {
1211
- if (isBase64DecodeCall(node) && node.loc) {
1212
- analysis.base64DecodeLines.push(node.loc.start.line);
1213
- } else if (isJsonParseCall(node)) {
1214
- analysis.hasJsonParse = true;
1215
- } else if (isSignatureVerificationCall(node)) {
1216
- analysis.hasSignatureVerification = true;
1358
+ const base64DecodeLine = isBase64DecodeCall(node) ? node.loc?.start.line : void 0;
1359
+ if (base64DecodeLine !== void 0) {
1360
+ analysis.base64DecodeLines.push(base64DecodeLine);
1217
1361
  }
1362
+ analysis.hasJsonParse ||= isJsonParseCall(node);
1363
+ analysis.hasSignatureVerification ||= isSignatureVerificationCall(node);
1218
1364
  });
1219
1365
  return analysis;
1220
1366
  };
@@ -1242,14 +1388,13 @@ var jwtDecodeWithoutVerifyRule = {
1242
1388
 
1243
1389
  // src/commands/jwt-decode-without-verify/jwt-decode-without-verify.command.ts
1244
1390
  var registerJwtDecodeWithoutVerifyCommand = (program) => {
1245
- 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(async (path, options) => {
1246
- await scanAndReport(
1247
- path,
1248
- [jwtDecodeWithoutVerifyRule],
1249
- "Checking JWT decode without verification...",
1250
- options
1251
- );
1252
- });
1391
+ withScanOptions(
1392
+ program.command("jwt-decode-without-verify").description(
1393
+ "Detecta decodifica\xE7\xE3o manual de um token (base64 + JSON.parse) sem verificar a assinatura"
1394
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
1395
+ ).action(
1396
+ (path, options) => scanAndReport(path, [jwtDecodeWithoutVerifyRule], "Checking JWT decode without verification...", options)
1397
+ );
1253
1398
  };
1254
1399
 
1255
1400
  // src/rules/jwt-no-expiration.rule.ts
@@ -1301,7 +1446,9 @@ var jwtNoExpirationRule = {
1301
1446
 
1302
1447
  // src/commands/jwt-no-expiration/jwt-no-expiration.command.ts
1303
1448
  var registerJwtNoExpirationCommand = (program) => {
1304
- 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(
1449
+ withScanOptions(
1450
+ program.command("jwt-no-expiration").description("Detecta jwt.sign() sem expira\xE7\xE3o configurada").argument("[path]", "diret\xF3rio a ser analisado", ".")
1451
+ ).action(
1305
1452
  (path, options) => scanAndReport(path, [jwtNoExpirationRule], "Checking JWT expiration...", options)
1306
1453
  );
1307
1454
  };
@@ -1354,7 +1501,9 @@ var longFunctionRule = {
1354
1501
 
1355
1502
  // src/commands/long-functions/long-functions.command.ts
1356
1503
  var registerLongFunctionsCommand = (program) => {
1357
- 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(
1504
+ withScanOptions(
1505
+ program.command("long-functions").description("Detecta fun\xE7\xF5es com mais de 30 linhas").argument("[path]", "diret\xF3rio a ser analisado", ".")
1506
+ ).action(
1358
1507
  (path, options) => scanAndReport(path, [longFunctionRule], "Checking function length...", options)
1359
1508
  );
1360
1509
  };
@@ -1386,7 +1535,9 @@ var noAnyRule = {
1386
1535
 
1387
1536
  // src/commands/no-any/no-any.command.ts
1388
1537
  var registerNoAnyCommand = (program) => {
1389
- 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(
1538
+ withScanOptions(
1539
+ program.command("no-any").description('Detecta o uso do tipo "any" no TypeScript').argument("[path]", "diret\xF3rio a ser analisado", ".")
1540
+ ).action(
1390
1541
  (path, options) => scanAndReport(path, [noAnyRule], "Checking any usage...", options)
1391
1542
  );
1392
1543
  };
@@ -1434,7 +1585,9 @@ var noEvalRule = {
1434
1585
 
1435
1586
  // src/commands/no-eval/no-eval.command.ts
1436
1587
  var registerNoEvalCommand = (program) => {
1437
- 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(
1588
+ withScanOptions(
1589
+ program.command("no-eval").description("Detecta o uso de eval() ou new Function()").argument("[path]", "diret\xF3rio a ser analisado", ".")
1590
+ ).action(
1438
1591
  (path, options) => scanAndReport(path, [noEvalRule], "Checking eval/new Function usage...", options)
1439
1592
  );
1440
1593
  };
@@ -1502,7 +1655,9 @@ var noHardcodedSecretRule = {
1502
1655
 
1503
1656
  // src/commands/no-hardcoded-secret/no-hardcoded-secret.command.ts
1504
1657
  var registerNoHardcodedSecretCommand = (program) => {
1505
- 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(
1658
+ withScanOptions(
1659
+ program.command("no-hardcoded-secret").description("Detecta segredos/credenciais hardcoded no c\xF3digo-fonte").argument("[path]", "diret\xF3rio a ser analisado", ".")
1660
+ ).action(
1506
1661
  (path, options) => scanAndReport(path, [noHardcodedSecretRule], "Checking hardcoded secrets...", options)
1507
1662
  );
1508
1663
  };
@@ -1565,7 +1720,9 @@ var permissiveCorsRule = {
1565
1720
 
1566
1721
  // src/commands/permissive-cors/permissive-cors.command.ts
1567
1722
  var registerPermissiveCorsCommand = (program) => {
1568
- 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(
1723
+ withScanOptions(
1724
+ program.command("permissive-cors").description("Detecta CORS configurado para permitir qualquer origem").argument("[path]", "diret\xF3rio a ser analisado", ".")
1725
+ ).action(
1569
1726
  (path, options) => scanAndReport(path, [permissiveCorsRule], "Checking permissive CORS...", options)
1570
1727
  );
1571
1728
  };
@@ -1610,9 +1767,13 @@ var publicEnvVarSecretRule = {
1610
1767
 
1611
1768
  // src/commands/public-env-var-secret/public-env-var-secret.command.ts
1612
1769
  var registerPublicEnvVarSecretCommand = (program) => {
1613
- 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(async (path, options) => {
1614
- await scanAndReport(path, [publicEnvVarSecretRule], "Checking public env var secrets...", options);
1615
- });
1770
+ withScanOptions(
1771
+ program.command("public-env-var-secret").description(
1772
+ "Detecta uma vari\xE1vel de ambiente p\xFAblica (NEXT_PUBLIC_/VITE_/REACT_APP_) com nome de segredo"
1773
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
1774
+ ).action(
1775
+ (path, options) => scanAndReport(path, [publicEnvVarSecretRule], "Checking public env var secrets...", options)
1776
+ );
1616
1777
  };
1617
1778
 
1618
1779
  // src/commands/rules/rules.command.ts
@@ -1760,15 +1921,15 @@ var chainReachesCatch = (thenCall, ancestors) => {
1760
1921
  let currentCall = thenCall;
1761
1922
  let i = 0;
1762
1923
  while (i < ancestors.length) {
1763
- const member = ancestors[i];
1764
- const nextCall = ancestors[i + 1];
1765
- const isChainMember = member?.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
1924
+ const member = ancestors.at(i);
1925
+ const nextCall = ancestors.at(i + 1);
1926
+ const isChainMember = member !== void 0 && member.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
1766
1927
  const methodName = isChainMember ? memberPropertyName(member) : void 0;
1767
1928
  const continuesChain = methodName === "then" || methodName === "finally";
1768
1929
  if (methodName === "catch") {
1769
1930
  return true;
1770
1931
  }
1771
- if (!continuesChain) {
1932
+ if (!nextCall || !continuesChain) {
1772
1933
  return false;
1773
1934
  }
1774
1935
  currentCall = nextCall;
@@ -1864,11 +2025,14 @@ var RULES = {
1864
2025
  "security/detect-new-buffer": "error"
1865
2026
  };
1866
2027
  var PLUGINS = { security: securityPlugin };
2028
+ var suppressionMarker = (ruleId) => `codesentry-disable-next-line ${ruleId}`;
2029
+ var isSuppressed = (contentLines, finding) => contentLines.at(finding.line - 2)?.includes(suppressionMarker(finding.ruleId)) ?? false;
1867
2030
  var securityLintRule = {
1868
2031
  id: "security-lint",
1869
2032
  description: "Detecta padr\xF5es de seguran\xE7a gen\xE9ricos (object injection, regex n\xE3o literal, fs n\xE3o literal, etc.) via eslint-plugin-security",
1870
2033
  check(filePath, content) {
1871
- return runEslintRules(filePath, content, RULES, PLUGINS).map((finding) => ({
2034
+ const contentLines = content.split("\n");
2035
+ return runEslintRules(filePath, content, RULES, PLUGINS).filter((finding) => !isSuppressed(contentLines, finding)).map((finding) => ({
1872
2036
  ruleId: finding.ruleId,
1873
2037
  message: `Padr\xE3o inseguro detectado (${finding.ruleId}): ${finding.message}`,
1874
2038
  file: filePath,
@@ -1906,14 +2070,9 @@ var propertyKeyName = (property) => {
1906
2070
  var argumentContainsSensitiveData = (argument) => {
1907
2071
  let found = false;
1908
2072
  visitSourceNodes(argument, (node) => {
1909
- if (node.type === "Identifier" && SENSITIVE_NAME_PATTERN2.test(node.name)) {
1910
- found = true;
1911
- } else if (node.type === "ObjectProperty") {
1912
- const keyName = propertyKeyName(node);
1913
- if (keyName && SENSITIVE_NAME_PATTERN2.test(keyName)) {
1914
- found = true;
1915
- }
1916
- }
2073
+ const isSensitiveIdentifier = node.type === "Identifier" && SENSITIVE_NAME_PATTERN2.test(node.name);
2074
+ const keyName = node.type === "ObjectProperty" ? propertyKeyName(node) : void 0;
2075
+ found ||= isSensitiveIdentifier || keyName !== void 0 && SENSITIVE_NAME_PATTERN2.test(keyName);
1917
2076
  });
1918
2077
  return found;
1919
2078
  };
@@ -2113,6 +2272,40 @@ var unsafeSqlRule = {
2113
2272
  }
2114
2273
  };
2115
2274
 
2275
+ // src/rules/weak-cipher-mode.rule.ts
2276
+ var CIPHER_METHOD_NAMES = /* @__PURE__ */ new Set(["createCipheriv", "createDecipheriv"]);
2277
+ var WEAK_MODE_PATTERN = /-(cbc|ecb)$/i;
2278
+ var isWeakCipherModeCall = (node) => {
2279
+ const callee = node.callee;
2280
+ const property = callee?.property;
2281
+ const args = node.arguments;
2282
+ const algorithm = args?.[0];
2283
+ 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);
2284
+ };
2285
+ var findWeakCipherModeLines = (filePath, content) => {
2286
+ const lines = /* @__PURE__ */ new Set();
2287
+ const sourceFile = parseSourceFile(filePath, content);
2288
+ visitSourceNodes(sourceFile, (node) => {
2289
+ if (isWeakCipherModeCall(node) && node.loc) {
2290
+ lines.add(node.loc.start.line);
2291
+ }
2292
+ });
2293
+ return [...lines].sort((a, b) => a - b);
2294
+ };
2295
+ var weakCipherModeRule = {
2296
+ id: "weak-cipher-mode",
2297
+ description: "Detecta cifragem em modo n\xE3o autenticado (CBC/ECB) sem AEAD/HMAC associado",
2298
+ check(filePath, content) {
2299
+ return findWeakCipherModeLines(filePath, content).map((line) => ({
2300
+ ruleId: "weak-cipher-mode",
2301
+ message: "Modo de cifra sem autentica\xE7\xE3o (CBC/ECB) \u2014 considere um modo AEAD como GCM",
2302
+ file: filePath,
2303
+ line,
2304
+ severity: "high"
2305
+ }));
2306
+ }
2307
+ };
2308
+
2116
2309
  // src/rules/weak-hash-algorithm.rule.ts
2117
2310
  var WEAK_ALGORITHMS = /^(md5|sha1)$/i;
2118
2311
  var isWeakCreateHashCall = (node) => {
@@ -2198,29 +2391,18 @@ var RULES2 = {
2198
2391
  "no-unsanitized/method": "error"
2199
2392
  };
2200
2393
  var PLUGINS2 = { "no-unsanitized": noUnsanitizedPlugin };
2394
+ var isHtmlProperty = (property) => {
2395
+ const key = property.key;
2396
+ return key?.type === "Identifier" && key.name === "__html";
2397
+ };
2201
2398
  var isDangerouslySetInnerHtmlWithDynamicValue = (node) => {
2202
- if (node.type !== "JSXAttribute") {
2203
- return false;
2204
- }
2205
2399
  const name = node.name;
2206
- if (name?.type !== "JSXIdentifier" || name.name !== "dangerouslySetInnerHTML") {
2207
- return false;
2208
- }
2209
2400
  const value = node.value;
2210
- if (value?.type !== "JSXExpressionContainer") {
2211
- return false;
2212
- }
2213
- const expression = value.expression;
2214
- if (expression?.type !== "ObjectExpression") {
2215
- return false;
2216
- }
2217
- const properties = expression.properties;
2218
- const htmlProperty = properties?.find((property) => {
2219
- const key = property.key;
2220
- return key?.type === "Identifier" && key.name === "__html";
2221
- });
2401
+ const expression = value?.expression;
2402
+ const properties = expression?.properties;
2403
+ const htmlProperty = properties?.find(isHtmlProperty);
2222
2404
  const htmlValue = htmlProperty?.value;
2223
- return htmlValue !== void 0 && htmlValue.type !== "StringLiteral";
2405
+ return node.type === "JSXAttribute" && name?.type === "JSXIdentifier" && name.name === "dangerouslySetInnerHTML" && value?.type === "JSXExpressionContainer" && expression?.type === "ObjectExpression" && htmlValue !== void 0 && htmlValue.type !== "StringLiteral";
2224
2406
  };
2225
2407
  var findDangerouslySetInnerHtmlFindings = (filePath, content) => {
2226
2408
  const findings = [];
@@ -2273,19 +2455,10 @@ var hasRiskyOptionEnabled = (options) => {
2273
2455
  }) ?? false;
2274
2456
  };
2275
2457
  var isUnsafeXmlParseCall = (node) => {
2276
- if (node.type !== "CallExpression") {
2277
- return false;
2278
- }
2279
2458
  const callee = node.callee;
2280
- if (callee?.type !== "MemberExpression") {
2281
- return false;
2282
- }
2283
- const property = callee.property;
2284
- if (property?.type !== "Identifier" || !XML_PARSE_METHOD_NAMES.has(property.name)) {
2285
- return false;
2286
- }
2459
+ const property = callee?.property;
2287
2460
  const args = node.arguments;
2288
- return hasRiskyOptionEnabled(args?.[1]);
2461
+ return node.type === "CallExpression" && callee?.type === "MemberExpression" && property?.type === "Identifier" && XML_PARSE_METHOD_NAMES.has(property.name) && hasRiskyOptionEnabled(args?.[1]);
2289
2462
  };
2290
2463
  var findUnsafeXmlParsingLines = (filePath, content) => {
2291
2464
  const lines = /* @__PURE__ */ new Set();
@@ -2342,7 +2515,9 @@ var allRules = [
2342
2515
  jwtDecodeWithoutVerifyRule,
2343
2516
  xxeUnsafeXmlParsingRule,
2344
2517
  sensitiveDataInLogsRule,
2345
- publicEnvVarSecretRule
2518
+ publicEnvVarSecretRule,
2519
+ weakCipherModeRule,
2520
+ hardcodedAuthorizationValueRule
2346
2521
  ];
2347
2522
 
2348
2523
  // src/commands/rules/rules.command.ts
@@ -2372,70 +2547,90 @@ var parseLocalSemgrepConfig = (value) => {
2372
2547
  return resolve2(value);
2373
2548
  };
2374
2549
  var registerScanCommand = (program) => {
2375
- 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(
2550
+ withScanOptions(
2551
+ 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)
2552
+ ).action(
2376
2553
  (path, options) => scanAndReport(path, allRules, "Scanning files...", { ...options, semgrep: true })
2377
2554
  );
2378
2555
  };
2379
2556
 
2380
2557
  // src/commands/security-lint/security-lint.command.ts
2381
2558
  var registerSecurityLintCommand = (program) => {
2382
- 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(
2559
+ withScanOptions(
2560
+ program.command("security-lint").description("Detecta padr\xF5es de seguran\xE7a gen\xE9ricos via eslint-plugin-security").argument("[path]", "diret\xF3rio a ser analisado", ".")
2561
+ ).action(
2383
2562
  (path, options) => scanAndReport(path, [securityLintRule], "Checking generic security patterns...", options)
2384
2563
  );
2385
2564
  };
2386
2565
 
2387
2566
  // src/commands/sensitive-data-in-logs/sensitive-data-in-logs.command.ts
2388
2567
  var registerSensitiveDataInLogsCommand = (program) => {
2389
- 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(async (path, options) => {
2390
- await scanAndReport(path, [sensitiveDataInLogsRule], "Checking sensitive data in logs...", options);
2391
- });
2568
+ withScanOptions(
2569
+ program.command("sensitive-data-in-logs").description("Detecta senhas/segredos/tokens sendo passados para chamadas de log").argument("[path]", "diret\xF3rio a ser analisado", ".")
2570
+ ).action(
2571
+ (path, options) => scanAndReport(path, [sensitiveDataInLogsRule], "Checking sensitive data in logs...", options)
2572
+ );
2392
2573
  };
2393
2574
 
2394
2575
  // src/commands/tls-validation-disabled/tls-validation-disabled.command.ts
2395
2576
  var registerTlsValidationDisabledCommand = (program) => {
2396
- 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(
2577
+ withScanOptions(
2578
+ 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", ".")
2579
+ ).action(
2397
2580
  (path, options) => scanAndReport(path, [tlsValidationDisabledRule], "Checking TLS validation...", options)
2398
2581
  );
2399
2582
  };
2400
2583
 
2401
2584
  // src/commands/too-many-for-loops/too-many-for-loops.command.ts
2402
2585
  var registerTooManyForLoopsCommand = (program) => {
2403
- 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(
2586
+ withScanOptions(
2587
+ 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", ".")
2588
+ ).action(
2404
2589
  (path, options) => scanAndReport(path, [tooManyForLoopsRule], "Checking for-loop count...", options)
2405
2590
  );
2406
2591
  };
2407
2592
 
2408
2593
  // src/commands/too-many-ifs/too-many-ifs.command.ts
2409
2594
  var registerTooManyIfsCommand = (program) => {
2410
- 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(
2595
+ withScanOptions(
2596
+ program.command("too-many-ifs").description('Detecta fun\xE7\xF5es com muitos "if" (incluindo "else if")').argument("[path]", "diret\xF3rio a ser analisado", ".")
2597
+ ).action(
2411
2598
  (path, options) => scanAndReport(path, [tooManyIfsRule], "Checking if count...", options)
2412
2599
  );
2413
2600
  };
2414
2601
 
2415
2602
  // src/commands/too-many-switch-cases/too-many-switch-cases.command.ts
2416
2603
  var registerTooManySwitchCasesCommand = (program) => {
2417
- 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(
2604
+ withScanOptions(
2605
+ program.command("too-many-switch-cases").description('Detecta "switch" com muitos "case" (considere um mapa/lookup)').argument("[path]", "diret\xF3rio a ser analisado", ".")
2606
+ ).action(
2418
2607
  (path, options) => scanAndReport(path, [tooManySwitchCasesRule], "Checking switch cases...", options)
2419
2608
  );
2420
2609
  };
2421
2610
 
2422
2611
  // src/commands/too-many-try-catch/too-many-try-catch.command.ts
2423
2612
  var registerTooManyTryCatchCommand = (program) => {
2424
- 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(
2613
+ withScanOptions(
2614
+ program.command("too-many-try-catch").description('Detecta fun\xE7\xF5es com muitos blocos "try/catch"').argument("[path]", "diret\xF3rio a ser analisado", ".")
2615
+ ).action(
2425
2616
  (path, options) => scanAndReport(path, [tooManyTryCatchRule], "Checking try/catch count...", options)
2426
2617
  );
2427
2618
  };
2428
2619
 
2429
2620
  // src/commands/too-many-while-loops/too-many-while-loops.command.ts
2430
2621
  var registerTooManyWhileLoopsCommand = (program) => {
2431
- 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(
2622
+ withScanOptions(
2623
+ program.command("too-many-while-loops").description('Detecta fun\xE7\xF5es com muitos loops "while"/"do-while"').argument("[path]", "diret\xF3rio a ser analisado", ".")
2624
+ ).action(
2432
2625
  (path, options) => scanAndReport(path, [tooManyWhileLoopsRule], "Checking while-loop count...", options)
2433
2626
  );
2434
2627
  };
2435
2628
 
2436
2629
  // src/commands/unhandled-promises/unhandled-promises.command.ts
2437
2630
  var registerUnhandledPromisesCommand = (program) => {
2438
- 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(
2631
+ withScanOptions(
2632
+ 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", ".")
2633
+ ).action(
2439
2634
  (path, options) => scanAndReport(
2440
2635
  path,
2441
2636
  [promiseNoCatchRule, awaitNoTryCatchRule, floatingPromiseRule],
@@ -2447,39 +2642,58 @@ var registerUnhandledPromisesCommand = (program) => {
2447
2642
 
2448
2643
  // src/commands/unsafe-sql/unsafe-sql.command.ts
2449
2644
  var registerUnsafeSqlCommand = (program) => {
2450
- 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(
2645
+ withScanOptions(
2646
+ program.command("unsafe-sql").description("Detecta concatena\xE7\xE3o insegura de SQL (risco de SQL injection)").argument("[path]", "diret\xF3rio a ser analisado", ".")
2647
+ ).action(
2451
2648
  (path, options) => scanAndReport(path, [unsafeSqlRule], "Checking unsafe SQL...", options)
2452
2649
  );
2453
2650
  };
2454
2651
 
2652
+ // src/commands/weak-cipher-mode/weak-cipher-mode.command.ts
2653
+ var registerWeakCipherModeCommand = (program) => {
2654
+ withScanOptions(
2655
+ 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", ".")
2656
+ ).action(
2657
+ (path, options) => scanAndReport(path, [weakCipherModeRule], "Checking weak cipher modes...", options)
2658
+ );
2659
+ };
2660
+
2455
2661
  // src/commands/weak-hash-algorithm/weak-hash-algorithm.command.ts
2456
2662
  var registerWeakHashAlgorithmCommand = (program) => {
2457
- 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(
2663
+ withScanOptions(
2664
+ program.command("weak-hash-algorithm").description("Detecta o uso de algoritmos de hash fracos (MD5, SHA-1)").argument("[path]", "diret\xF3rio a ser analisado", ".")
2665
+ ).action(
2458
2666
  (path, options) => scanAndReport(path, [weakHashAlgorithmRule], "Checking weak hash algorithms...", options)
2459
2667
  );
2460
2668
  };
2461
2669
 
2462
2670
  // src/commands/weak-secret-fallback/weak-secret-fallback.command.ts
2463
2671
  var registerWeakSecretFallbackCommand = (program) => {
2464
- program.command("weak-secret-fallback").description(
2465
- "Detecta uma vari\xE1vel de ambiente de segredo/chave com um valor hardcoded como fallback (|| ou ??)"
2466
- ).argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(async (path, options) => {
2467
- await scanAndReport(path, [weakSecretFallbackRule], "Checking weak secret fallbacks...", options);
2468
- });
2672
+ withScanOptions(
2673
+ program.command("weak-secret-fallback").description(
2674
+ "Detecta uma vari\xE1vel de ambiente de segredo/chave com um valor hardcoded como fallback (|| ou ??)"
2675
+ ).argument("[path]", "diret\xF3rio a ser analisado", ".")
2676
+ ).action(
2677
+ (path, options) => scanAndReport(path, [weakSecretFallbackRule], "Checking weak secret fallbacks...", options)
2678
+ );
2469
2679
  };
2470
2680
 
2471
2681
  // src/commands/xss/xss.command.ts
2472
2682
  var registerXssCommand = (program) => {
2473
- 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(
2683
+ withScanOptions(
2684
+ program.command("xss").description("Detecta sinks perigosos de XSS (innerHTML, document.write, etc.)").argument("[path]", "diret\xF3rio a ser analisado", ".")
2685
+ ).action(
2474
2686
  (path, options) => scanAndReport(path, [xssRule], "Checking XSS sinks...", options)
2475
2687
  );
2476
2688
  };
2477
2689
 
2478
2690
  // src/commands/xxe-unsafe-xml-parsing/xxe-unsafe-xml-parsing.command.ts
2479
2691
  var registerXxeUnsafeXmlParsingCommand = (program) => {
2480
- 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(async (path, options) => {
2481
- await scanAndReport(path, [xxeUnsafeXmlParsingRule], "Checking unsafe XML parsing...", options);
2482
- });
2692
+ withScanOptions(
2693
+ 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", ".")
2694
+ ).action(
2695
+ (path, options) => scanAndReport(path, [xxeUnsafeXmlParsingRule], "Checking unsafe XML parsing...", options)
2696
+ );
2483
2697
  };
2484
2698
 
2485
2699
  // src/cli.ts
@@ -2527,6 +2741,8 @@ var registerSecurityCommands = (program) => {
2527
2741
  registerXxeUnsafeXmlParsingCommand(program);
2528
2742
  registerSensitiveDataInLogsCommand(program);
2529
2743
  registerPublicEnvVarSecretCommand(program);
2744
+ registerWeakCipherModeCommand(program);
2745
+ registerHardcodedAuthorizationValueCommand(program);
2530
2746
  };
2531
2747
  var require5 = createRequire4(import.meta.url);
2532
2748
  var readPackageVersion = () => {