codesentry 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -2
- package/dist/index.js +659 -141
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Semgrep CE para as linguagens suportadas por ele.
|
|
|
16
16
|
|
|
17
17
|
## O que o CodeSentry faz
|
|
18
18
|
|
|
19
|
-
Rodando `codesentry scan` num projeto,
|
|
19
|
+
Rodando `codesentry scan` num projeto, três motores de análise trabalham
|
|
20
20
|
juntos e o resultado sai unificado em um único relatório:
|
|
21
21
|
|
|
22
22
|
- **Motor nativo** — regras próprias em TypeScript, sem dependências
|
|
@@ -29,6 +29,12 @@ juntos e o resultado sai unificado em um único relatório:
|
|
|
29
29
|
as linguagens que o Semgrep suporta (não só JS/TS), cobrindo os riscos
|
|
30
30
|
do OWASP Top 10 de forma mais ampla que regras hand-rolled sozinhas
|
|
31
31
|
conseguiriam.
|
|
32
|
+
- **Auditoria de dependências** (`npm audit` + [OSV.dev](https://osv.dev)) —
|
|
33
|
+
identifica vulnerabilidades conhecidas nas dependências reais do
|
|
34
|
+
projeto (via `package-lock.json`), com a sugestão de correção vindo
|
|
35
|
+
diretamente do banco consultado. Roda por padrão a partir desta versão
|
|
36
|
+
e exige rede; use `--no-deps` para um scan 100% offline. Ver
|
|
37
|
+
[ADR 0005](docs/adr/0005-osv-dependency-database.md).
|
|
32
38
|
|
|
33
39
|
Cada achado no relatório mostra o arquivo, a linha, a severidade e qual
|
|
34
40
|
motor encontrou o problema (prefixo `semgrep/` para achados do Semgrep).
|
|
@@ -141,6 +147,7 @@ codesentry scan . --json
|
|
|
141
147
|
codesentry scan . --concurrency 4
|
|
142
148
|
codesentry scan . --config ./rules/security.yml
|
|
143
149
|
codesentry scan . --tests
|
|
150
|
+
codesentry scan . --no-deps
|
|
144
151
|
```
|
|
145
152
|
|
|
146
153
|
O Semgrep CE embutido é executado automaticamente depois das regras nativas,
|
|
@@ -148,6 +155,44 @@ usando um snapshot local do ruleset OWASP e `--metrics=off`. O comando não
|
|
|
148
155
|
consulta a Semgrep Registry, não envia métricas e não requer internet após a
|
|
149
156
|
instalação.
|
|
150
157
|
|
|
158
|
+
Por padrão, `scan` também roda a auditoria de dependências (`npm audit` +
|
|
159
|
+
OSV.dev) e funde os achados no mesmo relatório — isso exige `npm` no `PATH`
|
|
160
|
+
e acesso à rede. Use `--no-deps` para pular essa etapa e manter o scan
|
|
161
|
+
100% offline (CI sem egress, ambientes air-gapped). Ver
|
|
162
|
+
[ADR 0005](docs/adr/0005-osv-dependency-database.md).
|
|
163
|
+
|
|
164
|
+
O console mostra quantos pacotes o `npm audit` cobriu e quantos deles o
|
|
165
|
+
OSV.dev conseguiu verificar de fato (`OSV.dev: 360/363 verificados`, por
|
|
166
|
+
exemplo — a diferença indica pacotes cuja consulta falhou, reportados
|
|
167
|
+
também como aviso). No relatório Markdown gerado automaticamente (mais de
|
|
168
|
+
20 problemas), a lista completa de dependências verificadas no OSV.dev
|
|
169
|
+
(`nome@versão`) aparece numa seção própria ao final do arquivo.
|
|
170
|
+
|
|
171
|
+
### Versões e atualização dos motores
|
|
172
|
+
|
|
173
|
+
Use `codesentry version --engines` para auditar exatamente os componentes
|
|
174
|
+
embutidos na sua instalação. O comando não executa o Semgrep nem acessa a
|
|
175
|
+
rede; ele mostra a versão do CodeSentry, a versão do Semgrep CE e Python do
|
|
176
|
+
runtime da plataforma, e a proveniência do snapshot `p/owasp-top-ten`
|
|
177
|
+
(origem, data de captura, revisão upstream quando disponível e SHA-256).
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
codesentry version --engines
|
|
181
|
+
codesentry version --engines --json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
O snapshot do ruleset é identificado pela versão do pacote publicada junto ao
|
|
185
|
+
CodeSentry e pelo SHA-256 do seu conteúdo. A Semgrep Registry não fornece
|
|
186
|
+
necessariamente um commit estável para um ruleset público; nesse caso, o hash
|
|
187
|
+
é o identificador imutável que permite comparar o conteúdo auditado.
|
|
188
|
+
|
|
189
|
+
**Política de atualização:** revisamos semanalmente novas versões do Semgrep
|
|
190
|
+
e alterações no `p/owasp-top-ten`; atualizações regulares são publicadas em
|
|
191
|
+
até 30 dias. Correções upstream classificadas como críticas ou que afetem a
|
|
192
|
+
integridade da análise têm prioridade para uma release em até 48 horas. Toda
|
|
193
|
+
release que atualizar um motor ou ruleset registra as versões e hashes nos
|
|
194
|
+
artefatos publicados.
|
|
195
|
+
|
|
151
196
|
`--concurrency <n>` limita o processamento paralelo do scanner nativo; use
|
|
152
197
|
apenas inteiros positivos. Sem valor, o limite é ajustado para a máquina
|
|
153
198
|
(`min(8, availableParallelism())`). Não há `--config` remoto: atualizações de
|
|
@@ -199,7 +244,7 @@ codesentry xss ./src --json # possíveis XSS (innerHTML, document.write
|
|
|
199
244
|
codesentry unsafe-sql ./src # SQL injection por concatenação
|
|
200
245
|
codesentry command-injection ./src # child_process com entrada não sanitizada
|
|
201
246
|
codesentry weak-hash-algorithm ./src # uso de MD5/SHA-1 para hashing sensível
|
|
202
|
-
codesentry dependency-audit . # `npm audit`
|
|
247
|
+
codesentry dependency-audit . # `npm audit` + OSV.dev nas dependências do projeto (sem --tests: não lê arquivos-fonte)
|
|
203
248
|
```
|
|
204
249
|
|
|
205
250
|
Assim como em `scan`, `--tests` inclui arquivos de teste na análise (por
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { createRequire as
|
|
4
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
5
|
+
import { createRequire as createRequire5 } from "module";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import figlet from "figlet";
|
|
8
8
|
import gradient from "gradient-string";
|
|
@@ -45,13 +45,13 @@ var isExecImport = (specifier) => {
|
|
|
45
45
|
return specifier.type === "ImportSpecifier" && imported?.type === "Identifier" && EXEC_METHOD_NAMES.has(imported.name);
|
|
46
46
|
};
|
|
47
47
|
var isNamespaceImport = (specifier) => specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier";
|
|
48
|
+
var bindingSetForImportSpecifier = (specifier, bindings) => isExecImport(specifier) ? bindings.directCalls : isNamespaceImport(specifier) ? bindings.namespaces : void 0;
|
|
48
49
|
var collectImportBinding = (specifier, bindings) => {
|
|
49
50
|
const name = localName(specifier);
|
|
50
51
|
if (!name) {
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
bindingSet?.add(name);
|
|
54
|
+
bindingSetForImportSpecifier(specifier, bindings)?.add(name);
|
|
55
55
|
};
|
|
56
56
|
var collectFromImportDeclaration = (node, bindings) => {
|
|
57
57
|
if (!isChildProcessModuleSpecifier(node.source)) {
|
|
@@ -71,13 +71,15 @@ var collectDirectCallBindings = (id, bindings) => {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
+
var namespaceNameFromDeclaratorId = (id) => id?.type === "Identifier" ? id.name : void 0;
|
|
75
|
+
var destructuredBindingsFromDeclaratorId = (id) => id?.type === "ObjectPattern" ? id : void 0;
|
|
74
76
|
var collectFromVariableDeclarator = (node, bindings) => {
|
|
75
77
|
if (!isRequireCall(node.init)) {
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
const id = node.id;
|
|
79
|
-
const namespaceName = id
|
|
80
|
-
const destructuredBindings = id
|
|
81
|
+
const namespaceName = namespaceNameFromDeclaratorId(id);
|
|
82
|
+
const destructuredBindings = destructuredBindingsFromDeclaratorId(id);
|
|
81
83
|
namespaceName && bindings.namespaces.add(namespaceName);
|
|
82
84
|
destructuredBindings && collectDirectCallBindings(destructuredBindings, bindings);
|
|
83
85
|
};
|
|
@@ -92,11 +94,8 @@ var collectChildProcessBindings = (sourceFile) => {
|
|
|
92
94
|
});
|
|
93
95
|
return bindings;
|
|
94
96
|
};
|
|
95
|
-
var
|
|
96
|
-
if (callee
|
|
97
|
-
return bindings.directCalls.has(callee.name);
|
|
98
|
-
}
|
|
99
|
-
if (callee?.type !== "MemberExpression") {
|
|
97
|
+
var isKnownChildProcessMemberCallee = (callee, bindings) => {
|
|
98
|
+
if (callee.type !== "MemberExpression") {
|
|
100
99
|
return false;
|
|
101
100
|
}
|
|
102
101
|
const object = callee.object;
|
|
@@ -104,6 +103,12 @@ var isKnownChildProcessCallee = (callee, bindings) => {
|
|
|
104
103
|
const methodName = property?.type === "Identifier" ? property.name : void 0;
|
|
105
104
|
return !!methodName && EXEC_METHOD_NAMES.has(methodName) && object?.type === "Identifier" && bindings.namespaces.has(object.name);
|
|
106
105
|
};
|
|
106
|
+
var isKnownChildProcessCallee = (callee, bindings) => {
|
|
107
|
+
if (callee?.type === "Identifier") {
|
|
108
|
+
return bindings.directCalls.has(callee.name);
|
|
109
|
+
}
|
|
110
|
+
return callee !== void 0 && isKnownChildProcessMemberCallee(callee, bindings);
|
|
111
|
+
};
|
|
107
112
|
var isDynamicCommandArgument = (argument) => argument?.type === "StringLiteral" ? false : argument?.type === "TemplateLiteral" ? (argument.expressions?.length ?? 0) > 0 : argument !== void 0;
|
|
108
113
|
var isCommandInjectionCall = (node, bindings) => {
|
|
109
114
|
if (node.type !== "CallExpression") {
|
|
@@ -146,7 +151,7 @@ var withScanOptions = (command) => command.option("--json", "exibe o resultado e
|
|
|
146
151
|
|
|
147
152
|
// src/commands/scan/scan-runner.ts
|
|
148
153
|
import { writeFile } from "fs/promises";
|
|
149
|
-
import { join as
|
|
154
|
+
import { join as join3 } from "path";
|
|
150
155
|
import chalk2 from "chalk";
|
|
151
156
|
import { Listr } from "listr2";
|
|
152
157
|
|
|
@@ -185,9 +190,9 @@ var mergeScanResults = (nativeResult, semgrepResult) => ({
|
|
|
185
190
|
semgrep: semgrepResult.engines?.semgrep ?? semgrepResult.scannedFiles
|
|
186
191
|
}
|
|
187
192
|
});
|
|
188
|
-
var finalizeScanResult = (result) => ({
|
|
193
|
+
var finalizeScanResult = (result, dependencyAuditCoverage2 = false) => ({
|
|
189
194
|
...result,
|
|
190
|
-
engines: { ...result.engines, dependencyAudit:
|
|
195
|
+
engines: { ...result.engines, dependencyAudit: dependencyAuditCoverage2 },
|
|
191
196
|
warnings: result.engines?.semgrep === 0 ? [...result.warnings ?? [], ZERO_SEMGREP_COVERAGE_WARNING] : result.warnings
|
|
192
197
|
});
|
|
193
198
|
|
|
@@ -198,7 +203,16 @@ var SEVERITY_COLOR = {
|
|
|
198
203
|
high: (text2) => chalk.red(text2),
|
|
199
204
|
critical: (text2) => chalk.bgRed.white(text2)
|
|
200
205
|
};
|
|
201
|
-
var
|
|
206
|
+
var semgrepCoverage = (result) => result.engines?.semgrep === void 0 ? void 0 : `CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s)`;
|
|
207
|
+
var dependencyAuditCoverage = (result) => typeof result.engines?.dependencyAudit === "number" ? `Dependency audit: ${result.engines.dependencyAudit} pacote(s) via npm audit` : void 0;
|
|
208
|
+
var osvCoverage = (result) => result.engines?.osv ? `OSV.dev: ${result.engines.osv.checked}/${result.engines.osv.total} verificados` : void 0;
|
|
209
|
+
var coverageParts = (result) => [semgrepCoverage(result), dependencyAuditCoverage(result), osvCoverage(result)].filter(
|
|
210
|
+
(part) => part !== void 0
|
|
211
|
+
);
|
|
212
|
+
var coverageText = (result) => {
|
|
213
|
+
const parts = coverageParts(result);
|
|
214
|
+
return parts.length ? ` ${parts.join("; ")}.` : "";
|
|
215
|
+
};
|
|
202
216
|
var printNotes = (result) => {
|
|
203
217
|
if (result.engines?.dependencyAudit === false) {
|
|
204
218
|
console.log(chalk.cyan(DEPENDENCY_AUDIT_NOTE));
|
|
@@ -316,26 +330,360 @@ var severitySections = (bySeverity) => {
|
|
|
316
330
|
}
|
|
317
331
|
return lines;
|
|
318
332
|
};
|
|
333
|
+
var osvCheckedSection = (result) => {
|
|
334
|
+
const { osvCheckedPackages } = result;
|
|
335
|
+
if (!osvCheckedPackages || osvCheckedPackages.length === 0) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
const checked = result.engines?.osv?.checked ?? osvCheckedPackages.length;
|
|
339
|
+
const total = result.engines?.osv?.total ?? osvCheckedPackages.length;
|
|
340
|
+
return [
|
|
341
|
+
`## Depend\xEAncias verificadas no OSV.dev (${checked}/${total})`,
|
|
342
|
+
"",
|
|
343
|
+
...osvCheckedPackages.map((pkg) => `- ${pkg}`),
|
|
344
|
+
""
|
|
345
|
+
];
|
|
346
|
+
};
|
|
319
347
|
var toMarkdownReport = (result, generatedAt = /* @__PURE__ */ new Date()) => {
|
|
320
348
|
const bySeverity = groupBy(result.findings, (f) => f.severity);
|
|
321
349
|
return [
|
|
322
350
|
...reportHeader(result, generatedAt),
|
|
323
351
|
...warningsSection(result),
|
|
324
352
|
...summaryTable(bySeverity),
|
|
325
|
-
...severitySections(bySeverity)
|
|
353
|
+
...severitySections(bySeverity),
|
|
354
|
+
...osvCheckedSection(result)
|
|
326
355
|
].join("\n");
|
|
327
356
|
};
|
|
328
357
|
|
|
329
|
-
// src/scanner/
|
|
358
|
+
// src/scanner/dependency-audit.ts
|
|
359
|
+
import { execFile } from "child_process";
|
|
330
360
|
import { readFile } from "fs/promises";
|
|
361
|
+
import { join } from "path";
|
|
362
|
+
import { promisify } from "util";
|
|
363
|
+
|
|
364
|
+
// src/scanner/run-with-concurrency-limit.ts
|
|
365
|
+
import pLimit from "p-limit";
|
|
366
|
+
var runWithConcurrencyLimit = async (items, concurrency, task) => {
|
|
367
|
+
const limit = pLimit(concurrency);
|
|
368
|
+
return Promise.all(items.map((item) => limit(() => task(item))));
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/scanner/osv-client.ts
|
|
372
|
+
var OSV_BATCH_CHUNK_SIZE = 100;
|
|
373
|
+
var OSV_DETAIL_CONCURRENCY = 10;
|
|
374
|
+
var OSV_API_BASE = "https://api.osv.dev/v1";
|
|
375
|
+
var SEVERITY_MAP = {
|
|
376
|
+
LOW: "low",
|
|
377
|
+
MODERATE: "medium",
|
|
378
|
+
HIGH: "high",
|
|
379
|
+
CRITICAL: "critical"
|
|
380
|
+
};
|
|
381
|
+
var chunk = (items, size) => {
|
|
382
|
+
const chunks = [];
|
|
383
|
+
for (let i = 0; i < items.length; i += size) {
|
|
384
|
+
chunks.push(items.slice(i, i + size));
|
|
385
|
+
}
|
|
386
|
+
return chunks;
|
|
387
|
+
};
|
|
388
|
+
var zipVulnIdsByPackage = (packages, body) => {
|
|
389
|
+
const vulnIdsByPackage = /* @__PURE__ */ new Map();
|
|
390
|
+
packages.forEach((pkg, index) => {
|
|
391
|
+
const ids = body.results?.[index]?.vulns?.map((v) => v.id);
|
|
392
|
+
if (ids?.length) {
|
|
393
|
+
vulnIdsByPackage.set(`${pkg.name}@${pkg.version}`, ids);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
return vulnIdsByPackage;
|
|
397
|
+
};
|
|
398
|
+
var queryBatchChunk = async (packages, fetchImpl) => {
|
|
399
|
+
try {
|
|
400
|
+
const response = await fetchImpl(`${OSV_API_BASE}/querybatch`, {
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: { "Content-Type": "application/json" },
|
|
403
|
+
body: JSON.stringify({
|
|
404
|
+
queries: packages.map((p) => ({
|
|
405
|
+
package: { name: p.name, ecosystem: "npm" },
|
|
406
|
+
version: p.version
|
|
407
|
+
}))
|
|
408
|
+
})
|
|
409
|
+
});
|
|
410
|
+
if (!response.ok) {
|
|
411
|
+
return { vulnIdsByPackage: /* @__PURE__ */ new Map(), checkedPackages: [], failedCount: packages.length };
|
|
412
|
+
}
|
|
413
|
+
const body = await response.json();
|
|
414
|
+
return { vulnIdsByPackage: zipVulnIdsByPackage(packages, body), checkedPackages: packages, failedCount: 0 };
|
|
415
|
+
} catch {
|
|
416
|
+
return { vulnIdsByPackage: /* @__PURE__ */ new Map(), checkedPackages: [], failedCount: packages.length };
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
var queryOsvBatch = async (packages, fetchImpl = fetch) => {
|
|
420
|
+
const vulnIdsByPackage = /* @__PURE__ */ new Map();
|
|
421
|
+
const checkedPackages = [];
|
|
422
|
+
let failedCount = 0;
|
|
423
|
+
try {
|
|
424
|
+
for (const packageChunk of chunk(packages, OSV_BATCH_CHUNK_SIZE)) {
|
|
425
|
+
const result = await queryBatchChunk(packageChunk, fetchImpl);
|
|
426
|
+
result.vulnIdsByPackage.forEach((ids, key) => vulnIdsByPackage.set(key, ids));
|
|
427
|
+
checkedPackages.push(...result.checkedPackages);
|
|
428
|
+
failedCount += result.failedCount;
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
failedCount += packages.length - checkedPackages.length;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
vulnIdsByPackage,
|
|
435
|
+
checkedPackages,
|
|
436
|
+
warning: failedCount > 0 ? `N\xE3o foi poss\xEDvel consultar o OSV.dev para ${failedCount} pacote(s).` : void 0
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
var fetchOneVulnerabilityDetail = async (id, fetchImpl, detailsById) => {
|
|
440
|
+
try {
|
|
441
|
+
const response = await fetchImpl(`${OSV_API_BASE}/vulns/${id}`);
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
detailsById.set(id, await response.json());
|
|
446
|
+
return true;
|
|
447
|
+
} catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
var fetchOsvVulnerabilityDetails = async (ids, fetchImpl = fetch) => {
|
|
452
|
+
const uniqueIds = [...new Set(ids)];
|
|
453
|
+
const detailsById = /* @__PURE__ */ new Map();
|
|
454
|
+
let results;
|
|
455
|
+
try {
|
|
456
|
+
results = await runWithConcurrencyLimit(
|
|
457
|
+
uniqueIds,
|
|
458
|
+
OSV_DETAIL_CONCURRENCY,
|
|
459
|
+
(id) => fetchOneVulnerabilityDetail(id, fetchImpl, detailsById)
|
|
460
|
+
);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
const failedCount = results.filter((ok) => !ok).length;
|
|
465
|
+
return {
|
|
466
|
+
detailsById,
|
|
467
|
+
warning: failedCount > 0 ? `N\xE3o foi poss\xEDvel obter detalhes do OSV.dev para ${failedCount} advisory(s).` : void 0
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
var isMatchingNpmPackage = (affected, packageName) => affected.package.ecosystem === "npm" && affected.package.name === packageName;
|
|
471
|
+
var extractFixedVersions = (vuln, packageName) => {
|
|
472
|
+
const fixedVersions = (vuln.affected ?? []).filter((affected) => isMatchingNpmPackage(affected, packageName)).flatMap((affected) => affected.ranges ?? []).flatMap((range) => range.events).map((event) => event.fixed).filter((fixed) => Boolean(fixed));
|
|
473
|
+
return [...new Set(fixedVersions)];
|
|
474
|
+
};
|
|
475
|
+
var mapOsvSeverity = (vuln) => {
|
|
476
|
+
const severity = vuln.database_specific?.severity;
|
|
477
|
+
if (!severity) {
|
|
478
|
+
return "medium";
|
|
479
|
+
}
|
|
480
|
+
return SEVERITY_MAP[severity] ?? "medium";
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/scanner/package-lock-parser.ts
|
|
484
|
+
var NODE_MODULES_SEGMENT = "node_modules/";
|
|
485
|
+
var isUnresolvableSource = (resolved) => resolved !== void 0 && (resolved.startsWith("file:") || resolved.startsWith("git"));
|
|
486
|
+
var nameFromKey = (key) => {
|
|
487
|
+
const lastIndex = key.lastIndexOf(NODE_MODULES_SEGMENT);
|
|
488
|
+
return lastIndex === -1 ? void 0 : key.slice(lastIndex + NODE_MODULES_SEGMENT.length);
|
|
489
|
+
};
|
|
490
|
+
var toLockedPackage = (key, entry) => {
|
|
491
|
+
if (entry.link || !entry.version || isUnresolvableSource(entry.resolved)) {
|
|
492
|
+
return void 0;
|
|
493
|
+
}
|
|
494
|
+
const name = entry.name ?? nameFromKey(key);
|
|
495
|
+
return name ? { name, version: entry.version } : void 0;
|
|
496
|
+
};
|
|
497
|
+
var assertSupportedLockfileVersion = (lockfileVersion) => {
|
|
498
|
+
if (lockfileVersion !== 2 && lockfileVersion !== 3) {
|
|
499
|
+
throw new Error("lockfileVersion 1 n\xE3o \xE9 suportado \u2014 regenere o lockfile com npm 7+.");
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
var parsePackageLock = (rawJson) => {
|
|
503
|
+
const parsed = JSON.parse(rawJson);
|
|
504
|
+
assertSupportedLockfileVersion(parsed.lockfileVersion);
|
|
505
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
506
|
+
Object.entries(parsed.packages ?? {}).filter(([key]) => key.includes(NODE_MODULES_SEGMENT)).forEach(([key, entry]) => {
|
|
507
|
+
const locked = toLockedPackage(key, entry);
|
|
508
|
+
if (locked) {
|
|
509
|
+
deduped.set(`${locked.name}@${locked.version}`, locked);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
return [...deduped.values()];
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/scanner/dependency-audit.ts
|
|
516
|
+
var execFileAsync = promisify(execFile);
|
|
517
|
+
var SEVERITY_MAP2 = {
|
|
518
|
+
info: "low",
|
|
519
|
+
low: "low",
|
|
520
|
+
moderate: "medium",
|
|
521
|
+
high: "high",
|
|
522
|
+
critical: "critical"
|
|
523
|
+
};
|
|
524
|
+
var vulnerabilityTitle = (vulnerability) => {
|
|
525
|
+
const firstVia = vulnerability.via[0];
|
|
526
|
+
if (typeof firstVia === "object" && firstVia?.title) {
|
|
527
|
+
return firstVia.title;
|
|
528
|
+
}
|
|
529
|
+
return 'ver "npm audit" para detalhes';
|
|
530
|
+
};
|
|
531
|
+
var npmFixSuggestion = (vulnerability) => {
|
|
532
|
+
const { fixAvailable } = vulnerability;
|
|
533
|
+
if (fixAvailable === false) {
|
|
534
|
+
return "sem corre\xE7\xE3o dispon\xEDvel ainda";
|
|
535
|
+
}
|
|
536
|
+
if (fixAvailable === true) {
|
|
537
|
+
return `atualize para uma vers\xE3o fora do intervalo vulner\xE1vel (${vulnerability.range})`;
|
|
538
|
+
}
|
|
539
|
+
return `atualize para ${fixAvailable.name}@${fixAvailable.version}`;
|
|
540
|
+
};
|
|
541
|
+
var mapAuditReportToFindings = (report) => {
|
|
542
|
+
return Object.values(report.vulnerabilities).map((vulnerability) => ({
|
|
543
|
+
ruleId: "dependency-audit",
|
|
544
|
+
message: `Depend\xEAncia vulner\xE1vel: ${vulnerability.name} (${vulnerability.severity}) \u2014 ${vulnerabilityTitle(vulnerability)} \u2014 ${npmFixSuggestion(vulnerability)}`,
|
|
545
|
+
file: "package.json",
|
|
546
|
+
line: 1,
|
|
547
|
+
severity: SEVERITY_MAP2[vulnerability.severity]
|
|
548
|
+
}));
|
|
549
|
+
};
|
|
550
|
+
var osvFixSuggestion = (fixedVersions) => fixedVersions.length ? `atualize para ${fixedVersions.join(" ou ")}` : "nenhuma vers\xE3o corrigida publicada pelo OSV.dev ainda";
|
|
551
|
+
var buildOsvFinding = (pkg, vuln) => {
|
|
552
|
+
const fixedVersions = extractFixedVersions(vuln, pkg.name);
|
|
553
|
+
return {
|
|
554
|
+
ruleId: "dependency-audit",
|
|
555
|
+
message: `OSV ${vuln.id}: ${pkg.name}@${pkg.version} (${mapOsvSeverity(vuln)}) \u2014 ${vuln.summary ?? "ver OSV.dev para detalhes"} \u2014 ${osvFixSuggestion(fixedVersions)}`,
|
|
556
|
+
file: "package-lock.json",
|
|
557
|
+
line: 1,
|
|
558
|
+
severity: mapOsvSeverity(vuln)
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
var findingsForLockedPackage = (pkg, vulnIdsByPackage, detailsById) => {
|
|
562
|
+
const ids = vulnIdsByPackage.get(`${pkg.name}@${pkg.version}`) ?? [];
|
|
563
|
+
return ids.map((id) => detailsById.get(id)).filter((vuln) => vuln !== void 0).map((vuln) => buildOsvFinding(pkg, vuln));
|
|
564
|
+
};
|
|
565
|
+
var mapOsvFindingsToRuleFindings = (lockedPackages, vulnIdsByPackage, detailsById, npmFlaggedNames) => lockedPackages.filter((pkg) => !npmFlaggedNames.has(pkg.name)).flatMap((pkg) => findingsForLockedPackage(pkg, vulnIdsByPackage, detailsById));
|
|
566
|
+
var errorMessage = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
567
|
+
var hasVulnerabilitiesRecord = (value) => typeof value === "object" && value !== null && typeof value.vulnerabilities === "object" && value.vulnerabilities !== null;
|
|
568
|
+
var normalizeNpmAuditReport = (raw) => {
|
|
569
|
+
if (hasVulnerabilitiesRecord(raw)) {
|
|
570
|
+
return { report: { vulnerabilities: raw.vulnerabilities } };
|
|
571
|
+
}
|
|
572
|
+
const errorSummary = typeof raw === "object" && raw !== null && "error" in raw ? raw.error?.summary ?? "formato de resposta inesperado" : "formato de resposta inesperado";
|
|
573
|
+
return {
|
|
574
|
+
report: { vulnerabilities: {} },
|
|
575
|
+
warning: `"npm audit" n\xE3o retornou um relat\xF3rio v\xE1lido: ${errorSummary}.`
|
|
576
|
+
};
|
|
577
|
+
};
|
|
578
|
+
var combineWarnings = (...warnings) => {
|
|
579
|
+
const present = warnings.filter((warning) => Boolean(warning));
|
|
580
|
+
return present.length ? present.join(" ") : void 0;
|
|
581
|
+
};
|
|
582
|
+
var fetchDetailsForIds = (ids, fetchImpl) => ids.length ? fetchOsvVulnerabilityDetails(ids, fetchImpl) : Promise.resolve({ detailsById: /* @__PURE__ */ new Map(), warning: void 0 });
|
|
583
|
+
var auditPackagesWithOsv = async (lockedPackages, npmFlaggedNames, fetchImpl = fetch) => {
|
|
584
|
+
try {
|
|
585
|
+
return await collectOsvAuditResult(lockedPackages, npmFlaggedNames, fetchImpl);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
return {
|
|
588
|
+
findings: [],
|
|
589
|
+
checkedPackages: [],
|
|
590
|
+
warning: `N\xE3o foi poss\xEDvel consultar o OSV.dev: ${errorMessage(error)}.`
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
var collectOsvAuditResult = async (lockedPackages, npmFlaggedNames, fetchImpl) => {
|
|
595
|
+
try {
|
|
596
|
+
const {
|
|
597
|
+
vulnIdsByPackage,
|
|
598
|
+
checkedPackages,
|
|
599
|
+
warning: batchWarning
|
|
600
|
+
} = await queryOsvBatch(lockedPackages, fetchImpl);
|
|
601
|
+
const ids = [...new Set([...vulnIdsByPackage.values()].flat())];
|
|
602
|
+
const { detailsById, warning: detailsWarning } = await fetchDetailsForIds(ids, fetchImpl);
|
|
603
|
+
return {
|
|
604
|
+
findings: mapOsvFindingsToRuleFindings(
|
|
605
|
+
lockedPackages,
|
|
606
|
+
vulnIdsByPackage,
|
|
607
|
+
detailsById,
|
|
608
|
+
npmFlaggedNames
|
|
609
|
+
),
|
|
610
|
+
checkedPackages,
|
|
611
|
+
warning: combineWarnings(batchWarning, detailsWarning)
|
|
612
|
+
};
|
|
613
|
+
} catch (error) {
|
|
614
|
+
throw new Error("N\xE3o foi poss\xEDvel consolidar os resultados do OSV.dev.", { cause: error });
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var runNpmAudit = async (targetDir) => {
|
|
618
|
+
let stdout;
|
|
619
|
+
try {
|
|
620
|
+
({ stdout } = await execFileAsync("npm", ["audit", "--json"], {
|
|
621
|
+
cwd: targetDir,
|
|
622
|
+
maxBuffer: 10 * 1024 * 1024
|
|
623
|
+
}));
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const stdoutFromError = error.stdout;
|
|
626
|
+
if (!stdoutFromError) {
|
|
627
|
+
throw new Error(`N\xE3o foi poss\xEDvel executar "npm audit" em "${targetDir}".`, { cause: error });
|
|
628
|
+
}
|
|
629
|
+
stdout = stdoutFromError;
|
|
630
|
+
}
|
|
631
|
+
return JSON.parse(stdout);
|
|
632
|
+
};
|
|
633
|
+
var auditPackagesFromLockfile = async (targetDir, npmFlaggedNames, fetchImpl) => {
|
|
634
|
+
try {
|
|
635
|
+
const raw = await readFile(join(targetDir, "package-lock.json"), "utf-8");
|
|
636
|
+
const lockedPackages = parsePackageLock(raw);
|
|
637
|
+
const osvResult = await auditPackagesWithOsv(lockedPackages, npmFlaggedNames, fetchImpl);
|
|
638
|
+
return {
|
|
639
|
+
findings: osvResult.findings,
|
|
640
|
+
packagesAudited: lockedPackages.length,
|
|
641
|
+
osvChecked: { checked: osvResult.checkedPackages.length, total: lockedPackages.length },
|
|
642
|
+
osvCheckedPackages: osvResult.checkedPackages.map((pkg) => `${pkg.name}@${pkg.version}`).sort(),
|
|
643
|
+
warning: osvResult.warning
|
|
644
|
+
};
|
|
645
|
+
} catch (error) {
|
|
646
|
+
return {
|
|
647
|
+
findings: [],
|
|
648
|
+
packagesAudited: false,
|
|
649
|
+
warning: `N\xE3o foi poss\xEDvel checar o OSV.dev: ${errorMessage(error)}.`
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
var runDependencyAudit = async (targetDir, fetchImpl = fetch) => {
|
|
654
|
+
const startedAt = Date.now();
|
|
655
|
+
try {
|
|
656
|
+
const { report: npmReport, warning: npmWarning } = normalizeNpmAuditReport(await runNpmAudit(targetDir));
|
|
657
|
+
const npmFindings = mapAuditReportToFindings(npmReport);
|
|
658
|
+
const npmFlaggedNames = new Set(Object.keys(npmReport.vulnerabilities));
|
|
659
|
+
const osv = await auditPackagesFromLockfile(targetDir, npmFlaggedNames, fetchImpl);
|
|
660
|
+
const warnings = combineWarnings(npmWarning, osv.warning);
|
|
661
|
+
return {
|
|
662
|
+
scannedFiles: 1,
|
|
663
|
+
findings: [...npmFindings, ...osv.findings],
|
|
664
|
+
durationMs: Date.now() - startedAt,
|
|
665
|
+
engines: {
|
|
666
|
+
dependencyAudit: osv.packagesAudited === false ? npmFlaggedNames.size : osv.packagesAudited,
|
|
667
|
+
osv: osv.osvChecked
|
|
668
|
+
},
|
|
669
|
+
osvCheckedPackages: osv.osvCheckedPackages,
|
|
670
|
+
warnings: warnings ? [warnings] : void 0
|
|
671
|
+
};
|
|
672
|
+
} catch (error) {
|
|
673
|
+
throw new Error(`N\xE3o foi poss\xEDvel auditar depend\xEAncias em "${targetDir}".`, { cause: error });
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/scanner/scanner.ts
|
|
678
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
331
679
|
import { availableParallelism } from "os";
|
|
332
680
|
|
|
333
681
|
// src/scanner/file-finder.ts
|
|
334
682
|
import { readdir } from "fs/promises";
|
|
335
|
-
import { join } from "path";
|
|
683
|
+
import { join as join2 } from "path";
|
|
336
684
|
|
|
337
685
|
// src/scanner/ignore-patterns.ts
|
|
338
|
-
var ALWAYS_IGNORED_DIR_NAMES = ["node_modules", ".git", "dist", ".next"];
|
|
686
|
+
var ALWAYS_IGNORED_DIR_NAMES = ["node_modules", ".git", "dist", ".next", ".angular"];
|
|
339
687
|
var TEST_DIR_NAMES = ["tests", "test", "__tests__"];
|
|
340
688
|
var TEST_FILE_GLOBS = ["*.spec.*", "*.test.*"];
|
|
341
689
|
var TEST_FILE_NAME_PATTERN = /\.(spec|test)\.[^./]+$/;
|
|
@@ -345,11 +693,17 @@ var isTestFileName = (fileName) => TEST_FILE_NAME_PATTERN.test(fileName);
|
|
|
345
693
|
// src/scanner/file-finder.ts
|
|
346
694
|
var SCANNABLE_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx"];
|
|
347
695
|
var isScannable = (fileName) => SCANNABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
696
|
+
var filesFromDirectory = async (currentDir, entry, includeTests) => {
|
|
697
|
+
return isIgnoredDirName(entry.name, includeTests) ? [] : walk(join2(currentDir, entry.name), includeTests);
|
|
698
|
+
};
|
|
699
|
+
var filesFromFile = (currentDir, entry, includeTests) => {
|
|
700
|
+
return entry.isFile() && isScannable(entry.name) && (includeTests || !isTestFileName(entry.name)) ? [join2(currentDir, entry.name)] : [];
|
|
701
|
+
};
|
|
348
702
|
var filesFromEntry = async (currentDir, entry, includeTests) => {
|
|
349
703
|
if (entry.isDirectory()) {
|
|
350
|
-
return
|
|
704
|
+
return filesFromDirectory(currentDir, entry, includeTests);
|
|
351
705
|
}
|
|
352
|
-
return
|
|
706
|
+
return filesFromFile(currentDir, entry, includeTests);
|
|
353
707
|
};
|
|
354
708
|
var walk = async (currentDir, includeTests) => {
|
|
355
709
|
try {
|
|
@@ -367,26 +721,19 @@ var findFiles = async (targetDir, includeTests = false) => {
|
|
|
367
721
|
}
|
|
368
722
|
};
|
|
369
723
|
|
|
370
|
-
// src/scanner/run-with-concurrency-limit.ts
|
|
371
|
-
import pLimit from "p-limit";
|
|
372
|
-
var runWithConcurrencyLimit = async (items, concurrency, task) => {
|
|
373
|
-
const limit = pLimit(concurrency);
|
|
374
|
-
return Promise.all(items.map((item) => limit(() => task(item))));
|
|
375
|
-
};
|
|
376
|
-
|
|
377
724
|
// src/scanner/scanner.ts
|
|
378
725
|
var DEFAULT_SCAN_CONCURRENCY = Math.max(1, Math.min(8, availableParallelism()));
|
|
379
|
-
var
|
|
726
|
+
var errorMessage2 = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
380
727
|
var parseErrorFinding = (filePath, error) => ({
|
|
381
728
|
ruleId: "parse-error",
|
|
382
|
-
message: `N\xE3o foi poss\xEDvel analisar este arquivo (erro de sintaxe): ${
|
|
729
|
+
message: `N\xE3o foi poss\xEDvel analisar este arquivo (erro de sintaxe): ${errorMessage2(error)}`,
|
|
383
730
|
file: filePath,
|
|
384
731
|
line: 1,
|
|
385
732
|
severity: "low"
|
|
386
733
|
});
|
|
387
734
|
var readFileContent = async (filePath) => {
|
|
388
735
|
try {
|
|
389
|
-
return await
|
|
736
|
+
return await readFile2(filePath, "utf-8");
|
|
390
737
|
} catch (error) {
|
|
391
738
|
throw new Error(`N\xE3o foi poss\xEDvel ler o arquivo "${filePath}".`, { cause: error });
|
|
392
739
|
}
|
|
@@ -433,9 +780,9 @@ var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY, i
|
|
|
433
780
|
};
|
|
434
781
|
|
|
435
782
|
// src/scanner/semgrep.ts
|
|
436
|
-
import { execFile } from "child_process";
|
|
783
|
+
import { execFile as execFile2 } from "child_process";
|
|
437
784
|
import { delimiter, dirname as dirname2 } from "path";
|
|
438
|
-
import { promisify } from "util";
|
|
785
|
+
import { promisify as promisify2 } from "util";
|
|
439
786
|
|
|
440
787
|
// src/scanner/semgrep-runtime.ts
|
|
441
788
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -452,6 +799,7 @@ var packageForPlatform = (platform, architecture) => {
|
|
|
452
799
|
return void 0;
|
|
453
800
|
};
|
|
454
801
|
var unsupportedRuntimeMessage = (platform, architecture) => `O runtime Semgrep embutido n\xE3o est\xE1 dispon\xEDvel para ${platform}-${architecture}. O CodeSentry oferece suporte a Linux x64 e Windows x64.`;
|
|
802
|
+
var errorReason = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
455
803
|
var resolveBundledSemgrepRuntime = (platform = process.platform, architecture = process.arch, resolveManifestPath = (packageName) => require2.resolve(`${packageName}/runtime.json`)) => {
|
|
456
804
|
const packageName = packageForPlatform(platform, architecture);
|
|
457
805
|
if (!packageName) {
|
|
@@ -468,8 +816,7 @@ var resolveBundledSemgrepRuntime = (platform = process.platform, architecture =
|
|
|
468
816
|
}
|
|
469
817
|
return runtime;
|
|
470
818
|
} catch (error) {
|
|
471
|
-
|
|
472
|
-
throw new Error(`N\xE3o foi poss\xEDvel carregar o runtime Semgrep embutido: ${reason}`, { cause: error });
|
|
819
|
+
throw new Error(`N\xE3o foi poss\xEDvel carregar o runtime Semgrep embutido: ${errorReason(error)}`, { cause: error });
|
|
473
820
|
}
|
|
474
821
|
};
|
|
475
822
|
|
|
@@ -491,7 +838,7 @@ var resolveBundledSemgrepRuleset = () => {
|
|
|
491
838
|
};
|
|
492
839
|
|
|
493
840
|
// src/scanner/semgrep.ts
|
|
494
|
-
var
|
|
841
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
495
842
|
var SEMGREP_SEVERITIES = {
|
|
496
843
|
INFO: "low",
|
|
497
844
|
WARNING: "medium",
|
|
@@ -556,7 +903,7 @@ var executeSemgrep = async (targetDir, runtime, ruleset, execute, includeTests)
|
|
|
556
903
|
throw new Error("N\xE3o foi poss\xEDvel executar o Semgrep embutido.", { cause: error });
|
|
557
904
|
}
|
|
558
905
|
};
|
|
559
|
-
var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute =
|
|
906
|
+
var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync2, includeTests = false) => {
|
|
560
907
|
const startedAt = Date.now();
|
|
561
908
|
try {
|
|
562
909
|
const report = parseSemgrepReport(await executeSemgrep(targetDir, runtime, ruleset, execute, includeTests));
|
|
@@ -580,7 +927,7 @@ var generateMarkdownReportFilename = (date = /* @__PURE__ */ new Date()) => {
|
|
|
580
927
|
};
|
|
581
928
|
|
|
582
929
|
// src/commands/scan/scan-runner.ts
|
|
583
|
-
var
|
|
930
|
+
var errorMessage3 = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
584
931
|
var reportScanFailure = (error) => {
|
|
585
932
|
process.exitCode = 1;
|
|
586
933
|
console.error(`Falha ao executar o scan: ${formatErrorChain(error)}`);
|
|
@@ -596,26 +943,53 @@ var writeMarkdownReportIfNeeded = async (result, targetDir) => {
|
|
|
596
943
|
if (result.findings.length <= MARKDOWN_REPORT_FINDINGS_THRESHOLD) {
|
|
597
944
|
return;
|
|
598
945
|
}
|
|
599
|
-
const filePath =
|
|
946
|
+
const filePath = join3(targetDir, generateMarkdownReportFilename());
|
|
600
947
|
try {
|
|
601
948
|
await writeFile(filePath, toMarkdownReport(result), "utf-8");
|
|
602
949
|
console.log(chalk2.cyan(`
|
|
603
950
|
Relat\xF3rio detalhado gerado em: ${filePath}`));
|
|
604
951
|
} catch (error) {
|
|
605
|
-
console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${
|
|
952
|
+
console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${errorMessage3(error)}`));
|
|
606
953
|
}
|
|
607
954
|
};
|
|
608
|
-
var
|
|
955
|
+
var runNativeAndSemgrep = async (path, rules, options) => {
|
|
609
956
|
try {
|
|
610
957
|
const includeTests = options.tests ?? false;
|
|
611
958
|
const nativeResult = await runScan(path, rules, options.concurrency, includeTests);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
return finalizeScanResult(mergeScanResults(nativeResult, semgrepResult));
|
|
959
|
+
return options.semgrep ? mergeScanResults(
|
|
960
|
+
nativeResult,
|
|
961
|
+
await runBundledSemgrep(path, void 0, options.config, void 0, includeTests)
|
|
962
|
+
) : nativeResult;
|
|
617
963
|
} catch (error) {
|
|
618
|
-
throw new Error(`Falha durante a an\xE1lise: ${
|
|
964
|
+
throw new Error(`Falha durante a an\xE1lise: ${errorMessage3(error)}`, { cause: error });
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
var runOptionalDependencyAudit = async (path, merged) => {
|
|
968
|
+
try {
|
|
969
|
+
const auditResult = await runDependencyAudit(path);
|
|
970
|
+
return finalizeScanResult(
|
|
971
|
+
{
|
|
972
|
+
...merged,
|
|
973
|
+
findings: [...merged.findings, ...auditResult.findings],
|
|
974
|
+
warnings: [...merged.warnings ?? [], ...auditResult.warnings ?? []],
|
|
975
|
+
engines: { ...merged.engines, osv: auditResult.engines?.osv },
|
|
976
|
+
osvCheckedPackages: auditResult.osvCheckedPackages
|
|
977
|
+
},
|
|
978
|
+
auditResult.engines?.dependencyAudit ?? false
|
|
979
|
+
);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
return finalizeScanResult({
|
|
982
|
+
...merged,
|
|
983
|
+
warnings: [...merged.warnings ?? [], `Auditoria de depend\xEAncias falhou: ${errorMessage3(error)}.`]
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
var runScanEngines = async (path, rules, options) => {
|
|
988
|
+
try {
|
|
989
|
+
const merged = await runNativeAndSemgrep(path, rules, options);
|
|
990
|
+
return options.deps ? runOptionalDependencyAudit(path, merged) : finalizeScanResult(merged);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
throw error;
|
|
619
993
|
}
|
|
620
994
|
};
|
|
621
995
|
var createScanTasks = (path, rules, taskTitle, options, onResult) => new Listr([
|
|
@@ -721,8 +1095,11 @@ var walkNestingBlock = (node, depth, results) => {
|
|
|
721
1095
|
checkDepth(newDepth, node.loc?.start.line, results);
|
|
722
1096
|
walkChildren(node, newDepth, results);
|
|
723
1097
|
};
|
|
1098
|
+
var selectNonFunctionWalker = (node) => {
|
|
1099
|
+
return node.type === "IfStatement" ? walkIfChain : NESTING_TYPES.has(node.type) ? walkNestingBlock : walkChildren;
|
|
1100
|
+
};
|
|
724
1101
|
var selectWalker = (node) => {
|
|
725
|
-
return FUNCTION_TYPES.has(node.type) ? walkFunction :
|
|
1102
|
+
return FUNCTION_TYPES.has(node.type) ? walkFunction : selectNonFunctionWalker(node);
|
|
726
1103
|
};
|
|
727
1104
|
var walk2 = (node, depth, results) => {
|
|
728
1105
|
selectWalker(node)(node, depth, results);
|
|
@@ -756,56 +1133,6 @@ var registerDeepNestingCommand = (program) => {
|
|
|
756
1133
|
);
|
|
757
1134
|
};
|
|
758
1135
|
|
|
759
|
-
// src/scanner/dependency-audit.ts
|
|
760
|
-
import { execFile as execFile2 } from "child_process";
|
|
761
|
-
import { promisify as promisify2 } from "util";
|
|
762
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
763
|
-
var SEVERITY_MAP = {
|
|
764
|
-
info: "low",
|
|
765
|
-
low: "low",
|
|
766
|
-
moderate: "medium",
|
|
767
|
-
high: "high",
|
|
768
|
-
critical: "critical"
|
|
769
|
-
};
|
|
770
|
-
var vulnerabilityTitle = (vulnerability) => {
|
|
771
|
-
const firstVia = vulnerability.via[0];
|
|
772
|
-
if (typeof firstVia === "object" && firstVia?.title) {
|
|
773
|
-
return firstVia.title;
|
|
774
|
-
}
|
|
775
|
-
return 'ver "npm audit" para detalhes';
|
|
776
|
-
};
|
|
777
|
-
var mapAuditReportToFindings = (report) => {
|
|
778
|
-
return Object.values(report.vulnerabilities).map((vulnerability) => ({
|
|
779
|
-
ruleId: "dependency-audit",
|
|
780
|
-
message: `Depend\xEAncia vulner\xE1vel: ${vulnerability.name} (${vulnerability.severity}) \u2014 ${vulnerabilityTitle(vulnerability)}`,
|
|
781
|
-
file: "package.json",
|
|
782
|
-
line: 1,
|
|
783
|
-
severity: SEVERITY_MAP[vulnerability.severity]
|
|
784
|
-
}));
|
|
785
|
-
};
|
|
786
|
-
var runDependencyAudit = async (targetDir) => {
|
|
787
|
-
const startedAt = Date.now();
|
|
788
|
-
let stdout;
|
|
789
|
-
try {
|
|
790
|
-
({ stdout } = await execFileAsync2("npm", ["audit", "--json"], {
|
|
791
|
-
cwd: targetDir,
|
|
792
|
-
maxBuffer: 10 * 1024 * 1024
|
|
793
|
-
}));
|
|
794
|
-
} catch (error) {
|
|
795
|
-
const stdoutFromError = error.stdout;
|
|
796
|
-
if (!stdoutFromError) {
|
|
797
|
-
throw new Error(`N\xE3o foi poss\xEDvel executar "npm audit" em "${targetDir}".`, { cause: error });
|
|
798
|
-
}
|
|
799
|
-
stdout = stdoutFromError;
|
|
800
|
-
}
|
|
801
|
-
const report = JSON.parse(stdout);
|
|
802
|
-
return {
|
|
803
|
-
scannedFiles: 1,
|
|
804
|
-
findings: mapAuditReportToFindings(report),
|
|
805
|
-
durationMs: Date.now() - startedAt
|
|
806
|
-
};
|
|
807
|
-
};
|
|
808
|
-
|
|
809
1136
|
// src/commands/dependency-audit/dependency-audit.command.ts
|
|
810
1137
|
var printAuditResult = (result, json) => {
|
|
811
1138
|
if (json) {
|
|
@@ -825,7 +1152,7 @@ var auditAndReport = async (path, options) => {
|
|
|
825
1152
|
};
|
|
826
1153
|
var registerDependencyAuditCommand = (program) => {
|
|
827
1154
|
program.command("dependency-audit").description(
|
|
828
|
-
'Audita as depend\xEAncias do projeto contra vulnerabilidades conhecidas (via "npm audit"; requer npm no PATH e acesso \xE0 rede)'
|
|
1155
|
+
'Audita as depend\xEAncias do projeto contra vulnerabilidades conhecidas (via "npm audit" e OSV.dev; requer npm no PATH e acesso \xE0 rede)'
|
|
829
1156
|
).argument("[path]", "diret\xF3rio do projeto a ser auditado", ".").option("--json", "exibe o resultado em JSON").action((path, options) => auditAndReport(path, options));
|
|
830
1157
|
};
|
|
831
1158
|
|
|
@@ -883,12 +1210,24 @@ var hasLimitOption = (options) => {
|
|
|
883
1210
|
return key?.type === "Identifier" && key.name === "limit";
|
|
884
1211
|
}) ?? false;
|
|
885
1212
|
};
|
|
1213
|
+
var getMemberCallParts = (node) => {
|
|
1214
|
+
if (node.type !== "CallExpression") {
|
|
1215
|
+
return { object: void 0, property: void 0, args: void 0 };
|
|
1216
|
+
}
|
|
1217
|
+
const callee = node.callee;
|
|
1218
|
+
const args = node.arguments;
|
|
1219
|
+
if (callee?.type !== "MemberExpression") {
|
|
1220
|
+
return { object: void 0, property: void 0, args };
|
|
1221
|
+
}
|
|
1222
|
+
return {
|
|
1223
|
+
object: callee.object,
|
|
1224
|
+
property: callee.property,
|
|
1225
|
+
args
|
|
1226
|
+
};
|
|
1227
|
+
};
|
|
886
1228
|
var isBodyParserWithoutLimit = (node) => {
|
|
887
|
-
const
|
|
888
|
-
const object = callee?.type === "MemberExpression" ? callee.object : void 0;
|
|
889
|
-
const property = callee?.type === "MemberExpression" ? callee.property : void 0;
|
|
1229
|
+
const { object, property, args } = getMemberCallParts(node);
|
|
890
1230
|
const isBodyParserCall = object?.type === "Identifier" && BODY_PARSER_OBJECTS.has(object.name) && property?.type === "Identifier" && BODY_PARSER_METHODS.has(property.name);
|
|
891
|
-
const args = node.type === "CallExpression" ? node.arguments : void 0;
|
|
892
1231
|
return isBodyParserCall && !hasLimitOption(args?.[0]);
|
|
893
1232
|
};
|
|
894
1233
|
var findMissingBodyLimitLines = (filePath, content) => {
|
|
@@ -1133,9 +1472,10 @@ var createFunctionStatementCountRule = (config) => ({
|
|
|
1133
1472
|
// src/rules/high-complexity.rule.ts
|
|
1134
1473
|
var highComplexityRule = createFunctionStatementCountRule({
|
|
1135
1474
|
id: "high-complexity",
|
|
1136
|
-
description: "Detecta fun\xE7\xF5es com muitos condicionais/loops (complexidade alta)",
|
|
1475
|
+
description: "Detecta fun\xE7\xF5es com muitos condicionais/loops/tern\xE1rios (complexidade alta)",
|
|
1137
1476
|
statementTypes: /* @__PURE__ */ new Set([
|
|
1138
1477
|
"IfStatement",
|
|
1478
|
+
"ConditionalExpression",
|
|
1139
1479
|
"ForStatement",
|
|
1140
1480
|
"ForInStatement",
|
|
1141
1481
|
"ForOfStatement",
|
|
@@ -1145,7 +1485,7 @@ var highComplexityRule = createFunctionStatementCountRule({
|
|
|
1145
1485
|
"CatchClause"
|
|
1146
1486
|
]),
|
|
1147
1487
|
maxCount: 5,
|
|
1148
|
-
unitLabel: "condicionais/loops"
|
|
1488
|
+
unitLabel: "condicionais/loops/tern\xE1rios"
|
|
1149
1489
|
});
|
|
1150
1490
|
|
|
1151
1491
|
// src/commands/high-complexity/high-complexity.command.ts
|
|
@@ -1216,13 +1556,16 @@ var isMathRandomCall = (node) => {
|
|
|
1216
1556
|
return object?.type === "Identifier" && object.name === "Math" && property?.type === "Identifier" && property.name === "random";
|
|
1217
1557
|
};
|
|
1218
1558
|
var HASH_LIKE_NAME_PATTERN = /hash|md5|sha1/i;
|
|
1559
|
+
var memberPropertyName = (callee) => {
|
|
1560
|
+
const property = callee.property;
|
|
1561
|
+
return property?.type === "Identifier" ? property.name : void 0;
|
|
1562
|
+
};
|
|
1219
1563
|
var calleeName = (callee) => {
|
|
1220
1564
|
if (callee?.type === "Identifier") {
|
|
1221
1565
|
return callee.name;
|
|
1222
1566
|
}
|
|
1223
1567
|
if (callee?.type === "MemberExpression") {
|
|
1224
|
-
|
|
1225
|
-
return property?.type === "Identifier" ? property.name : void 0;
|
|
1568
|
+
return memberPropertyName(callee);
|
|
1226
1569
|
}
|
|
1227
1570
|
return void 0;
|
|
1228
1571
|
};
|
|
@@ -1408,12 +1751,24 @@ var objectHasProperty = (object, propertyName) => {
|
|
|
1408
1751
|
return key?.type === "Identifier" && key.name === propertyName;
|
|
1409
1752
|
}) ?? false;
|
|
1410
1753
|
};
|
|
1411
|
-
var
|
|
1754
|
+
var getCallExpressionParts = (node) => {
|
|
1412
1755
|
const callee = node.type === "CallExpression" ? node.callee : void 0;
|
|
1756
|
+
const args = node.type === "CallExpression" ? node.arguments : void 0;
|
|
1757
|
+
return { callee, args };
|
|
1758
|
+
};
|
|
1759
|
+
var getMemberExpressionParts = (callee) => {
|
|
1413
1760
|
const object = callee?.type === "MemberExpression" ? callee.object : void 0;
|
|
1414
1761
|
const property = callee?.type === "MemberExpression" ? callee.property : void 0;
|
|
1762
|
+
return { object, property };
|
|
1763
|
+
};
|
|
1764
|
+
var getJwtSignCallParts = (node) => {
|
|
1765
|
+
const { callee, args } = getCallExpressionParts(node);
|
|
1766
|
+
const { object, property } = getMemberExpressionParts(callee);
|
|
1767
|
+
return { object, property, args };
|
|
1768
|
+
};
|
|
1769
|
+
var isJwtSignWithoutExpiration = (node) => {
|
|
1770
|
+
const { object, property, args } = getJwtSignCallParts(node);
|
|
1415
1771
|
const isJwtSignCall = object?.type === "Identifier" && object.name === "jwt" && property?.type === "Identifier" && property.name === "sign";
|
|
1416
|
-
const args = node.type === "CallExpression" ? node.arguments : void 0;
|
|
1417
1772
|
const payload = args?.[0];
|
|
1418
1773
|
const options = args?.[2];
|
|
1419
1774
|
const hasExpInPayload = objectHasProperty(payload, "exp");
|
|
@@ -1686,12 +2041,26 @@ var isPermissiveCorsCall = (node) => {
|
|
|
1686
2041
|
const args = node.arguments;
|
|
1687
2042
|
return (args?.length ?? 0) === 0 || isWildcardOriginOption(args?.[0]);
|
|
1688
2043
|
};
|
|
2044
|
+
var callExpressionParts = (node) => {
|
|
2045
|
+
if (node.type !== "CallExpression") {
|
|
2046
|
+
return void 0;
|
|
2047
|
+
}
|
|
2048
|
+
return {
|
|
2049
|
+
callee: node.callee,
|
|
2050
|
+
args: node.arguments
|
|
2051
|
+
};
|
|
2052
|
+
};
|
|
2053
|
+
var memberCalleeProperty = (callee) => {
|
|
2054
|
+
if (callee?.type !== "MemberExpression") {
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
return callee.property;
|
|
2058
|
+
};
|
|
1689
2059
|
var isWildcardOriginHeader = (node) => {
|
|
1690
|
-
const
|
|
1691
|
-
const property =
|
|
1692
|
-
const
|
|
1693
|
-
const
|
|
1694
|
-
const headerValue = args?.[1];
|
|
2060
|
+
const parts = callExpressionParts(node);
|
|
2061
|
+
const property = memberCalleeProperty(parts?.callee);
|
|
2062
|
+
const headerName = parts?.args?.[0];
|
|
2063
|
+
const headerValue = parts?.args?.[1];
|
|
1695
2064
|
return property?.type === "Identifier" && HEADER_SETTER_METHODS.has(property.name) && headerName?.type === "StringLiteral" && headerName.value === "Access-Control-Allow-Origin" && headerValue?.type === "StringLiteral" && headerValue.value === "*";
|
|
1696
2065
|
};
|
|
1697
2066
|
var findPermissiveCorsLines = (filePath, content) => {
|
|
@@ -1845,13 +2214,26 @@ var awaitNoTryCatchRule = {
|
|
|
1845
2214
|
|
|
1846
2215
|
// src/rules/floating-promise.rule.ts
|
|
1847
2216
|
var PROMISE_STATIC_METHODS = /* @__PURE__ */ new Set(["all", "race", "allSettled", "any"]);
|
|
1848
|
-
var
|
|
2217
|
+
var functionDeclarationName = (node) => {
|
|
1849
2218
|
const declarationId = node.type === "FunctionDeclaration" ? node.id : void 0;
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2219
|
+
return declarationId?.type === "Identifier" ? declarationId.name : void 0;
|
|
2220
|
+
};
|
|
2221
|
+
var isAsyncFunctionValue = (init) => {
|
|
2222
|
+
return (init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression") && init.async;
|
|
2223
|
+
};
|
|
2224
|
+
var asyncVariableDeclaratorId = (node) => {
|
|
2225
|
+
return node.type === "VariableDeclarator" ? node.id : void 0;
|
|
2226
|
+
};
|
|
2227
|
+
var asyncVariableDeclaratorInit = (node) => {
|
|
2228
|
+
return node.type === "VariableDeclarator" ? node.init : void 0;
|
|
2229
|
+
};
|
|
2230
|
+
var asyncVariableDeclaratorName = (node) => {
|
|
2231
|
+
const id = asyncVariableDeclaratorId(node);
|
|
2232
|
+
const init = asyncVariableDeclaratorInit(node);
|
|
2233
|
+
return id?.type === "Identifier" && isAsyncFunctionValue(init) ? id.name : void 0;
|
|
2234
|
+
};
|
|
2235
|
+
var asyncFunctionName = (node) => {
|
|
2236
|
+
return functionDeclarationName(node) ?? asyncVariableDeclaratorName(node);
|
|
1855
2237
|
};
|
|
1856
2238
|
var collectAsyncFunctionNames = (sourceFile) => {
|
|
1857
2239
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -1863,16 +2245,20 @@ var collectAsyncFunctionNames = (sourceFile) => {
|
|
|
1863
2245
|
});
|
|
1864
2246
|
return names;
|
|
1865
2247
|
};
|
|
2248
|
+
var isKnownFunctionCall = (callee, asyncFunctionNames) => {
|
|
2249
|
+
return callee.type === "Identifier" && (callee.name === "fetch" || asyncFunctionNames.has(callee.name));
|
|
2250
|
+
};
|
|
2251
|
+
var isPromiseStaticMethodCall = (callee) => {
|
|
2252
|
+
const object = callee.type === "MemberExpression" ? callee.object : void 0;
|
|
2253
|
+
const property = callee.type === "MemberExpression" ? callee.property : void 0;
|
|
2254
|
+
return object?.type === "Identifier" && object.name === "Promise" && property?.type === "Identifier" && PROMISE_STATIC_METHODS.has(property.name);
|
|
2255
|
+
};
|
|
1866
2256
|
var isKnownPromiseReturningCall = (call, asyncFunctionNames) => {
|
|
1867
2257
|
const callee = call.callee;
|
|
1868
2258
|
if (!callee) {
|
|
1869
2259
|
return false;
|
|
1870
2260
|
}
|
|
1871
|
-
|
|
1872
|
-
const object = callee.type === "MemberExpression" ? callee.object : void 0;
|
|
1873
|
-
const property = callee.type === "MemberExpression" ? callee.property : void 0;
|
|
1874
|
-
const isPromiseStaticMethod = object?.type === "Identifier" && object.name === "Promise" && property?.type === "Identifier" && PROMISE_STATIC_METHODS.has(property.name);
|
|
1875
|
-
return isKnownFunction || isPromiseStaticMethod;
|
|
2261
|
+
return isKnownFunctionCall(callee, asyncFunctionNames) || isPromiseStaticMethodCall(callee);
|
|
1876
2262
|
};
|
|
1877
2263
|
var findFloatingPromiseLines = (filePath, content) => {
|
|
1878
2264
|
const lines = /* @__PURE__ */ new Set();
|
|
@@ -1902,7 +2288,7 @@ var floatingPromiseRule = {
|
|
|
1902
2288
|
};
|
|
1903
2289
|
|
|
1904
2290
|
// src/rules/promise-no-catch.rule.ts
|
|
1905
|
-
var
|
|
2291
|
+
var memberPropertyName2 = (member) => {
|
|
1906
2292
|
const property = member.property;
|
|
1907
2293
|
return property?.type === "Identifier" ? property.name : void 0;
|
|
1908
2294
|
};
|
|
@@ -1915,7 +2301,11 @@ var isThenCall = (node) => {
|
|
|
1915
2301
|
}
|
|
1916
2302
|
const callee = node.callee;
|
|
1917
2303
|
const args = node.arguments;
|
|
1918
|
-
return callee?.type === "MemberExpression" &&
|
|
2304
|
+
return callee?.type === "MemberExpression" && memberPropertyName2(callee) === "then" && (args?.length ?? 0) < 2;
|
|
2305
|
+
};
|
|
2306
|
+
var chainMethodName = (currentCall, member, nextCall) => {
|
|
2307
|
+
const isChainMember = member !== void 0 && member.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
|
|
2308
|
+
return isChainMember ? memberPropertyName2(member) : void 0;
|
|
1919
2309
|
};
|
|
1920
2310
|
var chainReachesCatch = (thenCall, ancestors) => {
|
|
1921
2311
|
let currentCall = thenCall;
|
|
@@ -1923,8 +2313,7 @@ var chainReachesCatch = (thenCall, ancestors) => {
|
|
|
1923
2313
|
while (i < ancestors.length) {
|
|
1924
2314
|
const member = ancestors.at(i);
|
|
1925
2315
|
const nextCall = ancestors.at(i + 1);
|
|
1926
|
-
const
|
|
1927
|
-
const methodName = isChainMember ? memberPropertyName(member) : void 0;
|
|
2316
|
+
const methodName = chainMethodName(currentCall, member, nextCall);
|
|
1928
2317
|
const continuesChain = methodName === "then" || methodName === "finally";
|
|
1929
2318
|
if (methodName === "catch") {
|
|
1930
2319
|
return true;
|
|
@@ -2166,10 +2555,10 @@ var tooManyForLoopsRule = createFunctionStatementCountRule({
|
|
|
2166
2555
|
// src/rules/too-many-ifs.rule.ts
|
|
2167
2556
|
var tooManyIfsRule = createFunctionStatementCountRule({
|
|
2168
2557
|
id: "too-many-ifs",
|
|
2169
|
-
description: 'Detecta fun\xE7\xF5es com muitos "if" (incluindo "else if")',
|
|
2170
|
-
statementTypes: /* @__PURE__ */ new Set(["IfStatement"]),
|
|
2558
|
+
description: 'Detecta fun\xE7\xF5es com muitos "if" ou tern\xE1rios (incluindo "else if")',
|
|
2559
|
+
statementTypes: /* @__PURE__ */ new Set(["IfStatement", "ConditionalExpression"]),
|
|
2171
2560
|
maxCount: 2,
|
|
2172
|
-
unitLabel: "declara\xE7\xF5es if (incluindo else if)"
|
|
2561
|
+
unitLabel: "declara\xE7\xF5es if e express\xF5es tern\xE1rias (incluindo else if)"
|
|
2173
2562
|
});
|
|
2174
2563
|
|
|
2175
2564
|
// src/rules/too-many-switch-cases.rule.ts
|
|
@@ -2308,12 +2697,25 @@ var weakCipherModeRule = {
|
|
|
2308
2697
|
|
|
2309
2698
|
// src/rules/weak-hash-algorithm.rule.ts
|
|
2310
2699
|
var WEAK_ALGORITHMS = /^(md5|sha1)$/i;
|
|
2700
|
+
var getCallExpressionCallee = (node) => {
|
|
2701
|
+
return node.type === "CallExpression" ? node.callee : void 0;
|
|
2702
|
+
};
|
|
2703
|
+
var getCallExpressionArgs = (node) => {
|
|
2704
|
+
return node.type === "CallExpression" ? node.arguments : void 0;
|
|
2705
|
+
};
|
|
2706
|
+
var getCreateHashProperty = (callee) => {
|
|
2707
|
+
return callee?.type === "MemberExpression" ? callee.property : void 0;
|
|
2708
|
+
};
|
|
2709
|
+
var isCreateHashProperty = (property) => {
|
|
2710
|
+
return property?.type === "Identifier" && property.name === "createHash";
|
|
2711
|
+
};
|
|
2712
|
+
var isWeakAlgorithmLiteral = (algorithm) => {
|
|
2713
|
+
return algorithm?.type === "StringLiteral" && WEAK_ALGORITHMS.test(algorithm.value);
|
|
2714
|
+
};
|
|
2311
2715
|
var isWeakCreateHashCall = (node) => {
|
|
2312
|
-
const
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
const algorithm = args?.[0];
|
|
2316
|
-
return property?.type === "Identifier" && property.name === "createHash" && algorithm?.type === "StringLiteral" && WEAK_ALGORITHMS.test(algorithm.value);
|
|
2716
|
+
const property = getCreateHashProperty(getCallExpressionCallee(node));
|
|
2717
|
+
const algorithm = getCallExpressionArgs(node)?.[0];
|
|
2718
|
+
return isCreateHashProperty(property) && isWeakAlgorithmLiteral(algorithm);
|
|
2317
2719
|
};
|
|
2318
2720
|
var findWeakHashLines = (filePath, content) => {
|
|
2319
2721
|
const lines = /* @__PURE__ */ new Set();
|
|
@@ -2548,7 +2950,10 @@ var parseLocalSemgrepConfig = (value) => {
|
|
|
2548
2950
|
};
|
|
2549
2951
|
var registerScanCommand = (program) => {
|
|
2550
2952
|
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)
|
|
2953
|
+
program.command("scan").description("Analisa um diret\xF3rio em busca de vulnerabilidades e problemas de qualidade").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--concurrency <n>", "limita arquivos processados em paralelo", parseConcurrency).option("--config <file>", "usa um ruleset Semgrep YAML local", parseLocalSemgrepConfig).option(
|
|
2954
|
+
"--no-deps",
|
|
2955
|
+
"n\xE3o inclui a auditoria de depend\xEAncias (npm audit + OSV.dev) neste scan \u2014 permite rodar offline"
|
|
2956
|
+
)
|
|
2552
2957
|
).action(
|
|
2553
2958
|
(path, options) => scanAndReport(path, allRules, "Scanning files...", { ...options, semgrep: true })
|
|
2554
2959
|
);
|
|
@@ -2649,6 +3054,118 @@ var registerUnsafeSqlCommand = (program) => {
|
|
|
2649
3054
|
);
|
|
2650
3055
|
};
|
|
2651
3056
|
|
|
3057
|
+
// src/engines/engine-metadata.ts
|
|
3058
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3059
|
+
import { createRequire as createRequire4 } from "module";
|
|
3060
|
+
var require5 = createRequire4(import.meta.url);
|
|
3061
|
+
var packageForPlatform2 = (platform, architecture) => {
|
|
3062
|
+
if (platform === "linux" && architecture === "x64") {
|
|
3063
|
+
return "codesentry-semgrep-linux-x64";
|
|
3064
|
+
}
|
|
3065
|
+
if (platform === "win32" && architecture === "x64") {
|
|
3066
|
+
return "codesentry-semgrep-win32-x64";
|
|
3067
|
+
}
|
|
3068
|
+
return void 0;
|
|
3069
|
+
};
|
|
3070
|
+
var unsupportedRuntimeMessage2 = (platform, architecture) => `O runtime Semgrep embutido n\xE3o est\xE1 dispon\xEDvel para ${platform}-${architecture}. O CodeSentry oferece suporte a Linux x64 e Windows x64.`;
|
|
3071
|
+
var readJsonFile = (path) => {
|
|
3072
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
3073
|
+
};
|
|
3074
|
+
var nonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
3075
|
+
var errorReason2 = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
3076
|
+
var validRuntimeLock = (lock) => lock.schemaVersion === 1 && nonEmptyString(lock.semgrepVersion) && nonEmptyString(lock.pythonVersion) && /^[a-f0-9]{64}$/i.test(lock.runtimeSha256);
|
|
3077
|
+
var validRulesetLock = (lock) => lock.schemaVersion === 1 && nonEmptyString(lock.packageVersion) && nonEmptyString(lock.ruleset) && nonEmptyString(lock.source) && /^[a-f0-9]{64}$/i.test(lock.sha256) && nonEmptyString(lock.capturedAt) && !Number.isNaN(Date.parse(lock.capturedAt)) && (lock.upstreamRevision === void 0 || nonEmptyString(lock.upstreamRevision));
|
|
3078
|
+
var loadBundledSemgrepRuntimeMetadata = (platform = process.platform, architecture = process.arch, resolveLockPath = (packageName) => require5.resolve(`${packageName}/runtime.lock.json`)) => {
|
|
3079
|
+
const packageName = packageForPlatform2(platform, architecture);
|
|
3080
|
+
if (!packageName) {
|
|
3081
|
+
throw new Error(unsupportedRuntimeMessage2(platform, architecture));
|
|
3082
|
+
}
|
|
3083
|
+
try {
|
|
3084
|
+
const lock = readJsonFile(resolveLockPath(packageName));
|
|
3085
|
+
if (!validRuntimeLock(lock)) {
|
|
3086
|
+
throw new Error("manifesto de vers\xE3o inv\xE1lido");
|
|
3087
|
+
}
|
|
3088
|
+
return {
|
|
3089
|
+
packageName,
|
|
3090
|
+
semgrepVersion: lock.semgrepVersion,
|
|
3091
|
+
pythonVersion: lock.pythonVersion,
|
|
3092
|
+
runtimeSha256: lock.runtimeSha256
|
|
3093
|
+
};
|
|
3094
|
+
} catch (error) {
|
|
3095
|
+
throw new Error(`N\xE3o foi poss\xEDvel carregar os metadados do runtime Semgrep embutido: ${errorReason2(error)}`, {
|
|
3096
|
+
cause: error
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
var loadBundledOwaspRulesetMetadata = (resolveLockPath = () => require5.resolve("codesentry-semgrep-rules/rules/ruleset.lock.json")) => {
|
|
3101
|
+
try {
|
|
3102
|
+
const lock = readJsonFile(resolveLockPath());
|
|
3103
|
+
if (!validRulesetLock(lock)) {
|
|
3104
|
+
throw new Error("manifesto de vers\xE3o inv\xE1lido");
|
|
3105
|
+
}
|
|
3106
|
+
return {
|
|
3107
|
+
packageVersion: lock.packageVersion,
|
|
3108
|
+
ruleset: lock.ruleset,
|
|
3109
|
+
source: lock.source,
|
|
3110
|
+
sha256: lock.sha256,
|
|
3111
|
+
capturedAt: lock.capturedAt,
|
|
3112
|
+
...lock.upstreamRevision ? { upstreamRevision: lock.upstreamRevision } : {}
|
|
3113
|
+
};
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
throw new Error(`N\xE3o foi poss\xEDvel carregar os metadados do ruleset OWASP embutido: ${errorReason2(error)}`, {
|
|
3116
|
+
cause: error
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
};
|
|
3120
|
+
var loadEngineMetadata = () => ({
|
|
3121
|
+
semgrep: loadBundledSemgrepRuntimeMetadata(),
|
|
3122
|
+
owaspRuleset: loadBundledOwaspRulesetMetadata()
|
|
3123
|
+
});
|
|
3124
|
+
|
|
3125
|
+
// src/commands/version/version.command.ts
|
|
3126
|
+
var buildVersionReport = (codesentryVersion, engines) => ({
|
|
3127
|
+
codesentryVersion,
|
|
3128
|
+
engines
|
|
3129
|
+
});
|
|
3130
|
+
var formatEngineVersions = (report) => {
|
|
3131
|
+
const { semgrep, owaspRuleset } = report.engines;
|
|
3132
|
+
return [
|
|
3133
|
+
`CodeSentry: ${report.codesentryVersion}`,
|
|
3134
|
+
"",
|
|
3135
|
+
"Runtime Semgrep CE",
|
|
3136
|
+
` Vers\xE3o: ${semgrep.semgrepVersion}`,
|
|
3137
|
+
` Python: ${semgrep.pythonVersion}`,
|
|
3138
|
+
` Pacote: ${semgrep.packageName}`,
|
|
3139
|
+
` SHA-256: ${semgrep.runtimeSha256}`,
|
|
3140
|
+
"",
|
|
3141
|
+
"Ruleset OWASP",
|
|
3142
|
+
` Snapshot: ${owaspRuleset.ruleset}`,
|
|
3143
|
+
` Vers\xE3o do pacote: ${owaspRuleset.packageVersion}`,
|
|
3144
|
+
` Origem: ${owaspRuleset.source}`,
|
|
3145
|
+
` Capturado em: ${owaspRuleset.capturedAt}`,
|
|
3146
|
+
...owaspRuleset.upstreamRevision ? [` Revis\xE3o upstream: ${owaspRuleset.upstreamRevision}`] : [],
|
|
3147
|
+
` SHA-256: ${owaspRuleset.sha256}`
|
|
3148
|
+
].join("\n");
|
|
3149
|
+
};
|
|
3150
|
+
var errorReason3 = (error) => error instanceof Error ? error.message : "erro desconhecido";
|
|
3151
|
+
var formatVersionOutput = (options, report) => options.json ? JSON.stringify(report, null, 2) : formatEngineVersions(report);
|
|
3152
|
+
var registerVersionCommand = (program, readPackageVersion2, readEngineMetadata = loadEngineMetadata) => {
|
|
3153
|
+
program.command("version").description("Exibe a vers\xE3o do CodeSentry e dos motores embutidos").option("--engines", "inclui vers\xF5es e proveni\xEAncia do Semgrep e ruleset OWASP").option("--json", "emite o relat\xF3rio de vers\xF5es em JSON").action((options) => {
|
|
3154
|
+
const codesentryVersion = readPackageVersion2();
|
|
3155
|
+
if (!options.engines && !options.json) {
|
|
3156
|
+
console.log(`CodeSentry: ${codesentryVersion}`);
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
try {
|
|
3160
|
+
const report = buildVersionReport(codesentryVersion, readEngineMetadata());
|
|
3161
|
+
console.log(formatVersionOutput(options, report));
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
process.exitCode = 1;
|
|
3164
|
+
console.error(`N\xE3o foi poss\xEDvel auditar as vers\xF5es dos motores: ${errorReason3(error)}`);
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
};
|
|
3168
|
+
|
|
2652
3169
|
// src/commands/weak-cipher-mode/weak-cipher-mode.command.ts
|
|
2653
3170
|
var registerWeakCipherModeCommand = (program) => {
|
|
2654
3171
|
withScanOptions(
|
|
@@ -2744,16 +3261,17 @@ var registerSecurityCommands = (program) => {
|
|
|
2744
3261
|
registerWeakCipherModeCommand(program);
|
|
2745
3262
|
registerHardcodedAuthorizationValueCommand(program);
|
|
2746
3263
|
};
|
|
2747
|
-
var
|
|
3264
|
+
var require6 = createRequire5(import.meta.url);
|
|
2748
3265
|
var readPackageVersion = () => {
|
|
2749
|
-
const packageJsonPath =
|
|
2750
|
-
const { version } = JSON.parse(
|
|
3266
|
+
const packageJsonPath = require6.resolve("../package.json");
|
|
3267
|
+
const { version } = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
2751
3268
|
return version;
|
|
2752
3269
|
};
|
|
2753
3270
|
var createCli = () => {
|
|
2754
3271
|
printBanner();
|
|
2755
3272
|
const program = new Command().name("codesentry").description("CLI de verifica\xE7\xE3o de vulnerabilidades e qualidade de c\xF3digo").version(readPackageVersion()).helpCommand(false);
|
|
2756
3273
|
registerInitCommand(program);
|
|
3274
|
+
registerVersionCommand(program, readPackageVersion);
|
|
2757
3275
|
registerAnalysisCommands(program);
|
|
2758
3276
|
registerQualityCommands(program);
|
|
2759
3277
|
registerSecurityCommands(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesentry",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"workspaces": [
|
|
5
5
|
"packages/semgrep-rules"
|
|
6
6
|
],
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@clack/prompts": "^1.7.0",
|
|
49
49
|
"chalk": "^6.0.0",
|
|
50
50
|
"cli-table3": "^0.6.5",
|
|
51
|
-
"codesentry-semgrep-rules": "0.1
|
|
51
|
+
"codesentry-semgrep-rules": "0.2.1",
|
|
52
52
|
"commander": "^15.0.0",
|
|
53
53
|
"eslint": "^10.10.0",
|
|
54
54
|
"eslint-plugin-no-unsanitized": "^4.1.5",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"p-limit": "^7.3.2"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
|
-
"codesentry-semgrep-linux-x64": "0.1
|
|
63
|
-
"codesentry-semgrep-win32-x64": "0.1
|
|
62
|
+
"codesentry-semgrep-linux-x64": "0.2.1",
|
|
63
|
+
"codesentry-semgrep-win32-x64": "0.2.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^26.4.1",
|
|
@@ -70,5 +70,8 @@
|
|
|
70
70
|
"tsx": "^4.23.13",
|
|
71
71
|
"typescript": "^7.0.2",
|
|
72
72
|
"vitest": "^5.0.0"
|
|
73
|
+
},
|
|
74
|
+
"overrides": {
|
|
75
|
+
"esbuild": "^0.28.2"
|
|
73
76
|
}
|
|
74
77
|
}
|