@j3r3mcdev/scoring 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.github/workflows/ci.yml +29 -0
  2. package/.github/workflows/publish.yml +34 -0
  3. package/LICENSE +21 -0
  4. package/README.md +175 -0
  5. package/jest.config.js +11 -0
  6. package/package.json +29 -0
  7. package/src/core/__tests__/scoring-context.test.ts +47 -0
  8. package/src/core/__tests__/scoring-engine.test.ts +110 -0
  9. package/src/core/__tests__/scoring-result.test.ts +14 -0
  10. package/src/core/index.ts +8 -0
  11. package/src/core/scoring-context.ts +80 -0
  12. package/src/core/scoring-engine.ts +126 -0
  13. package/src/core/scoring-result.ts +15 -0
  14. package/src/core/scoring-types.ts +125 -0
  15. package/src/correlation/__tests__/chain-detector.test.ts +76 -0
  16. package/src/correlation/__tests__/correlator.test.ts +49 -0
  17. package/src/correlation/__tests__/event-grouper.test.ts +62 -0
  18. package/src/correlation/chain-detector.ts +99 -0
  19. package/src/correlation/correlator.ts +39 -0
  20. package/src/correlation/event-grouper.ts +47 -0
  21. package/src/correlation/index.ts +3 -0
  22. package/src/index.ts +21 -0
  23. package/src/normalizers/__tests__/dns.normalizer.test.ts +40 -0
  24. package/src/normalizers/__tests__/http.normalizer.test.ts +55 -0
  25. package/src/normalizers/__tests__/normalizer-registry.test.ts +89 -0
  26. package/src/normalizers/__tests__/waf.normalizer.test.ts +45 -0
  27. package/src/normalizers/dns.normalizer.ts +28 -0
  28. package/src/normalizers/http.normalizer.ts +53 -0
  29. package/src/normalizers/index.ts +34 -0
  30. package/src/normalizers/waf.normalizer.ts +39 -0
  31. package/src/reporters/__tests__/html-reporter.test.ts +51 -0
  32. package/src/reporters/__tests__/json-reporter.test.ts +50 -0
  33. package/src/reporters/__tests__/markdown-reporter.test.ts +75 -0
  34. package/src/reporters/__tests__/reporter-factory.test.ts +25 -0
  35. package/src/reporters/__tests__/reporters-integration.test.ts +46 -0
  36. package/src/reporters/base/BaseReporter.ts +56 -0
  37. package/src/reporters/base/ReporterTypes.ts +21 -0
  38. package/src/reporters/html/HTMLReporter.ts +240 -0
  39. package/src/reporters/index.ts +0 -0
  40. package/src/reporters/json/JSONReporter.ts +98 -0
  41. package/src/reporters/markdown/MarkdownReporter.ts +157 -0
  42. package/src/reporters/reporter-factory.ts +29 -0
  43. package/src/rules/__tests__/dns.rule.test.ts +42 -0
  44. package/src/rules/__tests__/http.rule.test.ts +46 -0
  45. package/src/rules/__tests__/lfi.rule.test.ts +42 -0
  46. package/src/rules/__tests__/path-traversal.rule.test.ts +42 -0
  47. package/src/rules/__tests__/rce.rule.test.ts +42 -0
  48. package/src/rules/__tests__/rule-registry.test.ts +40 -0
  49. package/src/rules/__tests__/ssrf.rule.test.ts +42 -0
  50. package/src/rules/__tests__/waf.rule.test.ts +40 -0
  51. package/src/rules/base-rule.ts +43 -0
  52. package/src/rules/dns.rules.ts +50 -0
  53. package/src/rules/http.rules.ts +72 -0
  54. package/src/rules/index.ts +35 -0
  55. package/src/rules/lfi.rule.ts +76 -0
  56. package/src/rules/path-transversal.rule.ts +65 -0
  57. package/src/rules/rce.rules.ts +73 -0
  58. package/src/rules/rule-registry.ts +39 -0
  59. package/src/rules/sqli.rules.ts +69 -0
  60. package/src/rules/ssrf.rules.ts +76 -0
  61. package/src/rules/waf.rules.ts +62 -0
  62. package/src/rules/xss.rules.ts +66 -0
  63. package/src/utils/chain-utils.ts +73 -0
  64. package/src/utils/date-utils.ts +80 -0
  65. package/src/utils/finding-utils.ts +97 -0
  66. package/src/utils/index.ts +6 -0
  67. package/src/utils/report-utils.ts +118 -0
  68. package/src/utils/score-utils.ts +103 -0
  69. package/src/utils/string-utils.ts +54 -0
  70. package/src.txt +0 -0
  71. package/tests/scoring-engine.test.ts +7 -0
  72. package/tsconfig.json +18 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * HTML REPORTER — Version PRO
4
+ * Génère un rapport HTML structuré, lisible et sécurisé.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ import { BaseReporter } from "../base/BaseReporter";
9
+ import { ReporterMetadata, ReporterOutput } from "../base/ReporterTypes";
10
+ import { Finding, CorrelationChain } from "../../core/scoring-types";
11
+
12
+ import {
13
+ escapeHtml,
14
+ renderHtmlTable,
15
+ renderHtmlList,
16
+ renderHtmlParagraph,
17
+ } from "../../utils/report-utils";
18
+ import { sortFindingsBySeverity } from "../../utils/finding-utils";
19
+ import { sortChains, chainSummary } from "../../utils/chain-utils";
20
+ import { scoreInfo } from "../../utils/score-utils";
21
+ import { formatIso } from "../../utils/date-utils";
22
+
23
+ function severityClass(severity: Finding["severity"]): string {
24
+ switch (severity) {
25
+ case "critical":
26
+ return "sev-critical";
27
+ case "high":
28
+ return "sev-high";
29
+ case "medium":
30
+ return "sev-medium";
31
+ case "low":
32
+ return "sev-low";
33
+ default:
34
+ return "sev-unknown";
35
+ }
36
+ }
37
+
38
+ export class HTMLReporter extends BaseReporter {
39
+ getName(): string {
40
+ return "html";
41
+ }
42
+
43
+ getExtension(): string {
44
+ return ".html";
45
+ }
46
+
47
+ generate(
48
+ findings: Finding[],
49
+ chains: CorrelationChain[],
50
+ metadata: ReporterMetadata = {},
51
+ ): ReporterOutput {
52
+ this.validateInput(findings, chains);
53
+
54
+ const { findings: preparedFindings, chains: preparedChains } =
55
+ this.prepareData(findings, chains);
56
+
57
+ const generatedAt = metadata.generatedAt ?? Date.now();
58
+ const generatedAtIso = formatIso(generatedAt);
59
+
60
+ const title = metadata.title ?? "Security Scan Report";
61
+
62
+ const summaryTable = renderHtmlTable(
63
+ ["Metric", "Value"],
64
+ [
65
+ ["Total Findings", String(preparedFindings.length)],
66
+ ["Total Chains", String(preparedChains.length)],
67
+ [
68
+ "Critical",
69
+ String(
70
+ preparedFindings.filter((f) => f.severity === "critical").length,
71
+ ),
72
+ ],
73
+ [
74
+ "High",
75
+ String(preparedFindings.filter((f) => f.severity === "high").length),
76
+ ],
77
+ [
78
+ "Medium",
79
+ String(
80
+ preparedFindings.filter((f) => f.severity === "medium").length,
81
+ ),
82
+ ],
83
+ [
84
+ "Low",
85
+ String(preparedFindings.filter((f) => f.severity === "low").length),
86
+ ],
87
+ ],
88
+ );
89
+
90
+ const findingsHtml =
91
+ preparedFindings.length === 0
92
+ ? renderHtmlParagraph("_No findings detected._")
93
+ : preparedFindings
94
+ .map((f) => {
95
+ const info = scoreInfo(f.score / 100);
96
+
97
+ const evidenceHtml =
98
+ f.evidence && f.evidence.length
99
+ ? `<h4>Evidence</h4>${renderHtmlList(
100
+ f.evidence.map((e) => String(e)),
101
+ )}`
102
+ : "";
103
+
104
+ const chainsHtml =
105
+ f.chains && f.chains.length
106
+ ? `<h4>Related Chains</h4>${renderHtmlList(
107
+ f.chains.map((c) => chainSummary(c)),
108
+ )}`
109
+ : "";
110
+
111
+ return `
112
+ <section class="finding ${severityClass(f.severity)}">
113
+ <h3>${escapeHtml(f.vulnerability.toUpperCase())}</h3>
114
+ <p><strong>Severity:</strong> ${escapeHtml(f.severity)}</p>
115
+ <p><strong>Score:</strong> ${info.normalized} (${escapeHtml(info.label)})</p>
116
+ ${f.details ? `<h4>Details</h4>${renderHtmlParagraph(f.details)}` : ""}
117
+ ${evidenceHtml}
118
+ ${chainsHtml}
119
+ </section>`;
120
+ })
121
+ .join("\n");
122
+
123
+ const chainsHtml =
124
+ preparedChains.length === 0
125
+ ? renderHtmlParagraph("_No chains detected._")
126
+ : renderHtmlList(preparedChains.map((c) => chainSummary(c)));
127
+
128
+ const html = `<!DOCTYPE html>
129
+ <html lang="en">
130
+ <head>
131
+ <meta charset="utf-8" />
132
+ <title>${escapeHtml(title)}</title>
133
+ <style>
134
+ body {
135
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
136
+ margin: 0;
137
+ padding: 2rem;
138
+ background: #0b1020;
139
+ color: #f5f7ff;
140
+ }
141
+ h1, h2, h3, h4 {
142
+ color: #ffffff;
143
+ }
144
+ a {
145
+ color: #7aa2ff;
146
+ }
147
+ table {
148
+ border-collapse: collapse;
149
+ width: 100%;
150
+ margin: 1rem 0;
151
+ background: #11162a;
152
+ }
153
+ th, td {
154
+ border: 1px solid #252b45;
155
+ padding: 0.5rem 0.75rem;
156
+ text-align: left;
157
+ }
158
+ th {
159
+ background: #181e36;
160
+ font-weight: 600;
161
+ }
162
+ tr:nth-child(even) td {
163
+ background: #101528;
164
+ }
165
+ .finding {
166
+ border: 1px solid #252b45;
167
+ border-left-width: 4px;
168
+ padding: 1rem;
169
+ margin: 1rem 0;
170
+ background: #11162a;
171
+ border-radius: 4px;
172
+ }
173
+ .sev-critical { border-left-color: #ff4b5c; }
174
+ .sev-high { border-left-color: #ff9f43; }
175
+ .sev-medium { border-left-color: #feca57; }
176
+ .sev-low { border-left-color: #1dd1a1; }
177
+ ul {
178
+ padding-left: 1.5rem;
179
+ }
180
+ code {
181
+ font-family: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
182
+ background: #181e36;
183
+ padding: 0.1rem 0.25rem;
184
+ border-radius: 3px;
185
+ }
186
+ .meta {
187
+ margin-bottom: 1.5rem;
188
+ font-size: 0.95rem;
189
+ color: #c3c8e8;
190
+ }
191
+ .section {
192
+ margin-top: 2rem;
193
+ }
194
+ </style>
195
+ </head>
196
+ <body>
197
+ <h1>${escapeHtml(title)}</h1>
198
+
199
+ <div class="meta">
200
+ <p><strong>Generated at:</strong> ${escapeHtml(generatedAtIso)}</p>
201
+ <p><strong>Target:</strong> ${escapeHtml(metadata.target ?? "unknown")}</p>
202
+ <p><strong>Engine version:</strong> ${escapeHtml(
203
+ metadata.version ?? "1.0.0",
204
+ )}</p>
205
+ </div>
206
+
207
+ <section class="section">
208
+ <h2>Summary</h2>
209
+ ${summaryTable}
210
+ </section>
211
+
212
+ <section class="section">
213
+ <h2>Findings</h2>
214
+ ${findingsHtml}
215
+ </section>
216
+
217
+ <section class="section">
218
+ <h2>Correlation Chains</h2>
219
+ ${chainsHtml}
220
+ </section>
221
+ </body>
222
+ </html>`;
223
+
224
+ return {
225
+ filename: `report${this.getExtension()}`,
226
+ content: html,
227
+ mime: "text/html",
228
+ };
229
+ }
230
+
231
+ protected prepareData(
232
+ findings: Finding[],
233
+ chains: CorrelationChain[],
234
+ ): { findings: Finding[]; chains: CorrelationChain[] } {
235
+ return {
236
+ findings: sortFindingsBySeverity(findings),
237
+ chains: sortChains(chains),
238
+ };
239
+ }
240
+ }
File without changes
@@ -0,0 +1,98 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * JSON REPORTER — Version PRO
4
+ * Génère un rapport JSON structuré, stable et lisible.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ import { BaseReporter } from "../base/BaseReporter";
9
+ import { ReporterMetadata, ReporterOutput } from "../base/ReporterTypes";
10
+ import { Finding, CorrelationChain } from "../../core/scoring-types";
11
+
12
+ import { sortFindingsBySeverity } from "../../utils/finding-utils";
13
+ import { sortChains } from "../../utils/chain-utils";
14
+ import { scoreInfo } from "../../utils/score-utils";
15
+ import { formatIso } from "../../utils/date-utils";
16
+
17
+ export class JSONReporter extends BaseReporter {
18
+ getName(): string {
19
+ return "json";
20
+ }
21
+
22
+ getExtension(): string {
23
+ return ".json";
24
+ }
25
+
26
+ generate(
27
+ findings: Finding[],
28
+ chains: CorrelationChain[],
29
+ metadata: ReporterMetadata = {},
30
+ ): ReporterOutput {
31
+ this.validateInput(findings, chains);
32
+
33
+ const { findings: preparedFindings, chains: preparedChains } =
34
+ this.prepareData(findings, chains);
35
+
36
+ const report = {
37
+ format: "json",
38
+ generatedAt: metadata.generatedAt ?? Date.now(),
39
+ generatedAtIso: formatIso(metadata.generatedAt ?? Date.now()),
40
+ title: metadata.title ?? "Security Scan Report",
41
+ target: metadata.target ?? "unknown",
42
+ version: metadata.version ?? "1.0.0",
43
+
44
+ summary: {
45
+ totalFindings: preparedFindings.length,
46
+ totalChains: preparedChains.length,
47
+ severities: {
48
+ critical: preparedFindings.filter((f) => f.severity === "critical")
49
+ .length,
50
+ high: preparedFindings.filter((f) => f.severity === "high").length,
51
+ medium: preparedFindings.filter((f) => f.severity === "medium")
52
+ .length,
53
+ low: preparedFindings.filter((f) => f.severity === "low").length,
54
+ },
55
+ },
56
+
57
+ findings: preparedFindings.map((f) => ({
58
+ ...f,
59
+ scoreInfo: scoreInfo(f.score / 100), // ton score brut est 0–100
60
+ })),
61
+
62
+ chains: preparedChains.map((c) => ({
63
+ ...c,
64
+ confidenceLabel:
65
+ c.confidence >= 0.9
66
+ ? "very_high"
67
+ : c.confidence >= 0.75
68
+ ? "high"
69
+ : c.confidence >= 0.5
70
+ ? "medium"
71
+ : c.confidence >= 0.25
72
+ ? "low"
73
+ : "very_low",
74
+ })),
75
+ };
76
+
77
+ return {
78
+ filename: `report${this.getExtension()}`,
79
+ content: JSON.stringify(report, null, 2),
80
+ mime: "application/json",
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Prépare les données avant génération :
86
+ * - tri des findings par sévérité
87
+ * - tri des chains par confiance
88
+ */
89
+ protected prepareData(
90
+ findings: Finding[],
91
+ chains: CorrelationChain[],
92
+ ): { findings: Finding[]; chains: CorrelationChain[] } {
93
+ return {
94
+ findings: sortFindingsBySeverity(findings),
95
+ chains: sortChains(chains),
96
+ };
97
+ }
98
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * MARKDOWN REPORTER — Version PRO
4
+ * Génère un rapport Markdown propre, lisible et structuré.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ import { BaseReporter } from "../base/BaseReporter";
9
+ import { ReporterMetadata, ReporterOutput } from "../base/ReporterTypes";
10
+ import { Finding, CorrelationChain } from "../../core/scoring-types";
11
+
12
+ import {
13
+ escapeMarkdown,
14
+ renderTable,
15
+ renderList,
16
+ renderSection,
17
+ } from "../../utils/report-utils";
18
+ import { sortFindingsBySeverity } from "../../utils/finding-utils";
19
+ import { sortChains, chainSummary } from "../../utils/chain-utils";
20
+ import { scoreInfo } from "../../utils/score-utils";
21
+ import { formatIso } from "../../utils/date-utils";
22
+
23
+ export class MarkdownReporter extends BaseReporter {
24
+ getName(): string {
25
+ return "markdown";
26
+ }
27
+
28
+ getExtension(): string {
29
+ return ".md";
30
+ }
31
+
32
+ generate(
33
+ findings: Finding[],
34
+ chains: CorrelationChain[],
35
+ metadata: ReporterMetadata = {},
36
+ ): ReporterOutput {
37
+ this.validateInput(findings, chains);
38
+
39
+ const { findings: preparedFindings, chains: preparedChains } =
40
+ this.prepareData(findings, chains);
41
+
42
+ const generatedAt = metadata.generatedAt ?? Date.now();
43
+ const generatedAtIso = formatIso(generatedAt);
44
+
45
+ // ─────────────────────────────────────────────────────────────
46
+ // HEADER
47
+ // ─────────────────────────────────────────────────────────────
48
+ let md = `# ${escapeMarkdown(metadata.title ?? "Security Scan Report")}\n\n`;
49
+
50
+ md += `**Generated at:** ${generatedAtIso}\n\n`;
51
+ md += `**Target:** ${escapeMarkdown(metadata.target ?? "unknown")}\n\n`;
52
+ md += `**Engine version:** ${escapeMarkdown(metadata.version ?? "1.0.0")}\n\n`;
53
+
54
+ // ─────────────────────────────────────────────────────────────
55
+ // SUMMARY
56
+ // ─────────────────────────────────────────────────────────────
57
+ md += renderSection(
58
+ "Summary",
59
+ renderTable(
60
+ ["Metric", "Value"],
61
+ [
62
+ ["Total Findings", String(preparedFindings.length)],
63
+ ["Total Chains", String(preparedChains.length)],
64
+ [
65
+ "Critical",
66
+ String(
67
+ preparedFindings.filter((f) => f.severity === "critical").length,
68
+ ),
69
+ ],
70
+ [
71
+ "High",
72
+ String(
73
+ preparedFindings.filter((f) => f.severity === "high").length,
74
+ ),
75
+ ],
76
+ [
77
+ "Medium",
78
+ String(
79
+ preparedFindings.filter((f) => f.severity === "medium").length,
80
+ ),
81
+ ],
82
+ [
83
+ "Low",
84
+ String(preparedFindings.filter((f) => f.severity === "low").length),
85
+ ],
86
+ ],
87
+ ),
88
+ );
89
+
90
+ // ─────────────────────────────────────────────────────────────
91
+ // FINDINGS
92
+ // ─────────────────────────────────────────────────────────────
93
+ const findingsContent = preparedFindings
94
+ .map((f) => {
95
+ const info = scoreInfo(f.score / 100);
96
+
97
+ return (
98
+ `### ${escapeMarkdown(f.vulnerability.toUpperCase())}\n\n` +
99
+ `**Severity:** ${escapeMarkdown(f.severity)}\n\n` +
100
+ `**Score:** ${info.normalized} (${escapeMarkdown(info.label)})\n\n` +
101
+ (f.details
102
+ ? `**Details:**\n\n${escapeMarkdown(f.details)}\n\n`
103
+ : "") +
104
+ (f.evidence?.length
105
+ ? `**Evidence:**\n\n${renderList(
106
+ f.evidence.map((e) => escapeMarkdown(String(e))),
107
+ )}\n\n`
108
+ : "") +
109
+ (f.chains?.length
110
+ ? `**Related Chains:**\n\n${renderList(f.chains.map((c) => escapeMarkdown(chainSummary(c))))}\n\n`
111
+ : "")
112
+ );
113
+ })
114
+ .join("\n");
115
+
116
+ md += renderSection(
117
+ "Findings",
118
+ findingsContent || "_No findings detected._",
119
+ );
120
+
121
+ // ─────────────────────────────────────────────────────────────
122
+ // CHAINS
123
+ // ─────────────────────────────────────────────────────────────
124
+ const chainsContent = preparedChains
125
+ .map((c) => `- ${escapeMarkdown(chainSummary(c))}`)
126
+ .join("\n");
127
+
128
+ md += renderSection(
129
+ "Correlation Chains",
130
+ chainsContent || "_No chains detected._",
131
+ );
132
+
133
+ // ─────────────────────────────────────────────────────────────
134
+ // OUTPUT
135
+ // ─────────────────────────────────────────────────────────────
136
+ return {
137
+ filename: `report${this.getExtension()}`,
138
+ content: md,
139
+ mime: "text/markdown",
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Prépare les données avant génération :
145
+ * - tri des findings par sévérité
146
+ * - tri des chains par confiance
147
+ */
148
+ protected prepareData(
149
+ findings: Finding[],
150
+ chains: CorrelationChain[],
151
+ ): { findings: Finding[]; chains: CorrelationChain[] } {
152
+ return {
153
+ findings: sortFindingsBySeverity(findings),
154
+ chains: sortChains(chains),
155
+ };
156
+ }
157
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * REPORTER FACTORY
4
+ * Crée dynamiquement un reporter selon le format demandé.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ import { ReporterFormat } from "./base/ReporterTypes";
9
+ import { BaseReporter } from "./base/BaseReporter";
10
+
11
+ import { JSONReporter } from "./json/JSONReporter";
12
+ import { MarkdownReporter } from "./markdown/MarkdownReporter";
13
+ import { HTMLReporter } from "./html/HTMLReporter";
14
+
15
+ export function createReporter(format: ReporterFormat): BaseReporter {
16
+ switch (format) {
17
+ case "json":
18
+ return new JSONReporter();
19
+
20
+ case "markdown":
21
+ return new MarkdownReporter();
22
+
23
+ case "html":
24
+ return new HTMLReporter();
25
+
26
+ default:
27
+ throw new Error(`Unknown reporter format: ${format}`);
28
+ }
29
+ }
@@ -0,0 +1,42 @@
1
+ import { DnsRule } from "../dns.rules";
2
+
3
+ describe("DNS Rule", () => {
4
+ const rule = new DnsRule();
5
+
6
+ it("détecte une anomalie DNS", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "dns" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ payload: "internal.localdomain",
15
+ },
16
+ ],
17
+ chains: [],
18
+ };
19
+
20
+ expect(rule.applies(ctx)).toBe(true);
21
+ const findings = rule.execute(ctx);
22
+ expect(findings.length).toBe(1);
23
+ expect(findings[0].vulnerability).toBe("dns");
24
+ });
25
+
26
+ it("ne détecte rien sur un domaine normal", () => {
27
+ const ctx = {
28
+ events: [
29
+ {
30
+ id: "1",
31
+ source: "dns" as const,
32
+ timestamp: Date.now(),
33
+ metadata: {},
34
+ payload: "google.com",
35
+ },
36
+ ],
37
+ chains: [],
38
+ };
39
+
40
+ expect(rule.applies(ctx)).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,46 @@
1
+ import { HttpRule } from "../http.rules";
2
+
3
+ describe("HTTP Rule", () => {
4
+ const rule = new HttpRule();
5
+
6
+ it("détecte une anomalie HTTP", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "http" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {
14
+ method: "TRACE",
15
+ path: "/",
16
+ },
17
+ },
18
+ ],
19
+ chains: [],
20
+ };
21
+
22
+ expect(rule.applies(ctx)).toBe(true);
23
+ const findings = rule.execute(ctx);
24
+ expect(findings.length).toBe(1);
25
+ expect(findings[0].vulnerability).toBe("http");
26
+ });
27
+
28
+ it("ne détecte rien sur une requête normale", () => {
29
+ const ctx = {
30
+ events: [
31
+ {
32
+ id: "1",
33
+ source: "http" as const,
34
+ timestamp: Date.now(),
35
+ metadata: {
36
+ method: "GET",
37
+ path: "/home",
38
+ },
39
+ },
40
+ ],
41
+ chains: [],
42
+ };
43
+
44
+ expect(rule.applies(ctx)).toBe(false);
45
+ });
46
+ });
@@ -0,0 +1,42 @@
1
+ import { LfiRule } from "../lfi.rule";
2
+
3
+ describe("LFI Rule", () => {
4
+ const rule = new LfiRule();
5
+
6
+ it("détecte un payload LFI", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "http" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ payload: "../../../../etc/passwd",
15
+ },
16
+ ],
17
+ chains: [],
18
+ };
19
+
20
+ expect(rule.applies(ctx)).toBe(true);
21
+ const findings = rule.execute(ctx);
22
+ expect(findings.length).toBe(1);
23
+ expect(findings[0].vulnerability).toBe("lfi");
24
+ });
25
+
26
+ it("ne détecte rien sur un payload normal", () => {
27
+ const ctx = {
28
+ events: [
29
+ {
30
+ id: "1",
31
+ source: "http" as const,
32
+ timestamp: Date.now(),
33
+ metadata: {},
34
+ payload: "/home/user/profile",
35
+ },
36
+ ],
37
+ chains: [],
38
+ };
39
+
40
+ expect(rule.applies(ctx)).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,42 @@
1
+ import { PathTraversalRule } from "../path-transversal.rule";
2
+
3
+ describe("Path Traversal Rule", () => {
4
+ const rule = new PathTraversalRule();
5
+
6
+ it("détecte un path traversal", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "http" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ payload: "../etc/passwd",
15
+ },
16
+ ],
17
+ chains: [],
18
+ };
19
+
20
+ expect(rule.applies(ctx)).toBe(true);
21
+ const findings = rule.execute(ctx);
22
+ expect(findings.length).toBe(1);
23
+ expect(findings[0].vulnerability).toBe("path_traversal");
24
+ });
25
+
26
+ it("ne détecte rien sur un payload normal", () => {
27
+ const ctx = {
28
+ events: [
29
+ {
30
+ id: "1",
31
+ source: "http" as const,
32
+ timestamp: Date.now(),
33
+ metadata: {},
34
+ payload: "/api/user",
35
+ },
36
+ ],
37
+ chains: [],
38
+ };
39
+
40
+ expect(rule.applies(ctx)).toBe(false);
41
+ });
42
+ });