@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,15 @@
1
+ import { ScoringResult } from "./scoring-types";
2
+
3
+ /**
4
+ * Helper pour créer un résultat vide (utile pour les tests).
5
+ */
6
+ export function createEmptyResult(): ScoringResult {
7
+ return {
8
+ score: 0,
9
+ severity: "low",
10
+ findings: [],
11
+ chains: [],
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ };
15
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * SCORING TYPES — Version avancée et stable
4
+ * Source de vérité unique pour tout le moteur.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ //
9
+ // 1. Vulnérabilités supportées
10
+ //
11
+ export type Vulnerability =
12
+ | "ssrf"
13
+ | "xss"
14
+ | "sqli"
15
+ | "dns"
16
+ | "http"
17
+ | "waf"
18
+ | "rce"
19
+ | "lfi"
20
+ | "path_traversal";
21
+
22
+ //
23
+ // 2. Sévérité standardisée
24
+ //
25
+ export type Severity = "low" | "medium" | "high" | "critical";
26
+
27
+ //
28
+ // 3. Source d’un événement normalisé
29
+ //
30
+ export type EventSource = "http" | "dns" | "waf" | "scan" | "oast";
31
+
32
+ //
33
+ // 4. Événement normalisé (format unique pour tout le moteur)
34
+ //
35
+ export interface NormalizedEvent {
36
+ id: string; // UUID
37
+ source: EventSource;
38
+ protocol?: string; // http, dns, smtp, websocket, etc.
39
+ timestamp: number;
40
+
41
+ // Payload brut ou transformé
42
+ payload?: string;
43
+
44
+ // Données additionnelles (headers, body, metadata…)
45
+ metadata: Record<string, any>;
46
+ }
47
+
48
+ //
49
+ // 5. Résultat d’une règle individuelle
50
+ //
51
+ export interface RuleFinding {
52
+ ruleId: string;
53
+ vulnerability: Vulnerability;
54
+ score: number; // score brut (0 → 1)
55
+ severity: Severity;
56
+ details?: string;
57
+ }
58
+
59
+ //
60
+ // 6. Chaîne de corrélation (ex : DNS → HTTP = SSRF)
61
+ //
62
+ export interface CorrelationChain {
63
+ id: string;
64
+ type: Vulnerability;
65
+ events: NormalizedEvent[];
66
+ confidence: number; // 0 → 1
67
+ }
68
+
69
+ //
70
+ // 7. Résultat final d’une vulnérabilité consolidée
71
+ //
72
+ export interface Finding {
73
+ id: string;
74
+ vulnerability: Vulnerability;
75
+ severity: Severity;
76
+ score: number; // score final pondéré
77
+ evidence: NormalizedEvent[];
78
+ chains?: CorrelationChain[];
79
+ details?: string;
80
+ }
81
+
82
+ //
83
+ // 8. Interface de base pour toutes les règles
84
+ //
85
+ export interface Rule {
86
+ id: string;
87
+ name: string;
88
+ description?: string;
89
+
90
+ /**
91
+ * Vérifie si la règle doit s’appliquer au contexte.
92
+ */
93
+ applies(context: ScoringContext): boolean;
94
+
95
+ /**
96
+ * Retourne un ou plusieurs findings bruts.
97
+ */
98
+ execute(context: ScoringContext): RuleFinding[];
99
+ }
100
+
101
+ //
102
+ // 9. Contexte minimal pour les règles et le moteur
103
+ //
104
+ export interface ScoringContext {
105
+ events: NormalizedEvent[];
106
+ chains: CorrelationChain[];
107
+ metadata?: Record<string, any>;
108
+ }
109
+
110
+ //
111
+ // 10. Résultat final du moteur de scoring
112
+ //
113
+ export interface ScoringResult {
114
+ score: number; // score global (0–100)
115
+ severity: Severity;
116
+ findings: Finding[];
117
+ chains: CorrelationChain[];
118
+ timestamp: number;
119
+
120
+ /**
121
+ * Métadonnées additionnelles (optionnelles).
122
+ * Ex : cible, IP, user-agent, contexte d'analyse…
123
+ */
124
+ metadata?: Record<string, any>;
125
+ }
@@ -0,0 +1,76 @@
1
+ import { ChainDetector } from "../chain-detector";
2
+ import { NormalizedEvent } from "../../core/scoring-types";
3
+
4
+ const evt = (partial: Partial<NormalizedEvent>): NormalizedEvent => ({
5
+ id: partial.id ?? "id",
6
+ source: partial.source ?? "http",
7
+ timestamp: partial.timestamp ?? Date.now(),
8
+ payload: partial.payload ?? "",
9
+ metadata: partial.metadata ?? {},
10
+ });
11
+
12
+ describe("ChainDetector (PRO)", () => {
13
+ it("détecte une chaîne SSRF (HTTP + DNS)", () => {
14
+ const events = [
15
+ evt({ source: "http", timestamp: 1 }),
16
+ evt({ source: "dns", timestamp: 2 }),
17
+ ];
18
+
19
+ const chains = ChainDetector.detect(events);
20
+
21
+ expect(chains.length).toBe(1);
22
+ expect(chains[0].chain).toBe("ssrf_chain");
23
+ expect(chains[0].events.length).toBe(2);
24
+ });
25
+
26
+ it("détecte une chaîne XSS (HTTP + WAF)", () => {
27
+ const events = [
28
+ evt({ source: "http", timestamp: 1 }),
29
+ evt({
30
+ source: "waf",
31
+ timestamp: 2,
32
+ metadata: { triggeredRules: ["xss"] },
33
+ }),
34
+ ];
35
+
36
+ const chains = ChainDetector.detect(events);
37
+
38
+ expect(chains.some((c) => c.chain === "xss_chain")).toBe(true);
39
+ });
40
+
41
+ it("détecte une chaîne SQLi", () => {
42
+ const events = [
43
+ evt({ source: "http", timestamp: 1 }),
44
+ evt({
45
+ source: "waf",
46
+ timestamp: 2,
47
+ metadata: { triggeredRules: ["sqli"] },
48
+ }),
49
+ ];
50
+
51
+ const chains = ChainDetector.detect(events);
52
+
53
+ expect(chains.some((c) => c.chain === "sqli_chain")).toBe(true);
54
+ });
55
+
56
+ it("détecte une chaîne WAF confirmée (score >= 0.9)", () => {
57
+ const events = [
58
+ evt({
59
+ source: "waf",
60
+ metadata: { wafScore: 0.95 },
61
+ }),
62
+ ];
63
+
64
+ const chains = ChainDetector.detect(events);
65
+
66
+ expect(chains.some((c) => c.chain === "waf_confirmed_chain")).toBe(true);
67
+ });
68
+
69
+ it("ne détecte rien si aucun pattern n'est présent", () => {
70
+ const events = [evt({ source: "http" }), evt({ source: "http" })];
71
+
72
+ const chains = ChainDetector.detect(events);
73
+
74
+ expect(chains.length).toBe(0);
75
+ });
76
+ });
@@ -0,0 +1,49 @@
1
+ import { Correlator } from "../correlator";
2
+ import { NormalizedEvent } from "../../core/scoring-types";
3
+
4
+ const evt = (partial: Partial<NormalizedEvent>): NormalizedEvent => ({
5
+ id: partial.id ?? "id",
6
+ source: partial.source ?? "http",
7
+ timestamp: partial.timestamp ?? Date.now(),
8
+ payload: partial.payload ?? "",
9
+ metadata: partial.metadata ?? {},
10
+ });
11
+
12
+ describe("Correlator (PRO)", () => {
13
+ it("retourne des groupes et des chaînes", () => {
14
+ const events = [
15
+ evt({ source: "http", timestamp: 1, metadata: { ip: "1.1.1.1" } }),
16
+ evt({ source: "dns", timestamp: 2, metadata: { ip: "1.1.1.1" } }),
17
+ ];
18
+
19
+ const result = Correlator.correlate(events);
20
+
21
+ expect(result.groups.length).toBe(1);
22
+ expect(result.chains.length).toBe(1);
23
+ expect(result.chains[0].chain).toBe("ssrf_chain");
24
+ });
25
+
26
+ it("gère plusieurs groupes distincts", () => {
27
+ const events = [
28
+ evt({ metadata: { ip: "A" } }),
29
+ evt({ metadata: { ip: "B" } }),
30
+ ];
31
+
32
+ const result = Correlator.correlate(events);
33
+
34
+ expect(result.groups.length).toBe(2);
35
+ });
36
+
37
+ it("lance une erreur si l'entrée n'est pas un tableau", () => {
38
+ // @ts-expect-error test volontaire
39
+ expect(() => Correlator.correlate(null)).toThrow();
40
+ });
41
+
42
+ it("ne détecte aucune chaîne si aucun pattern n'est présent", () => {
43
+ const events = [evt({ source: "http" }), evt({ source: "http" })];
44
+
45
+ const result = Correlator.correlate(events);
46
+
47
+ expect(result.chains.length).toBe(0);
48
+ });
49
+ });
@@ -0,0 +1,62 @@
1
+ import { EventGrouper } from "../event-grouper";
2
+ import { NormalizedEvent } from "../../core/scoring-types";
3
+
4
+ const evt = (partial: Partial<NormalizedEvent>): NormalizedEvent => ({
5
+ id: partial.id ?? "id",
6
+ source: partial.source ?? "http",
7
+ timestamp: partial.timestamp ?? Date.now(),
8
+ payload: partial.payload ?? "",
9
+ metadata: partial.metadata ?? {},
10
+ });
11
+
12
+ describe("EventGrouper (PRO)", () => {
13
+ it("groupe les événements par IP (par défaut)", () => {
14
+ const events = [
15
+ evt({ metadata: { ip: "1.1.1.1" } }),
16
+ evt({ metadata: { ip: "1.1.1.1" } }),
17
+ evt({ metadata: { ip: "2.2.2.2" } }),
18
+ ];
19
+
20
+ const groups = EventGrouper.groupBy(events);
21
+
22
+ expect(groups.length).toBe(2);
23
+ expect(groups.find((g) => g.key === "1.1.1.1")!.events.length).toBe(2);
24
+ expect(groups.find((g) => g.key === "2.2.2.2")!.events.length).toBe(1);
25
+ });
26
+
27
+ it("groupe selon un sélecteur personnalisé", () => {
28
+ const events = [
29
+ evt({ metadata: { sessionId: "A" } }),
30
+ evt({ metadata: { sessionId: "B" } }),
31
+ evt({ metadata: { sessionId: "A" } }),
32
+ ];
33
+
34
+ const groups = EventGrouper.groupBy(events, (e) => e.metadata.sessionId);
35
+
36
+ expect(groups.length).toBe(2);
37
+ expect(groups.find((g) => g.key === "A")!.events.length).toBe(2);
38
+ });
39
+
40
+ it("gère les événements sans clé (fallback 'unknown')", () => {
41
+ const events = [evt({ metadata: {} }), evt({ metadata: {} })];
42
+
43
+ const groups = EventGrouper.groupBy(events);
44
+
45
+ expect(groups.length).toBe(1);
46
+ expect(groups[0].key).toBe("unknown");
47
+ expect(groups[0].events.length).toBe(2);
48
+ });
49
+
50
+ it("tri les événements dans chaque groupe", () => {
51
+ const events = [
52
+ evt({ timestamp: 3, metadata: { ip: "1.1.1.1" } }),
53
+ evt({ timestamp: 1, metadata: { ip: "1.1.1.1" } }),
54
+ evt({ timestamp: 2, metadata: { ip: "1.1.1.1" } }),
55
+ ];
56
+
57
+ const groups = EventGrouper.groupBy(events);
58
+ const timestamps = groups[0].events.map((e) => e.timestamp);
59
+
60
+ expect(timestamps).toEqual([1, 2, 3]);
61
+ });
62
+ });
@@ -0,0 +1,99 @@
1
+ import { NormalizedEvent } from "../core/scoring-types";
2
+
3
+ export type AttackChain =
4
+ | "ssrf_chain"
5
+ | "xss_chain"
6
+ | "sqli_chain"
7
+ | "lfi_chain"
8
+ | "path_traversal_chain"
9
+ | "waf_confirmed_chain";
10
+
11
+ export interface ChainDetectionResult {
12
+ chain: AttackChain;
13
+ events: NormalizedEvent[];
14
+ }
15
+
16
+ /**
17
+ * ─────────────────────────────────────────────────────────────
18
+ * CHAIN DETECTOR — Détecte les chaînes d’attaque multi‑événements
19
+ * Exemple : HTTP suspect → DNS interne → WAF → SSRF confirmé
20
+ * ─────────────────────────────────────────────────────────────
21
+ */
22
+ export class ChainDetector {
23
+ /**
24
+ * Analyse une liste d'événements normalisés et détecte
25
+ * les chaînes d’attaque connues.
26
+ */
27
+ static detect(events: NormalizedEvent[]): ChainDetectionResult[] {
28
+ const results: ChainDetectionResult[] = [];
29
+
30
+ // Tri chronologique (important pour les chaînes)
31
+ const sorted = [...events].sort((a, b) => a.timestamp - b.timestamp);
32
+
33
+ // ─────────────────────────────────────────────────────────────
34
+ // 1. Chaîne SSRF : HTTP → DNS → WAF
35
+ // ─────────────────────────────────────────────────────────────
36
+ const http = sorted.find((e) => e.source === "http");
37
+ const dns = sorted.find((e) => e.source === "dns");
38
+ const waf = sorted.find((e) => e.source === "waf");
39
+
40
+ if (http && dns) {
41
+ results.push({
42
+ chain: "ssrf_chain",
43
+ events: [http, dns, ...(waf ? [waf] : [])],
44
+ });
45
+ }
46
+
47
+ // ─────────────────────────────────────────────────────────────
48
+ // 2. Chaîne XSS : HTTP → WAF
49
+ // ─────────────────────────────────────────────────────────────
50
+ if (http && waf && waf.metadata.triggeredRules?.includes("xss")) {
51
+ results.push({
52
+ chain: "xss_chain",
53
+ events: [http, waf],
54
+ });
55
+ }
56
+
57
+ // ─────────────────────────────────────────────────────────────
58
+ // 3. Chaîne SQLi : HTTP → WAF
59
+ // ─────────────────────────────────────────────────────────────
60
+ if (http && waf && waf.metadata.triggeredRules?.includes("sqli")) {
61
+ results.push({
62
+ chain: "sqli_chain",
63
+ events: [http, waf],
64
+ });
65
+ }
66
+
67
+ // ─────────────────────────────────────────────────────────────
68
+ // 4. Chaîne LFI : HTTP → WAF
69
+ // ─────────────────────────────────────────────────────────────
70
+ if (http && waf && waf.metadata.triggeredRules?.includes("lfi")) {
71
+ results.push({
72
+ chain: "lfi_chain",
73
+ events: [http, waf],
74
+ });
75
+ }
76
+
77
+ // ─────────────────────────────────────────────────────────────
78
+ // 5. Chaîne Path Traversal : HTTP → WAF
79
+ // ─────────────────────────────────────────────────────────────
80
+ if (http && waf && waf.metadata.triggeredRules?.includes("path")) {
81
+ results.push({
82
+ chain: "path_traversal_chain",
83
+ events: [http, waf],
84
+ });
85
+ }
86
+
87
+ // ─────────────────────────────────────────────────────────────
88
+ // 6. Chaîne WAF Confirmed : WAF seul
89
+ // ─────────────────────────────────────────────────────────────
90
+ if (waf && waf.metadata.wafScore >= 0.9) {
91
+ results.push({
92
+ chain: "waf_confirmed_chain",
93
+ events: [waf],
94
+ });
95
+ }
96
+
97
+ return results;
98
+ }
99
+ }
@@ -0,0 +1,39 @@
1
+ import { NormalizedEvent } from "../core/scoring-types";
2
+ import { EventGrouper, EventGroup } from "./event-grouper";
3
+ import { ChainDetector, ChainDetectionResult } from "./chain-detector";
4
+
5
+ export interface CorrelationResult {
6
+ groups: EventGroup[];
7
+ chains: ChainDetectionResult[];
8
+ }
9
+
10
+ /**
11
+ * ─────────────────────────────────────────────────────────────
12
+ * CORRELATOR — Orchestrateur de la corrélation
13
+ * 1. Regroupe les événements (EventGrouper)
14
+ * 2. Détecte les chaînes d’attaque (ChainDetector)
15
+ * ─────────────────────────────────────────────────────────────
16
+ */
17
+ export class Correlator {
18
+ static correlate(events: NormalizedEvent[]): CorrelationResult {
19
+ if (!Array.isArray(events)) {
20
+ throw new Error("Correlator: events must be an array");
21
+ }
22
+
23
+ // 1. Groupement logique (par IP par défaut)
24
+ const groups = EventGrouper.groupBy(events);
25
+
26
+ // 2. Détection des chaînes dans chaque groupe
27
+ const chains: ChainDetectionResult[] = [];
28
+
29
+ for (const group of groups) {
30
+ const detected = ChainDetector.detect(group.events);
31
+ chains.push(...detected);
32
+ }
33
+
34
+ return {
35
+ groups,
36
+ chains,
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,47 @@
1
+ import { NormalizedEvent } from "../core/scoring-types";
2
+
3
+ export interface EventGroup {
4
+ key: string;
5
+ events: NormalizedEvent[];
6
+ }
7
+
8
+ /**
9
+ * ─────────────────────────────────────────────────────────────
10
+ * EVENT GROUPER — Regroupe les événements par contexte
11
+ * (IP, sessionId, requestId, etc.)
12
+ * ─────────────────────────────────────────────────────────────
13
+ */
14
+ export class EventGrouper {
15
+ /**
16
+ * Regroupe les événements selon une clé dynamique.
17
+ * Par défaut : groupement par IP.
18
+ */
19
+ static groupBy(
20
+ events: NormalizedEvent[],
21
+ selector: (evt: NormalizedEvent) => string = EventGrouper.defaultSelector,
22
+ ): EventGroup[] {
23
+ const map = new Map<string, NormalizedEvent[]>();
24
+
25
+ for (const evt of events) {
26
+ const key = selector(evt) || "unknown";
27
+
28
+ if (!map.has(key)) {
29
+ map.set(key, []);
30
+ }
31
+
32
+ map.get(key)!.push(evt);
33
+ }
34
+
35
+ return Array.from(map.entries()).map(([key, events]) => ({
36
+ key,
37
+ events: events.sort((a, b) => a.timestamp - b.timestamp),
38
+ }));
39
+ }
40
+
41
+ /**
42
+ * Sélecteur par défaut : groupement par IP.
43
+ */
44
+ private static defaultSelector(evt: NormalizedEvent): string {
45
+ return evt.metadata?.ip ?? "unknown";
46
+ }
47
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./chain-detector";
2
+ export * from "./event-grouper";
3
+ export * from "./correlator";
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // CORE (scoring complet)
3
+ // ─────────────────────────────────────────────────────────────
4
+ export * from "./core";
5
+
6
+ // ─────────────────────────────────────────────────────────────
7
+ // RULES
8
+ // ─────────────────────────────────────────────────────────────
9
+ export * from "./rules";
10
+ export * from "./rules/rule-registry";
11
+
12
+ // ─────────────────────────────────────────────────────────────
13
+ // NORMALIZERS
14
+ // ─────────────────────────────────────────────────────────────
15
+ export * from "./normalizers";
16
+ export { Normalizers, normalizeEvent } from "./normalizers";
17
+
18
+ // ─────────────────────────────────────────────────────────────
19
+ // CORRELATION
20
+ // ─────────────────────────────────────────────────────────────
21
+ export * from "./correlation";
@@ -0,0 +1,40 @@
1
+ import { DnsNormalizer } from "../dns.normalizer";
2
+
3
+ describe("DnsNormalizer (PRO)", () => {
4
+ it("normalise un événement DNS complet", () => {
5
+ const raw = {
6
+ ip: "10.0.0.5",
7
+ query: "internal.service.local",
8
+ type: "A",
9
+ raw: { packet: true },
10
+ };
11
+
12
+ const evt = DnsNormalizer.normalize(raw);
13
+
14
+ expect(evt.source).toBe("dns");
15
+ expect(evt.payload).toBe("internal.service.local");
16
+ expect(evt.metadata.ip).toBe("10.0.0.5");
17
+ expect(evt.metadata.recordType).toBe("A");
18
+ });
19
+
20
+ it("gère un événement DNS minimal", () => {
21
+ const raw = { query: "example.com" };
22
+ const evt = DnsNormalizer.normalize(raw);
23
+
24
+ expect(evt.metadata.ip).toBe("unknown");
25
+ expect(evt.metadata.recordType).toBe("A");
26
+ });
27
+
28
+ it("gère un événement DNS sans query", () => {
29
+ const evt = DnsNormalizer.normalize({});
30
+ expect(evt.payload).toBe("");
31
+ expect(evt.metadata.query).toBe("");
32
+ });
33
+
34
+ it("gère un événement DNS avec type inconnu", () => {
35
+ const raw = { query: "test", type: "AAAAA" };
36
+ const evt = DnsNormalizer.normalize(raw);
37
+
38
+ expect(evt.metadata.recordType).toBe("AAAAA");
39
+ });
40
+ });
@@ -0,0 +1,55 @@
1
+ import { HttpNormalizer } from "../http.normalizer";
2
+
3
+ describe("HttpNormalizer (PRO)", () => {
4
+ it("normalise correctement une requête HTTP complète", () => {
5
+ const raw = {
6
+ method: "POST",
7
+ url: "/login",
8
+ headers: { "x-forwarded-for": "1.2.3.4", host: "example.com" },
9
+ query: { user: "admin" },
10
+ body: { pass: "123" },
11
+ socket: { remoteAddress: "5.6.7.8" },
12
+ };
13
+
14
+ const evt = HttpNormalizer.normalize(raw);
15
+
16
+ expect(evt.source).toBe("http");
17
+ expect(typeof evt.id).toBe("string");
18
+ expect(typeof evt.timestamp).toBe("number");
19
+
20
+ expect(evt.payload).toContain("POST /login");
21
+ expect(evt.metadata.method).toBe("POST");
22
+ expect(evt.metadata.path).toBe("/login");
23
+ expect(evt.metadata.headers.host).toBe("example.com");
24
+ expect(evt.metadata.ip).toBe("1.2.3.4");
25
+ });
26
+
27
+ it("gère une requête sans headers ni body", () => {
28
+ const raw = { method: "GET", url: "/test" };
29
+ const evt = HttpNormalizer.normalize(raw);
30
+
31
+ expect(evt.metadata.headers).toEqual({});
32
+ expect(evt.metadata.body).toBe(null);
33
+ });
34
+
35
+ it("gère une requête avec body string", () => {
36
+ const raw = { method: "POST", url: "/x", body: "raw-body" };
37
+ const evt = HttpNormalizer.normalize(raw);
38
+
39
+ expect(evt.payload).toContain("raw-body");
40
+ });
41
+
42
+ it("fallback IP si aucune info n'est fournie", () => {
43
+ const raw = { method: "GET", url: "/" };
44
+ const evt = HttpNormalizer.normalize(raw);
45
+
46
+ expect(evt.metadata.ip).toBe("unknown");
47
+ });
48
+
49
+ it("gère un input vide sans planter", () => {
50
+ const evt = HttpNormalizer.normalize({});
51
+ expect(evt.source).toBe("http");
52
+ expect(evt.metadata.method).toBe("UNKNOWN");
53
+ expect(evt.metadata.path).toBe("/");
54
+ });
55
+ });