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.
- package/README.md +103 -18
- package/dist/index.js +465 -249
- 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
|
-
|
|
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
|
|
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
|
|
174
|
-
|
|
175
|
-
if (result.
|
|
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
|
|
182
|
-
|
|
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
|
-
|
|
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
|
|
212
|
-
high
|
|
213
|
-
medium
|
|
214
|
-
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
|
|
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 = [`## ${
|
|
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})`, "", ...
|
|
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(`| ${
|
|
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 [
|
|
275
|
-
|
|
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
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
352
|
+
return entry.isFile() && isScannable(entry.name) && (includeTests || !isTestFileName(entry.name)) ? [join(currentDir, entry.name)] : [];
|
|
312
353
|
};
|
|
313
|
-
var
|
|
354
|
+
var walk = async (currentDir, includeTests) => {
|
|
314
355
|
try {
|
|
315
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
|
487
|
-
|
|
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}${
|
|
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
|
-
|
|
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 (
|
|
496
|
-
|
|
553
|
+
if (output) {
|
|
554
|
+
return output;
|
|
497
555
|
}
|
|
498
|
-
|
|
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:
|
|
547
|
-
|
|
548
|
-
onResult(
|
|
549
|
-
|
|
624
|
+
task: async () => {
|
|
625
|
+
try {
|
|
626
|
+
onResult(await runScanEngines(path, rules, options));
|
|
627
|
+
} catch (error) {
|
|
628
|
+
throw error;
|
|
550
629
|
}
|
|
551
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
888
|
-
VariableDeclarator
|
|
889
|
-
ObjectProperty
|
|
890
|
-
AssignmentExpression
|
|
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
|
|
894
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1614
|
-
|
|
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
|
|
1764
|
-
const nextCall = ancestors
|
|
1765
|
-
const isChainMember = 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
|
-
|
|
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
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
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
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2465
|
-
"
|
|
2466
|
-
|
|
2467
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2481
|
-
|
|
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 = () => {
|