@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,42 @@
1
+ import { RceRule } from "../rce.rules";
2
+
3
+ describe("RCE Rule", () => {
4
+ const rule = new RceRule();
5
+
6
+ it("détecte un payload RCE", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "http" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ payload: "test; bash -c 'ls'",
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("rce");
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: "hello world",
35
+ },
36
+ ],
37
+ chains: [],
38
+ };
39
+
40
+ expect(rule.applies(ctx)).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,40 @@
1
+ import { RuleRegistry } from "../rule-registry";
2
+
3
+ describe("RuleRegistry", () => {
4
+ beforeEach(() => RuleRegistry.clear());
5
+
6
+ test("registers a rule", () => {
7
+ const rule = {
8
+ id: "r1",
9
+ name: "Test",
10
+ applies: () => false,
11
+ execute: () => [],
12
+ };
13
+
14
+ RuleRegistry.register(rule);
15
+
16
+ expect(RuleRegistry.getAll()).toContain(rule);
17
+ });
18
+
19
+ test("registers multiple rules", () => {
20
+ const r1 = { id: "r1", name: "A", applies: () => false, execute: () => [] };
21
+ const r2 = { id: "r2", name: "B", applies: () => false, execute: () => [] };
22
+
23
+ RuleRegistry.registerMany([r1, r2]);
24
+
25
+ expect(RuleRegistry.getAll()).toHaveLength(2);
26
+ });
27
+
28
+ test("clears registry", () => {
29
+ RuleRegistry.register({
30
+ id: "r1",
31
+ name: "A",
32
+ applies: () => false,
33
+ execute: () => [],
34
+ });
35
+
36
+ RuleRegistry.clear();
37
+
38
+ expect(RuleRegistry.getAll()).toHaveLength(0);
39
+ });
40
+ });
@@ -0,0 +1,42 @@
1
+ import { SsrfRule } from "../ssrf.rules";
2
+
3
+ describe("SSRF Rule", () => {
4
+ const rule = new SsrfRule();
5
+
6
+ it("détecte un payload SSRF", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "http" as const,
12
+ timestamp: Date.now(),
13
+ metadata: {},
14
+ payload: "http://169.254.169.254/latest/meta-data/",
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("ssrf");
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: "https://example.com",
35
+ },
36
+ ],
37
+ chains: [],
38
+ };
39
+
40
+ expect(rule.applies(ctx)).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,40 @@
1
+ import { WafRule } from "../waf.rules";
2
+
3
+ describe("WAF Rule", () => {
4
+ const rule = new WafRule();
5
+
6
+ it("détecte un score WAF élevé", () => {
7
+ const ctx = {
8
+ events: [
9
+ {
10
+ id: "1",
11
+ source: "waf" as const,
12
+ timestamp: Date.now(),
13
+ metadata: { wafScore: 0.9 },
14
+ },
15
+ ],
16
+ chains: [],
17
+ };
18
+
19
+ expect(rule.applies(ctx)).toBe(true);
20
+ const findings = rule.execute(ctx);
21
+ expect(findings.length).toBe(1);
22
+ expect(findings[0].severity).toBe("critical");
23
+ });
24
+
25
+ it("ignore un score WAF nul", () => {
26
+ const ctx = {
27
+ events: [
28
+ {
29
+ id: "1",
30
+ source: "waf" as const,
31
+ timestamp: Date.now(),
32
+ metadata: { wafScore: 0 },
33
+ },
34
+ ],
35
+ chains: [],
36
+ };
37
+
38
+ expect(rule.applies(ctx)).toBe(false);
39
+ });
40
+ });
@@ -0,0 +1,43 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * BASE RULE — Classe abstraite pour toutes les règles
6
+ * Fournit des helpers communs et une structure standardisée.
7
+ * ─────────────────────────────────────────────────────────────
8
+ */
9
+ export abstract class BaseRule implements Rule {
10
+ abstract id: string;
11
+ abstract name: string;
12
+ description?: string;
13
+
14
+ /**
15
+ * Vérifie si la règle doit s’appliquer au contexte.
16
+ * Chaque règle doit implémenter sa propre logique.
17
+ */
18
+ abstract applies(context: ScoringContext): boolean;
19
+
20
+ /**
21
+ * Exécute la règle et retourne un ou plusieurs findings.
22
+ */
23
+ abstract execute(context: ScoringContext): RuleFinding[];
24
+
25
+ /**
26
+ * Helper : sécurise un payload en string.
27
+ */
28
+ protected safeString(value: unknown): string {
29
+ if (typeof value === "string") return value;
30
+ try {
31
+ return JSON.stringify(value);
32
+ } catch {
33
+ return String(value ?? "");
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Helper : teste si un input match au moins un regex.
39
+ */
40
+ protected matchAny(input: string, patterns: RegExp[]): boolean {
41
+ return patterns.some((regex) => regex.test(input));
42
+ }
43
+ }
@@ -0,0 +1,50 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ export class DnsRule implements Rule {
4
+ id = "dns-basic";
5
+ name = "Basic DNS Anomaly Detection";
6
+ description =
7
+ "Détecte des patterns DNS suspects (rebinding, domaines internes, etc.).";
8
+
9
+ applies(context: ScoringContext): boolean {
10
+ return context.events.some(
11
+ (evt) =>
12
+ evt.source === "dns" &&
13
+ typeof evt.payload === "string" &&
14
+ this.containsDnsAnomaly(evt.payload),
15
+ );
16
+ }
17
+
18
+ execute(context: ScoringContext): RuleFinding[] {
19
+ const findings: RuleFinding[] = [];
20
+
21
+ for (const evt of context.events) {
22
+ if (evt.source !== "dns" || typeof evt.payload !== "string") continue;
23
+
24
+ if (this.containsDnsAnomaly(evt.payload)) {
25
+ findings.push({
26
+ ruleId: this.id,
27
+ vulnerability: "dns",
28
+ score: 0.7,
29
+ severity: "medium",
30
+ details: `Anomalie DNS détectée : ${evt.payload}`,
31
+ });
32
+ }
33
+ }
34
+
35
+ return findings;
36
+ }
37
+
38
+ private containsDnsAnomaly(input: string): boolean {
39
+ const patterns = [
40
+ /localhost/i,
41
+ /127\.0\.0\.1/i,
42
+ /\[::1\]/i,
43
+ /internal/i,
44
+ /corp/i,
45
+ /localdomain/i,
46
+ ];
47
+
48
+ return patterns.some((regex) => regex.test(input));
49
+ }
50
+ }
@@ -0,0 +1,72 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * HTTP RULE — Détection d’anomalies HTTP simples
6
+ * Version minimaliste, sans heuristique lourde.
7
+ * Compatible avec le moteur de scoring.
8
+ * ─────────────────────────────────────────────────────────────
9
+ */
10
+ export class HttpRule implements Rule {
11
+ id = "http-basic";
12
+ name = "Basic HTTP Anomaly Detection";
13
+ description =
14
+ "Détecte des anomalies HTTP simples (méthodes, headers, chemins suspects).";
15
+
16
+ /**
17
+ * La règle s’applique si au moins un event HTTP contient une anomalie.
18
+ */
19
+ applies(context: ScoringContext): boolean {
20
+ return context.events.some(
21
+ (evt) => evt.source === "http" && this.containsHttpAnomaly(evt),
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Retourne un ou plusieurs findings HTTP.
27
+ */
28
+ execute(context: ScoringContext): RuleFinding[] {
29
+ const findings: RuleFinding[] = [];
30
+
31
+ for (const evt of context.events) {
32
+ if (evt.source !== "http") continue;
33
+
34
+ if (this.containsHttpAnomaly(evt)) {
35
+ findings.push({
36
+ ruleId: this.id,
37
+ vulnerability: "http",
38
+ score: 0.6,
39
+ severity: "medium",
40
+ details: `Anomalie HTTP détectée sur l’événement ${evt.id}`,
41
+ });
42
+ }
43
+ }
44
+
45
+ return findings;
46
+ }
47
+
48
+ /**
49
+ * Détection simple d’anomalies HTTP.
50
+ */
51
+ private containsHttpAnomaly(evt: any): boolean {
52
+ const method = evt.metadata?.method?.toUpperCase?.() ?? "";
53
+ const path =
54
+ typeof evt.metadata?.path === "string" ? evt.metadata.path : "";
55
+ const headers = evt.metadata?.headers ?? {};
56
+
57
+ // Méthodes dangereuses
58
+ if (["TRACE", "TRACK"].includes(method)) return true;
59
+
60
+ // Path traversal dans l’URL
61
+ if (/\/\.\.\//.test(path)) return true;
62
+
63
+ // Headers suspects (URL rewriting)
64
+ if (typeof headers["x-original-url"] === "string") return true;
65
+ if (typeof headers["x-rewrite-url"] === "string") return true;
66
+
67
+ // Double slash anormal
68
+ if (/\/{2,}/.test(path)) return true;
69
+
70
+ return false;
71
+ }
72
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * RULES AUTO-LOAD — Version avancée et stable
4
+ * Charge automatiquement toutes les règles du moteur.
5
+ * ─────────────────────────────────────────────────────────────
6
+ */
7
+
8
+ import { RuleRegistry } from "./rule-registry";
9
+
10
+ // Import de toutes les règles individuelles
11
+ import { XssRule } from "./xss.rules";
12
+ import { SqliRule } from "./sqli.rules";
13
+ import { SsrfRule } from "./ssrf.rules";
14
+ import { LfiRule } from "./lfi.rule";
15
+ import { PathTraversalRule } from "./path-transversal.rule";
16
+ import { DnsRule } from "./dns.rules";
17
+ import { HttpRule } from "./http.rules";
18
+ import { WafRule } from "./waf.rules";
19
+ import { RceRule } from "./rce.rules";
20
+
21
+ // Enregistrement global
22
+ RuleRegistry.registerMany([
23
+ new XssRule(),
24
+ new SqliRule(),
25
+ new SsrfRule(),
26
+ new LfiRule(),
27
+ new PathTraversalRule(),
28
+ new DnsRule(),
29
+ new HttpRule(),
30
+ new WafRule(),
31
+ new RceRule(),
32
+ ]);
33
+
34
+ // Export optionnel (utile pour debug ou tests)
35
+ export const loadedRules = RuleRegistry.getAll();
@@ -0,0 +1,76 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * LFI RULE — Détection simple basée sur les patterns classiques
6
+ * Version minimaliste, sans heuristique lourde.
7
+ * Compatible avec le moteur de scoring.
8
+ * ─────────────────────────────────────────────────────────────
9
+ */
10
+ export class LfiRule implements Rule {
11
+ id = "lfi-basic";
12
+ name = "Basic LFI Detection";
13
+ description =
14
+ "Détecte les patterns LFI classiques dans les événements normalisés.";
15
+
16
+ /**
17
+ * La règle s’applique si au moins un event contient un payload suspect.
18
+ */
19
+ applies(context: ScoringContext): boolean {
20
+ return context.events.some(
21
+ (evt) => typeof evt.payload === "string" && this.containsLfi(evt.payload),
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Retourne un ou plusieurs findings LFI.
27
+ */
28
+ execute(context: ScoringContext): RuleFinding[] {
29
+ const findings: RuleFinding[] = [];
30
+
31
+ for (const evt of context.events) {
32
+ if (typeof evt.payload !== "string") continue;
33
+
34
+ if (this.containsLfi(evt.payload)) {
35
+ findings.push({
36
+ ruleId: this.id,
37
+ vulnerability: "lfi",
38
+ score: 0.85, // score brut (0 → 1)
39
+ severity: "high",
40
+ details: `Payload LFI détecté : ${evt.payload}`,
41
+ });
42
+ }
43
+ }
44
+
45
+ return findings;
46
+ }
47
+
48
+ /**
49
+ * Détection simple de patterns LFI.
50
+ */
51
+ private containsLfi(input: string): boolean {
52
+ const patterns = [
53
+ // Path traversal
54
+ /\.\.\//i,
55
+ /\.\.\\/, // Windows
56
+
57
+ // Accès à /etc/passwd
58
+ /\/etc\/passwd/i,
59
+
60
+ // Accès à des fichiers sensibles
61
+ /\/proc\/self/i,
62
+ /\/proc\/net/i,
63
+ /\/var\/log/i,
64
+
65
+ // Inclusion PHP
66
+ /php:\/\//i,
67
+ /data:\/\//i,
68
+
69
+ // Encodages
70
+ /%2e%2e%2f/i, // ../ encodé
71
+ /%2fetc%2fpasswd/i,
72
+ ];
73
+
74
+ return patterns.some((regex) => regex.test(input));
75
+ }
76
+ }
@@ -0,0 +1,65 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * PATH TRAVERSAL RULE — Détection simple des patterns ../
6
+ * Version minimaliste, sans heuristique lourde.
7
+ * Compatible avec le moteur de scoring.
8
+ * ─────────────────────────────────────────────────────────────
9
+ */
10
+ export class PathTraversalRule implements Rule {
11
+ id = "path-traversal-basic";
12
+ name = "Basic Path Traversal Detection";
13
+ description =
14
+ "Détecte les patterns de Path Traversal dans les événements normalisés.";
15
+
16
+ /**
17
+ * La règle s’applique si au moins un event contient un payload suspect.
18
+ */
19
+ applies(context: ScoringContext): boolean {
20
+ return context.events.some(
21
+ (evt) =>
22
+ typeof evt.payload === "string" &&
23
+ this.containsPathTraversal(evt.payload),
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Retourne un ou plusieurs findings Path Traversal.
29
+ */
30
+ execute(context: ScoringContext): RuleFinding[] {
31
+ const findings: RuleFinding[] = [];
32
+
33
+ for (const evt of context.events) {
34
+ if (typeof evt.payload !== "string") continue;
35
+
36
+ if (this.containsPathTraversal(evt.payload)) {
37
+ findings.push({
38
+ ruleId: this.id,
39
+ vulnerability: "path_traversal",
40
+ score: 0.75, // score brut (0 → 1)
41
+ severity: "medium",
42
+ details: `Payload Path Traversal détecté : ${evt.payload}`,
43
+ });
44
+ }
45
+ }
46
+
47
+ return findings;
48
+ }
49
+
50
+ /**
51
+ * Détection simple de patterns Path Traversal.
52
+ */
53
+ private containsPathTraversal(input: string): boolean {
54
+ const patterns = [
55
+ /\.\.\//i, // ../
56
+ /\.\.\\/, // ..\ (Windows)
57
+ /%2e%2e%2f/i, // ../ encodé
58
+ /%2e%2e%5c/i, // ..\ encodé
59
+ /\/etc\/passwd/i,
60
+ /c:\\windows\\system32/i,
61
+ ];
62
+
63
+ return patterns.some((regex) => regex.test(input));
64
+ }
65
+ }
@@ -0,0 +1,73 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * RCE RULE — Détection simple de Remote Code Execution
6
+ * Version minimaliste, sans heuristique lourde.
7
+ * Compatible avec le moteur de scoring.
8
+ * ─────────────────────────────────────────────────────────────
9
+ */
10
+ export class RceRule implements Rule {
11
+ id = "rce-basic";
12
+ name = "Basic RCE Detection";
13
+ description =
14
+ "Détecte des patterns classiques de Remote Code Execution dans les payloads.";
15
+
16
+ /**
17
+ * La règle s’applique si au moins un event contient un payload suspect.
18
+ */
19
+ applies(context: ScoringContext): boolean {
20
+ return context.events.some(
21
+ (evt) => typeof evt.payload === "string" && this.containsRce(evt.payload),
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Retourne un ou plusieurs findings RCE.
27
+ */
28
+ execute(context: ScoringContext): RuleFinding[] {
29
+ const findings: RuleFinding[] = [];
30
+
31
+ for (const evt of context.events) {
32
+ if (typeof evt.payload !== "string") continue;
33
+
34
+ if (this.containsRce(evt.payload)) {
35
+ findings.push({
36
+ ruleId: this.id,
37
+ vulnerability: "rce",
38
+ score: 0.95, // score brut (0 → 1)
39
+ severity: "critical",
40
+ details: `Payload RCE détecté : ${evt.payload}`,
41
+ });
42
+ }
43
+ }
44
+
45
+ return findings;
46
+ }
47
+
48
+ /**
49
+ * Détection simple de patterns RCE.
50
+ */
51
+ private containsRce(input: string): boolean {
52
+ const patterns = [
53
+ // Command injection classiques
54
+ /;\s*rm\s+-rf\s+/i,
55
+ /;\s*curl\s+http/i,
56
+ /;\s*wget\s+http/i,
57
+ /;\s*nc\s+-e/i,
58
+ /;\s*bash\s+-c/i,
59
+
60
+ // Fonctions dangereuses
61
+ /system\s*\(/i,
62
+ /exec\s*\(/i,
63
+ /shell_exec\s*\(/i,
64
+ /passthru\s*\(/i,
65
+
66
+ // Substitution de commandes
67
+ /`[^`]+`/i,
68
+ /\$\([^)]*\)/i,
69
+ ];
70
+
71
+ return patterns.some((regex) => regex.test(input));
72
+ }
73
+ }
@@ -0,0 +1,39 @@
1
+ import { Rule } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * RULE REGISTRY — Version avancée, stable
6
+ * Gestion centralisée des règles du moteur.
7
+ * ─────────────────────────────────────────────────────────────
8
+ */
9
+ export class RuleRegistry {
10
+ private static rules: Rule[] = [];
11
+
12
+ /**
13
+ * Enregistre une règle unique.
14
+ */
15
+ static register(rule: Rule): void {
16
+ this.rules.push(rule);
17
+ }
18
+
19
+ /**
20
+ * Enregistre plusieurs règles d'un coup.
21
+ */
22
+ static registerMany(rules: Rule[]): void {
23
+ this.rules.push(...rules);
24
+ }
25
+
26
+ /**
27
+ * Retourne toutes les règles enregistrées.
28
+ */
29
+ static getAll(): Rule[] {
30
+ return [...this.rules];
31
+ }
32
+
33
+ /**
34
+ * Vide la registry (utile pour les tests).
35
+ */
36
+ static clear(): void {
37
+ this.rules = [];
38
+ }
39
+ }
@@ -0,0 +1,69 @@
1
+ import { Rule, RuleFinding, ScoringContext } from "../core/scoring-types";
2
+
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────
5
+ * SQLi RULE — Détection simple basée sur les patterns classiques
6
+ * Version minimaliste, sans heuristique lourde.
7
+ * Compatible avec le moteur de scoring.
8
+ * ─────────────────────────────────────────────────────────────
9
+ */
10
+ export class SqliRule implements Rule {
11
+ id = "sqli-basic";
12
+ name = "Basic SQL Injection Detection";
13
+ description =
14
+ "Détecte les patterns SQLi classiques dans les événements normalisés.";
15
+
16
+ /**
17
+ * La règle s’applique si au moins un event contient un payload suspect.
18
+ */
19
+ applies(context: ScoringContext): boolean {
20
+ return context.events.some(
21
+ (evt) =>
22
+ typeof evt.payload === "string" && this.containsSqli(evt.payload),
23
+ );
24
+ }
25
+
26
+ /**
27
+ * Retourne un ou plusieurs findings SQLi.
28
+ */
29
+ execute(context: ScoringContext): RuleFinding[] {
30
+ const findings: RuleFinding[] = [];
31
+
32
+ for (const evt of context.events) {
33
+ if (typeof evt.payload !== "string") continue;
34
+
35
+ if (this.containsSqli(evt.payload)) {
36
+ findings.push({
37
+ ruleId: this.id,
38
+ vulnerability: "sqli",
39
+ score: 0.9, // score brut (0 → 1)
40
+ severity: "critical",
41
+ details: `Payload SQLi détecté : ${evt.payload}`,
42
+ });
43
+ }
44
+ }
45
+
46
+ return findings;
47
+ }
48
+
49
+ /**
50
+ * Détection simple de patterns SQLi.
51
+ */
52
+ private containsSqli(input: string): boolean {
53
+ const patterns = [
54
+ /('|%27)\s*or\s*1=1/i,
55
+ /('|%27)\s*or\s*'1'='1/i,
56
+ /union\s+select/i,
57
+ /select\s+\*/i,
58
+ /insert\s+into/i,
59
+ /drop\s+table/i,
60
+ /--/i,
61
+ /#/i,
62
+ /sleep\s*\(/i,
63
+ /benchmark\s*\(/i,
64
+ /xp_cmdshell/i,
65
+ ];
66
+
67
+ return patterns.some((regex) => regex.test(input));
68
+ }
69
+ }