codesentry 0.1.8 → 0.1.10
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 +93 -18
- package/dist/index.js +211 -200
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/logo.png" alt="Logo do CodeSentry" width="640">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# CodeSentry
|
|
2
6
|
|
|
3
7
|
CLI de verificação de vulnerabilidades e qualidade de código. O scanner
|
|
4
8
|
combina regras próprias para JavaScript/TypeScript e o ruleset OWASP do
|
|
5
9
|
Semgrep CE para as linguagens suportadas por ele.
|
|
6
10
|
|
|
11
|
+
- 🔎 **Dois motores num só comando** — regras próprias em TS/JS + Semgrep CE (OWASP Top 10) para dezenas de outras linguagens.
|
|
12
|
+
- 📦 **Uma instalação, zero fricção** — `npm install -g codesentry` e pronto: sem Python, Docker, Semgrep ou conta em lugar nenhum.
|
|
13
|
+
- 🔌 **100% offline depois de instalado** — nunca consulta a Semgrep Registry nem envia métricas.
|
|
14
|
+
- 🪟🐧 **Windows e Linux nativamente** — sem WSL, sem container.
|
|
15
|
+
- 📊 **Console, JSON ou Markdown** — saída pronta tanto para ler no terminal quanto para plugar em CI.
|
|
16
|
+
|
|
7
17
|
## O que o CodeSentry faz
|
|
8
18
|
|
|
9
19
|
Rodando `codesentry scan` num projeto, dois motores de análise trabalham
|
|
@@ -25,6 +35,22 @@ motor encontrou o problema (prefixo `semgrep/` para achados do Semgrep).
|
|
|
25
35
|
Saídas disponíveis: tabela no console, JSON (`--json`) e, quando há mais
|
|
26
36
|
de 20 problemas, um relatório Markdown detalhado é gerado automaticamente.
|
|
27
37
|
|
|
38
|
+
Exemplo de saída no console:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
❯ codesentry scan .
|
|
42
|
+
✔ Scanning files...
|
|
43
|
+
┌──────────┬──────────────────────┬─────────────────┬──────┬──────────────────────────────────────────┐
|
|
44
|
+
│ Severity │ Rule │ File │ Line │ Message │
|
|
45
|
+
├──────────┼──────────────────────┼─────────────────┼──────┼──────────────────────────────────────────┤
|
|
46
|
+
│ high │ no-hardcoded-secret │ src/config.ts │ 12 │ Possível segredo hardcoded na variável... │
|
|
47
|
+
│ high │ semgrep/...shell-true│ scripts/run.py │ 8 │ subprocess com shell=True é perigoso... │
|
|
48
|
+
│ medium │ jwt-no-expiration │ src/auth.ts │ 34 │ Token JWT assinado sem "expiresIn"... │
|
|
49
|
+
└──────────┴──────────────────────┴─────────────────┴──────┴──────────────────────────────────────────┘
|
|
50
|
+
|
|
51
|
+
3 problema(s) encontrado(s) em 87 arquivo(s) (4213ms). CodeSentry: 87 JS/TS; Semgrep: 64 arquivo(s).
|
|
52
|
+
```
|
|
53
|
+
|
|
28
54
|
## Como funciona por baixo dos panos
|
|
29
55
|
|
|
30
56
|
O ponto central do design é **zero fricção de instalação**: o usuário final
|
|
@@ -42,20 +68,43 @@ O racional completo está na [ADR 0004](docs/adr/0004-bundled-semgrep-runtime.md
|
|
|
42
68
|
|
|
43
69
|
## Instalação
|
|
44
70
|
|
|
45
|
-
|
|
71
|
+
Pré-requisito único: [Node.js](https://nodejs.org) 22.12 ou mais recente
|
|
72
|
+
(`node --version` para conferir). Nenhum outro requisito — não precisa
|
|
73
|
+
instalar Python, Docker, Semgrep, criar conta ou autenticar em nada.
|
|
74
|
+
|
|
75
|
+
### Windows
|
|
76
|
+
|
|
77
|
+
No PowerShell ou no Prompt de Comando (não precisa de WSL):
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
npm install -g codesentry
|
|
81
|
+
codesentry scan .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Linux
|
|
46
85
|
|
|
47
86
|
```bash
|
|
48
87
|
npm install -g codesentry
|
|
88
|
+
codesentry scan .
|
|
49
89
|
```
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
Em ambos os casos, o `npm install` já resolve automaticamente o pacote de
|
|
92
|
+
runtime compatível com a sua plataforma (Semgrep CE + Python portátil) como
|
|
93
|
+
dependência opcional — é isso que faz `codesentry scan` funcionar com
|
|
94
|
+
cobertura OWASP completa sem nenhuma instalação manual. Plataformas com
|
|
95
|
+
runtime publicado hoje: Linux x64 e Windows x64. Os pacotes têm dezenas de
|
|
96
|
+
MB porque incluem esse runtime embutido; essa é a troca para o scan
|
|
97
|
+
funcionar 100% offline depois de instalado.
|
|
98
|
+
|
|
99
|
+
Se `codesentry` não for encontrado no terminal depois de instalado
|
|
100
|
+
globalmente, feche e reabra o terminal (ou rode `npx codesentry scan .`) —
|
|
101
|
+
alguns terminais não recarregam o PATH do npm automaticamente na mesma
|
|
102
|
+
sessão.
|
|
54
103
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
104
|
+
### A partir do código-fonte (desenvolvimento)
|
|
105
|
+
|
|
106
|
+
Para rodar a partir do repositório clonado, em vez do pacote publicado, use
|
|
107
|
+
`npm link`:
|
|
59
108
|
|
|
60
109
|
```bash
|
|
61
110
|
git clone git@github.com:Ivan-ReisDev/code-sentry.git
|
|
@@ -68,6 +117,16 @@ npm link
|
|
|
68
117
|
Depois disso, o comando `codesentry` fica disponível em qualquer
|
|
69
118
|
diretório do seu terminal.
|
|
70
119
|
|
|
120
|
+
> **Atenção:** `npx codesentry` rodado de dentro deste repositório clonado
|
|
121
|
+
> executa o `dist/index.js` local (o `package.json` daqui se chama
|
|
122
|
+
> `codesentry`, e o `npx` prioriza isso sobre a instalação global/publicada).
|
|
123
|
+
> O workspace de desenvolvimento sempre tem o ruleset do Semgrep vazio por
|
|
124
|
+
> design (populado só via `npm run prepare:owasp-rules` ou durante a release —
|
|
125
|
+
> ver [ADR 0004](docs/adr/0004-bundled-semgrep-runtime.md)), então o scan
|
|
126
|
+
> roda sem erro mas sempre reporta zero arquivos analisados pelo Semgrep.
|
|
127
|
+
> Para testar o pacote publicado de verdade, rode `codesentry scan` (sem
|
|
128
|
+
> `npx`) fora deste diretório.
|
|
129
|
+
|
|
71
130
|
## Uso
|
|
72
131
|
|
|
73
132
|
### `codesentry scan [path]`
|
|
@@ -99,31 +158,47 @@ explicitamente em vez de declarar uma análise parcial como completa.
|
|
|
99
158
|
|
|
100
159
|
### `codesentry rules`
|
|
101
160
|
|
|
102
|
-
Lista as regras de análise disponíveis.
|
|
161
|
+
Lista as regras de análise disponíveis (id e descrição de cada uma).
|
|
103
162
|
|
|
104
163
|
```bash
|
|
105
164
|
codesentry rules
|
|
106
165
|
```
|
|
107
166
|
|
|
108
|
-
### `codesentry
|
|
167
|
+
### `codesentry help`
|
|
109
168
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
também roda automaticamente como parte do `codesentry scan`.
|
|
169
|
+
Lista **todos** os comandos disponíveis, sempre atualizada — inclui tanto
|
|
170
|
+
os comandos gerais quanto os individuais listados a seguir.
|
|
113
171
|
|
|
114
172
|
```bash
|
|
115
|
-
codesentry
|
|
116
|
-
codesentry long-functions ./src --json
|
|
173
|
+
codesentry help
|
|
117
174
|
```
|
|
118
175
|
|
|
119
|
-
###
|
|
176
|
+
### Comandos individuais por regra
|
|
120
177
|
|
|
121
|
-
|
|
178
|
+
Cada regra nativa também tem um comando próprio, que roda **só ela** sobre
|
|
179
|
+
um diretório — útil para focar em um tipo de problema específico sem esperar
|
|
180
|
+
o scan completo (e sem o Semgrep, que só roda como parte de `scan`). Todos
|
|
181
|
+
seguem o mesmo formato:
|
|
122
182
|
|
|
123
183
|
```bash
|
|
124
|
-
codesentry
|
|
184
|
+
codesentry <comando> [path] [--json]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Alguns exemplos:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
codesentry long-functions . # funções com mais de 30 linhas
|
|
191
|
+
codesentry no-eval ./src # uso de eval()
|
|
192
|
+
codesentry xss ./src --json # possíveis XSS (innerHTML, document.write, dangerouslySetInnerHTML)
|
|
193
|
+
codesentry unsafe-sql ./src # SQL injection por concatenação
|
|
194
|
+
codesentry command-injection ./src # child_process com entrada não sanitizada
|
|
195
|
+
codesentry weak-hash-algorithm ./src # uso de MD5/SHA-1 para hashing sensível
|
|
196
|
+
codesentry dependency-audit . # `npm audit` das dependências do projeto
|
|
125
197
|
```
|
|
126
198
|
|
|
199
|
+
A lista completa (30+ comandos, um por regra) sai de `codesentry help` —
|
|
200
|
+
mantê-la sempre em sincronia aqui manualmente não seria viável.
|
|
201
|
+
|
|
127
202
|
### `codesentry init`
|
|
128
203
|
|
|
129
204
|
Assistente interativo para configurar o CodeSentry no projeto atual.
|
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) => {
|
|
@@ -170,17 +175,12 @@ var SEVERITY_COLOR = {
|
|
|
170
175
|
high: (text2) => chalk.red(text2),
|
|
171
176
|
critical: (text2) => chalk.bgRed.white(text2)
|
|
172
177
|
};
|
|
173
|
-
var
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const table = new Table({
|
|
182
|
-
head: ["Severity", "Rule", "File", "Line", "Message"]
|
|
183
|
-
});
|
|
178
|
+
var coverageText = (result) => result.engines?.semgrep === void 0 ? "" : ` CodeSentry: ${result.engines.codesentry ?? result.scannedFiles} JS/TS; Semgrep: ${result.engines.semgrep} arquivo(s).`;
|
|
179
|
+
var printCleanReport = (result, coverage) => {
|
|
180
|
+
console.log(chalk.green(`Nenhum problema encontrado (${result.scannedFiles} arquivos analisados).${coverage}`));
|
|
181
|
+
};
|
|
182
|
+
var findingsTable = (result) => {
|
|
183
|
+
const table = new Table({ head: ["Severity", "Rule", "File", "Line", "Message"] });
|
|
184
184
|
for (const finding of result.findings) {
|
|
185
185
|
const colorize = SEVERITY_COLOR[finding.severity];
|
|
186
186
|
table.push([
|
|
@@ -191,7 +191,10 @@ var printConsoleReport = (result) => {
|
|
|
191
191
|
finding.message
|
|
192
192
|
]);
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
return table;
|
|
195
|
+
};
|
|
196
|
+
var printFindingsReport = (result, coverage) => {
|
|
197
|
+
console.log(findingsTable(result).toString());
|
|
195
198
|
console.log(
|
|
196
199
|
chalk.bold(
|
|
197
200
|
`
|
|
@@ -199,6 +202,14 @@ ${result.findings.length} problema(s) encontrado(s) em ${result.scannedFiles} ar
|
|
|
199
202
|
)
|
|
200
203
|
);
|
|
201
204
|
};
|
|
205
|
+
var printConsoleReport = (result) => {
|
|
206
|
+
const coverage = coverageText(result);
|
|
207
|
+
if (result.findings.length === 0) {
|
|
208
|
+
printCleanReport(result, coverage);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
printFindingsReport(result, coverage);
|
|
212
|
+
};
|
|
202
213
|
|
|
203
214
|
// src/reporters/json.reporter.ts
|
|
204
215
|
var toJsonReport = (result) => {
|
|
@@ -207,12 +218,13 @@ var toJsonReport = (result) => {
|
|
|
207
218
|
|
|
208
219
|
// src/reporters/markdown.reporter.ts
|
|
209
220
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low"];
|
|
210
|
-
var SEVERITY_LABEL =
|
|
211
|
-
critical
|
|
212
|
-
high
|
|
213
|
-
medium
|
|
214
|
-
low
|
|
215
|
-
|
|
221
|
+
var SEVERITY_LABEL = /* @__PURE__ */ new Map([
|
|
222
|
+
["critical", "Critical"],
|
|
223
|
+
["high", "High"],
|
|
224
|
+
["medium", "Medium"],
|
|
225
|
+
["low", "Low"]
|
|
226
|
+
]);
|
|
227
|
+
var severityLabel = (severity) => SEVERITY_LABEL.get(severity) ?? severity;
|
|
216
228
|
var escapeCell = (text2) => text2.replaceAll("|", "\\|");
|
|
217
229
|
var groupBy = (items, keyOf) => {
|
|
218
230
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -222,7 +234,7 @@ var groupBy = (items, keyOf) => {
|
|
|
222
234
|
}
|
|
223
235
|
return map;
|
|
224
236
|
};
|
|
225
|
-
var
|
|
237
|
+
var findingsTable2 = (findings) => {
|
|
226
238
|
const sorted = [...findings].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
227
239
|
const lines = ["| Arquivo | Linha | Mensagem |", "| --- | --- | --- |"];
|
|
228
240
|
for (const f of sorted) {
|
|
@@ -231,11 +243,11 @@ var findingsTable = (findings) => {
|
|
|
231
243
|
return lines;
|
|
232
244
|
};
|
|
233
245
|
var severitySection = (severity, findings) => {
|
|
234
|
-
const lines = [`## ${
|
|
246
|
+
const lines = [`## ${severityLabel(severity)} (${findings.length})`, ""];
|
|
235
247
|
const byRule = groupBy(findings, (f) => f.ruleId);
|
|
236
248
|
for (const ruleId of [...byRule.keys()].sort()) {
|
|
237
249
|
const ruleFindings = byRule.get(ruleId) ?? [];
|
|
238
|
-
lines.push(`### ${ruleId} (${ruleFindings.length})`, "", ...
|
|
250
|
+
lines.push(`### ${ruleId} (${ruleFindings.length})`, "", ...findingsTable2(ruleFindings), "");
|
|
239
251
|
}
|
|
240
252
|
return lines;
|
|
241
253
|
};
|
|
@@ -254,7 +266,7 @@ var reportHeader = (result, generatedAt) => [
|
|
|
254
266
|
var summaryTable = (bySeverity) => {
|
|
255
267
|
const lines = ["## Resumo por severidade", "", "| Severidade | Quantidade |", "| --- | --- |"];
|
|
256
268
|
for (const severity of SEVERITY_ORDER) {
|
|
257
|
-
lines.push(`| ${
|
|
269
|
+
lines.push(`| ${severityLabel(severity)} | ${(bySeverity.get(severity) ?? []).length} |`);
|
|
258
270
|
}
|
|
259
271
|
lines.push("");
|
|
260
272
|
return lines;
|
|
@@ -295,20 +307,21 @@ import { availableParallelism } from "os";
|
|
|
295
307
|
import { readdir } from "fs/promises";
|
|
296
308
|
import { join } from "path";
|
|
297
309
|
var SCANNABLE_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx"];
|
|
298
|
-
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"]);
|
|
310
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "tests"]);
|
|
299
311
|
var isScannable = (fileName) => SCANNABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
312
|
+
var filesFromEntry = async (currentDir, entry) => {
|
|
313
|
+
if (entry.isDirectory()) {
|
|
314
|
+
return IGNORED_DIRS.has(entry.name) ? [] : walk(join(currentDir, entry.name));
|
|
315
|
+
}
|
|
316
|
+
return entry.isFile() && isScannable(entry.name) ? [join(currentDir, entry.name)] : [];
|
|
317
|
+
};
|
|
300
318
|
var walk = async (currentDir) => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
}
|
|
319
|
+
try {
|
|
320
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
321
|
+
return (await Promise.all(entries.map((entry) => filesFromEntry(currentDir, entry)))).flat();
|
|
322
|
+
} catch (error) {
|
|
323
|
+
throw new Error(`N\xE3o foi poss\xEDvel listar os arquivos em "${currentDir}".`, { cause: error });
|
|
310
324
|
}
|
|
311
|
-
return files;
|
|
312
325
|
};
|
|
313
326
|
var findFiles = async (targetDir) => {
|
|
314
327
|
try {
|
|
@@ -342,21 +355,30 @@ var readFileContent = async (filePath) => {
|
|
|
342
355
|
throw new Error(`N\xE3o foi poss\xEDvel ler o arquivo "${filePath}".`, { cause: error });
|
|
343
356
|
}
|
|
344
357
|
};
|
|
358
|
+
var checkRule = (rule, filePath, content) => {
|
|
359
|
+
try {
|
|
360
|
+
return { findings: rule.check(filePath, content) };
|
|
361
|
+
} catch (error) {
|
|
362
|
+
return { findings: [], error };
|
|
363
|
+
}
|
|
364
|
+
};
|
|
345
365
|
var scanFile = async (filePath, rules) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (!hasParseError) {
|
|
354
|
-
findings.push(parseErrorFinding(filePath, error));
|
|
366
|
+
try {
|
|
367
|
+
const content = await readFileContent(filePath);
|
|
368
|
+
const findings = [];
|
|
369
|
+
let hasParseError = false;
|
|
370
|
+
for (const rule of rules) {
|
|
371
|
+
const result = checkRule(rule, filePath, content);
|
|
372
|
+
findings.push(...result.findings);
|
|
373
|
+
if (result.error && !hasParseError) {
|
|
374
|
+
findings.push(parseErrorFinding(filePath, result.error));
|
|
355
375
|
hasParseError = true;
|
|
356
376
|
}
|
|
357
377
|
}
|
|
378
|
+
return findings;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
throw new Error(`N\xE3o foi poss\xEDvel analisar o arquivo "${filePath}".`, { cause: error });
|
|
358
381
|
}
|
|
359
|
-
return findings;
|
|
360
382
|
};
|
|
361
383
|
var runScan = async (targetDir, rules, concurrency = DEFAULT_SCAN_CONCURRENCY) => {
|
|
362
384
|
try {
|
|
@@ -460,50 +482,65 @@ var mapSemgrepReportToFindings = (report) => report.results.map((finding) => ({
|
|
|
460
482
|
severity: SEMGREP_SEVERITIES[finding.extra.severity] ?? "medium"
|
|
461
483
|
}));
|
|
462
484
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
const
|
|
485
|
+
var semgrepArgs = (ruleset) => [
|
|
486
|
+
"scan",
|
|
487
|
+
"--config",
|
|
488
|
+
ruleset,
|
|
489
|
+
"--metrics=off",
|
|
490
|
+
"--json",
|
|
491
|
+
"--quiet",
|
|
492
|
+
"--exclude",
|
|
493
|
+
"node_modules",
|
|
494
|
+
"--exclude",
|
|
495
|
+
".git",
|
|
496
|
+
"--exclude",
|
|
497
|
+
"dist",
|
|
498
|
+
"--exclude",
|
|
499
|
+
".next",
|
|
500
|
+
"--exclude",
|
|
501
|
+
"tests",
|
|
502
|
+
// Não repetir targetDir aqui: o processo já roda com cwd = targetDir,
|
|
503
|
+
// então o alvo relativo a esse cwd é o diretório atual.
|
|
504
|
+
"."
|
|
505
|
+
];
|
|
506
|
+
var semgrepEnvironment = (runtime) => {
|
|
507
|
+
const semgrepDir = dirname2(runtime.semgrep);
|
|
508
|
+
const systemPathFallback = process.platform === "win32" ? "C:\\Windows\\System32;C:\\Windows" : "/usr/local/bin:/usr/bin:/bin";
|
|
509
|
+
return {
|
|
487
510
|
...process.env,
|
|
488
|
-
PATH: `${dirname2(
|
|
511
|
+
PATH: `${semgrepDir}${delimiter}${dirname2(semgrepDir)}${delimiter}${process.env.PATH ?? ""}${delimiter}${systemPathFallback}`
|
|
489
512
|
};
|
|
513
|
+
};
|
|
514
|
+
var executeSemgrep = async (targetDir, runtime, ruleset, execute) => {
|
|
490
515
|
try {
|
|
491
|
-
|
|
516
|
+
const { stdout } = await execute(runtime.semgrep, semgrepArgs(ruleset), {
|
|
517
|
+
cwd: targetDir,
|
|
518
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
519
|
+
env: semgrepEnvironment(runtime)
|
|
520
|
+
});
|
|
521
|
+
return stdout;
|
|
492
522
|
} catch (error) {
|
|
493
523
|
const output = outputFromError(error);
|
|
494
|
-
if (
|
|
495
|
-
|
|
524
|
+
if (output) {
|
|
525
|
+
return output;
|
|
496
526
|
}
|
|
497
|
-
|
|
527
|
+
throw new Error("N\xE3o foi poss\xEDvel executar o Semgrep embutido.", { cause: error });
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
var runBundledSemgrep = async (targetDir, runtime = resolveBundledSemgrepRuntime(), ruleset = resolveBundledSemgrepRuleset(), execute = execFileAsync) => {
|
|
531
|
+
const startedAt = Date.now();
|
|
532
|
+
try {
|
|
533
|
+
const report = parseSemgrepReport(await executeSemgrep(targetDir, runtime, ruleset, execute));
|
|
534
|
+
const scannedFiles = report.paths?.scanned.length ?? 0;
|
|
535
|
+
return {
|
|
536
|
+
scannedFiles,
|
|
537
|
+
findings: mapSemgrepReportToFindings(report),
|
|
538
|
+
durationMs: Date.now() - startedAt,
|
|
539
|
+
engines: { semgrep: scannedFiles }
|
|
540
|
+
};
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new Error("N\xE3o foi poss\xEDvel processar o resultado do Semgrep.", { cause: error });
|
|
498
543
|
}
|
|
499
|
-
const report = parseSemgrepReport(stdout);
|
|
500
|
-
const scannedFiles = report.paths?.scanned.length ?? 0;
|
|
501
|
-
return {
|
|
502
|
-
scannedFiles,
|
|
503
|
-
findings: mapSemgrepReportToFindings(report),
|
|
504
|
-
durationMs: Date.now() - startedAt,
|
|
505
|
-
engines: { semgrep: scannedFiles }
|
|
506
|
-
};
|
|
507
544
|
};
|
|
508
545
|
|
|
509
546
|
// src/commands/scan/report-filename.ts
|
|
@@ -539,22 +576,28 @@ Relat\xF3rio detalhado gerado em: ${filePath}`));
|
|
|
539
576
|
console.error(chalk2.red(`N\xE3o foi poss\xEDvel gerar o relat\xF3rio Markdown: ${errorMessage2(error)}`));
|
|
540
577
|
}
|
|
541
578
|
};
|
|
579
|
+
var runScanEngines = async (path, rules, options) => {
|
|
580
|
+
try {
|
|
581
|
+
const nativeResult = await runScan(path, rules, options.concurrency);
|
|
582
|
+
if (!options.semgrep) {
|
|
583
|
+
return nativeResult;
|
|
584
|
+
}
|
|
585
|
+
const semgrepResult = await runBundledSemgrep(path, void 0, options.config);
|
|
586
|
+
return mergeScanResults(nativeResult, semgrepResult);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
throw new Error(`Falha durante a an\xE1lise: ${errorMessage2(error)}`, { cause: error });
|
|
589
|
+
}
|
|
590
|
+
};
|
|
542
591
|
var createScanTasks = (path, rules, taskTitle, options, onResult) => new Listr([
|
|
543
592
|
{
|
|
544
593
|
title: taskTitle,
|
|
545
|
-
task:
|
|
546
|
-
|
|
547
|
-
onResult(
|
|
548
|
-
|
|
594
|
+
task: async () => {
|
|
595
|
+
try {
|
|
596
|
+
onResult(await runScanEngines(path, rules, options));
|
|
597
|
+
} catch (error) {
|
|
598
|
+
throw error;
|
|
549
599
|
}
|
|
550
|
-
|
|
551
|
-
onResult(mergeScanResults(nativeResult, semgrepResult));
|
|
552
|
-
});
|
|
553
|
-
}).catch((error) => {
|
|
554
|
-
throw new Error(`Falha durante a an\xE1lise: ${errorMessage2(error)}`, {
|
|
555
|
-
cause: error
|
|
556
|
-
});
|
|
557
|
-
})
|
|
600
|
+
}
|
|
558
601
|
}
|
|
559
602
|
]);
|
|
560
603
|
var scanAndReport = async (path, rules, taskTitle, options) => {
|
|
@@ -883,14 +926,14 @@ var nodeName = (node) => {
|
|
|
883
926
|
}
|
|
884
927
|
return nameValue(node) ?? literalValue(node) ?? nodeName(node.id);
|
|
885
928
|
};
|
|
886
|
-
var
|
|
887
|
-
VariableDeclarator
|
|
888
|
-
ObjectProperty
|
|
889
|
-
AssignmentExpression
|
|
890
|
-
|
|
929
|
+
var PARENT_NAME_READERS = /* @__PURE__ */ new Map([
|
|
930
|
+
["VariableDeclarator", (parent) => parent.id],
|
|
931
|
+
["ObjectProperty", (parent) => parent.key],
|
|
932
|
+
["AssignmentExpression", (parent) => parent.left]
|
|
933
|
+
]);
|
|
891
934
|
var parentFunctionName = (parent) => {
|
|
892
|
-
const
|
|
893
|
-
return
|
|
935
|
+
const readName = parent ? PARENT_NAME_READERS.get(parent.type) : void 0;
|
|
936
|
+
return readName && parent ? nodeName(readName(parent)) : void 0;
|
|
894
937
|
};
|
|
895
938
|
var isConstructor = (node) => {
|
|
896
939
|
return node.type === "ClassMethod" && node.kind === "constructor";
|
|
@@ -1072,20 +1115,11 @@ var calleeName = (callee) => {
|
|
|
1072
1115
|
return void 0;
|
|
1073
1116
|
};
|
|
1074
1117
|
var isDateNowOrGetTimeCall = (node) => {
|
|
1075
|
-
if (node.type !== "CallExpression") {
|
|
1076
|
-
return false;
|
|
1077
|
-
}
|
|
1078
1118
|
const callee = node.callee;
|
|
1079
|
-
|
|
1080
|
-
return false;
|
|
1081
|
-
}
|
|
1082
|
-
const property = callee.property;
|
|
1119
|
+
const property = callee?.property;
|
|
1083
1120
|
const propertyName = property?.type === "Identifier" ? property.name : void 0;
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
const object = callee.object;
|
|
1088
|
-
return object?.type === "Identifier" && object.name === "Date" && propertyName === "now";
|
|
1121
|
+
const object = callee?.object;
|
|
1122
|
+
return node.type === "CallExpression" && callee?.type === "MemberExpression" && (propertyName === "getTime" || object?.type === "Identifier" && object.name === "Date" && propertyName === "now");
|
|
1089
1123
|
};
|
|
1090
1124
|
var TIMESTAMP_NAME_PATTERN = /timestamp|^now$/i;
|
|
1091
1125
|
var containsPredictableTimestamp = (node) => {
|
|
@@ -1207,13 +1241,12 @@ var analyzeFile = (sourceFile) => {
|
|
|
1207
1241
|
hasSignatureVerification: false
|
|
1208
1242
|
};
|
|
1209
1243
|
visitSourceNodes(sourceFile, (node) => {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
analysis.hasJsonParse = true;
|
|
1214
|
-
} else if (isSignatureVerificationCall(node)) {
|
|
1215
|
-
analysis.hasSignatureVerification = true;
|
|
1244
|
+
const base64DecodeLine = isBase64DecodeCall(node) ? node.loc?.start.line : void 0;
|
|
1245
|
+
if (base64DecodeLine !== void 0) {
|
|
1246
|
+
analysis.base64DecodeLines.push(base64DecodeLine);
|
|
1216
1247
|
}
|
|
1248
|
+
analysis.hasJsonParse ||= isJsonParseCall(node);
|
|
1249
|
+
analysis.hasSignatureVerification ||= isSignatureVerificationCall(node);
|
|
1217
1250
|
});
|
|
1218
1251
|
return analysis;
|
|
1219
1252
|
};
|
|
@@ -1241,14 +1274,14 @@ var jwtDecodeWithoutVerifyRule = {
|
|
|
1241
1274
|
|
|
1242
1275
|
// src/commands/jwt-decode-without-verify/jwt-decode-without-verify.command.ts
|
|
1243
1276
|
var registerJwtDecodeWithoutVerifyCommand = (program) => {
|
|
1244
|
-
program.command("jwt-decode-without-verify").description("Detecta decodifica\xE7\xE3o manual de um token (base64 + JSON.parse) sem verificar a assinatura").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
1245
|
-
|
|
1277
|
+
program.command("jwt-decode-without-verify").description("Detecta decodifica\xE7\xE3o manual de um token (base64 + JSON.parse) sem verificar a assinatura").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
1278
|
+
(path, options) => scanAndReport(
|
|
1246
1279
|
path,
|
|
1247
1280
|
[jwtDecodeWithoutVerifyRule],
|
|
1248
1281
|
"Checking JWT decode without verification...",
|
|
1249
1282
|
options
|
|
1250
|
-
)
|
|
1251
|
-
|
|
1283
|
+
)
|
|
1284
|
+
);
|
|
1252
1285
|
};
|
|
1253
1286
|
|
|
1254
1287
|
// src/rules/jwt-no-expiration.rule.ts
|
|
@@ -1609,9 +1642,9 @@ var publicEnvVarSecretRule = {
|
|
|
1609
1642
|
|
|
1610
1643
|
// src/commands/public-env-var-secret/public-env-var-secret.command.ts
|
|
1611
1644
|
var registerPublicEnvVarSecretCommand = (program) => {
|
|
1612
|
-
program.command("public-env-var-secret").description("Detecta uma vari\xE1vel de ambiente p\xFAblica (NEXT_PUBLIC_/VITE_/REACT_APP_) com nome de segredo").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
1613
|
-
|
|
1614
|
-
|
|
1645
|
+
program.command("public-env-var-secret").description("Detecta uma vari\xE1vel de ambiente p\xFAblica (NEXT_PUBLIC_/VITE_/REACT_APP_) com nome de segredo").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
1646
|
+
(path, options) => scanAndReport(path, [publicEnvVarSecretRule], "Checking public env var secrets...", options)
|
|
1647
|
+
);
|
|
1615
1648
|
};
|
|
1616
1649
|
|
|
1617
1650
|
// src/commands/rules/rules.command.ts
|
|
@@ -1759,15 +1792,15 @@ var chainReachesCatch = (thenCall, ancestors) => {
|
|
|
1759
1792
|
let currentCall = thenCall;
|
|
1760
1793
|
let i = 0;
|
|
1761
1794
|
while (i < ancestors.length) {
|
|
1762
|
-
const member = ancestors
|
|
1763
|
-
const nextCall = ancestors
|
|
1764
|
-
const isChainMember = member
|
|
1795
|
+
const member = ancestors.at(i);
|
|
1796
|
+
const nextCall = ancestors.at(i + 1);
|
|
1797
|
+
const isChainMember = member !== void 0 && member.type === "MemberExpression" && member.object === currentCall && isCallOf(nextCall, member);
|
|
1765
1798
|
const methodName = isChainMember ? memberPropertyName(member) : void 0;
|
|
1766
1799
|
const continuesChain = methodName === "then" || methodName === "finally";
|
|
1767
1800
|
if (methodName === "catch") {
|
|
1768
1801
|
return true;
|
|
1769
1802
|
}
|
|
1770
|
-
if (!continuesChain) {
|
|
1803
|
+
if (!nextCall || !continuesChain) {
|
|
1771
1804
|
return false;
|
|
1772
1805
|
}
|
|
1773
1806
|
currentCall = nextCall;
|
|
@@ -1863,11 +1896,14 @@ var RULES = {
|
|
|
1863
1896
|
"security/detect-new-buffer": "error"
|
|
1864
1897
|
};
|
|
1865
1898
|
var PLUGINS = { security: securityPlugin };
|
|
1899
|
+
var suppressionMarker = (ruleId) => `codesentry-disable-next-line ${ruleId}`;
|
|
1900
|
+
var isSuppressed = (contentLines, finding) => contentLines.at(finding.line - 2)?.includes(suppressionMarker(finding.ruleId)) ?? false;
|
|
1866
1901
|
var securityLintRule = {
|
|
1867
1902
|
id: "security-lint",
|
|
1868
1903
|
description: "Detecta padr\xF5es de seguran\xE7a gen\xE9ricos (object injection, regex n\xE3o literal, fs n\xE3o literal, etc.) via eslint-plugin-security",
|
|
1869
1904
|
check(filePath, content) {
|
|
1870
|
-
|
|
1905
|
+
const contentLines = content.split("\n");
|
|
1906
|
+
return runEslintRules(filePath, content, RULES, PLUGINS).filter((finding) => !isSuppressed(contentLines, finding)).map((finding) => ({
|
|
1871
1907
|
ruleId: finding.ruleId,
|
|
1872
1908
|
message: `Padr\xE3o inseguro detectado (${finding.ruleId}): ${finding.message}`,
|
|
1873
1909
|
file: filePath,
|
|
@@ -1905,14 +1941,9 @@ var propertyKeyName = (property) => {
|
|
|
1905
1941
|
var argumentContainsSensitiveData = (argument) => {
|
|
1906
1942
|
let found = false;
|
|
1907
1943
|
visitSourceNodes(argument, (node) => {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
const keyName = propertyKeyName(node);
|
|
1912
|
-
if (keyName && SENSITIVE_NAME_PATTERN2.test(keyName)) {
|
|
1913
|
-
found = true;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1944
|
+
const isSensitiveIdentifier = node.type === "Identifier" && SENSITIVE_NAME_PATTERN2.test(node.name);
|
|
1945
|
+
const keyName = node.type === "ObjectProperty" ? propertyKeyName(node) : void 0;
|
|
1946
|
+
found ||= isSensitiveIdentifier || keyName !== void 0 && SENSITIVE_NAME_PATTERN2.test(keyName);
|
|
1916
1947
|
});
|
|
1917
1948
|
return found;
|
|
1918
1949
|
};
|
|
@@ -2197,29 +2228,18 @@ var RULES2 = {
|
|
|
2197
2228
|
"no-unsanitized/method": "error"
|
|
2198
2229
|
};
|
|
2199
2230
|
var PLUGINS2 = { "no-unsanitized": noUnsanitizedPlugin };
|
|
2231
|
+
var isHtmlProperty = (property) => {
|
|
2232
|
+
const key = property.key;
|
|
2233
|
+
return key?.type === "Identifier" && key.name === "__html";
|
|
2234
|
+
};
|
|
2200
2235
|
var isDangerouslySetInnerHtmlWithDynamicValue = (node) => {
|
|
2201
|
-
if (node.type !== "JSXAttribute") {
|
|
2202
|
-
return false;
|
|
2203
|
-
}
|
|
2204
2236
|
const name = node.name;
|
|
2205
|
-
if (name?.type !== "JSXIdentifier" || name.name !== "dangerouslySetInnerHTML") {
|
|
2206
|
-
return false;
|
|
2207
|
-
}
|
|
2208
2237
|
const value = node.value;
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
const expression = value.expression;
|
|
2213
|
-
if (expression?.type !== "ObjectExpression") {
|
|
2214
|
-
return false;
|
|
2215
|
-
}
|
|
2216
|
-
const properties = expression.properties;
|
|
2217
|
-
const htmlProperty = properties?.find((property) => {
|
|
2218
|
-
const key = property.key;
|
|
2219
|
-
return key?.type === "Identifier" && key.name === "__html";
|
|
2220
|
-
});
|
|
2238
|
+
const expression = value?.expression;
|
|
2239
|
+
const properties = expression?.properties;
|
|
2240
|
+
const htmlProperty = properties?.find(isHtmlProperty);
|
|
2221
2241
|
const htmlValue = htmlProperty?.value;
|
|
2222
|
-
return htmlValue !== void 0 && htmlValue.type !== "StringLiteral";
|
|
2242
|
+
return node.type === "JSXAttribute" && name?.type === "JSXIdentifier" && name.name === "dangerouslySetInnerHTML" && value?.type === "JSXExpressionContainer" && expression?.type === "ObjectExpression" && htmlValue !== void 0 && htmlValue.type !== "StringLiteral";
|
|
2223
2243
|
};
|
|
2224
2244
|
var findDangerouslySetInnerHtmlFindings = (filePath, content) => {
|
|
2225
2245
|
const findings = [];
|
|
@@ -2272,19 +2292,10 @@ var hasRiskyOptionEnabled = (options) => {
|
|
|
2272
2292
|
}) ?? false;
|
|
2273
2293
|
};
|
|
2274
2294
|
var isUnsafeXmlParseCall = (node) => {
|
|
2275
|
-
if (node.type !== "CallExpression") {
|
|
2276
|
-
return false;
|
|
2277
|
-
}
|
|
2278
2295
|
const callee = node.callee;
|
|
2279
|
-
|
|
2280
|
-
return false;
|
|
2281
|
-
}
|
|
2282
|
-
const property = callee.property;
|
|
2283
|
-
if (property?.type !== "Identifier" || !XML_PARSE_METHOD_NAMES.has(property.name)) {
|
|
2284
|
-
return false;
|
|
2285
|
-
}
|
|
2296
|
+
const property = callee?.property;
|
|
2286
2297
|
const args = node.arguments;
|
|
2287
|
-
return hasRiskyOptionEnabled(args?.[1]);
|
|
2298
|
+
return node.type === "CallExpression" && callee?.type === "MemberExpression" && property?.type === "Identifier" && XML_PARSE_METHOD_NAMES.has(property.name) && hasRiskyOptionEnabled(args?.[1]);
|
|
2288
2299
|
};
|
|
2289
2300
|
var findUnsafeXmlParsingLines = (filePath, content) => {
|
|
2290
2301
|
const lines = /* @__PURE__ */ new Set();
|
|
@@ -2385,9 +2396,9 @@ var registerSecurityLintCommand = (program) => {
|
|
|
2385
2396
|
|
|
2386
2397
|
// src/commands/sensitive-data-in-logs/sensitive-data-in-logs.command.ts
|
|
2387
2398
|
var registerSensitiveDataInLogsCommand = (program) => {
|
|
2388
|
-
program.command("sensitive-data-in-logs").description("Detecta senhas/segredos/tokens sendo passados para chamadas de log").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2389
|
-
|
|
2390
|
-
|
|
2399
|
+
program.command("sensitive-data-in-logs").description("Detecta senhas/segredos/tokens sendo passados para chamadas de log").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2400
|
+
(path, options) => scanAndReport(path, [sensitiveDataInLogsRule], "Checking sensitive data in logs...", options)
|
|
2401
|
+
);
|
|
2391
2402
|
};
|
|
2392
2403
|
|
|
2393
2404
|
// src/commands/tls-validation-disabled/tls-validation-disabled.command.ts
|
|
@@ -2462,9 +2473,9 @@ var registerWeakHashAlgorithmCommand = (program) => {
|
|
|
2462
2473
|
var registerWeakSecretFallbackCommand = (program) => {
|
|
2463
2474
|
program.command("weak-secret-fallback").description(
|
|
2464
2475
|
"Detecta uma vari\xE1vel de ambiente de segredo/chave com um valor hardcoded como fallback (|| ou ??)"
|
|
2465
|
-
).argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2466
|
-
|
|
2467
|
-
|
|
2476
|
+
).argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2477
|
+
(path, options) => scanAndReport(path, [weakSecretFallbackRule], "Checking weak secret fallbacks...", options)
|
|
2478
|
+
);
|
|
2468
2479
|
};
|
|
2469
2480
|
|
|
2470
2481
|
// src/commands/xss/xss.command.ts
|
|
@@ -2476,9 +2487,9 @@ var registerXssCommand = (program) => {
|
|
|
2476
2487
|
|
|
2477
2488
|
// src/commands/xxe-unsafe-xml-parsing/xxe-unsafe-xml-parsing.command.ts
|
|
2478
2489
|
var registerXxeUnsafeXmlParsingCommand = (program) => {
|
|
2479
|
-
program.command("xxe-unsafe-xml-parsing").description("Detecta parsing de XML com noent/dtdload habilitados (risco de XXE)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2480
|
-
|
|
2481
|
-
|
|
2490
|
+
program.command("xxe-unsafe-xml-parsing").description("Detecta parsing de XML com noent/dtdload habilitados (risco de XXE)").argument("[path]", "diret\xF3rio a ser analisado", ".").option("--json", "exibe o resultado em JSON").action(
|
|
2491
|
+
(path, options) => scanAndReport(path, [xxeUnsafeXmlParsingRule], "Checking unsafe XML parsing...", options)
|
|
2492
|
+
);
|
|
2482
2493
|
};
|
|
2483
2494
|
|
|
2484
2495
|
// src/cli.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesentry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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.1.10",
|
|
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.1.10",
|
|
63
|
+
"codesentry-semgrep-win32-x64": "0.1.10"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^26.4.1",
|