express-cybershield 1.0.34

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 ADDED
@@ -0,0 +1,104 @@
1
+ # CyberShield WAF Plugin for Express.js
2
+
3
+ CyberShield WAF Plugin is a real-time web application firewall (WAF) middleware for Express.js applications. Powered by the MERN Cloud AI engine, it provides robust protection against common web vulnerabilities
4
+
5
+ ## Installation
6
+
7
+ You can install the CyberShield Plugin via `npm` or `yarn`.
8
+
9
+ ### Using npm
10
+
11
+ ```bash
12
+ npm install express-cybershield
13
+ ```
14
+
15
+ ### Using yarn
16
+
17
+ ```bash
18
+ yarn add express-cybershield
19
+ ```
20
+
21
+ > **Note**: This package requires Node.js `14.0.0` or higher and Express `4.0.0` or higher.
22
+
23
+ ---
24
+
25
+ ## Basic Usage
26
+
27
+ To integrate the CyberShield WAF into your Express application, simply import the agent, initialize it with your configuration credentials, and add it as a middleware.
28
+
29
+ ```javascript
30
+ const express = require('express');
31
+ const cyberShield = require('express-cybershield');
32
+
33
+ const app = express();
34
+
35
+ // 1. Initialize CyberShield Agent
36
+ cyberShield.init({
37
+ apiKey: process.env.CYBERSHIELD_API_KEY,
38
+ siteUrl: process.env.CYBERSHIELD_SITE_URL,
39
+ emailAddress: process.env.CYBERSHIELD_EMAIL,
40
+ });
41
+ // 2. Attach the WAF middleware globally
42
+ // Ensure you place this BEFORE your route definitions so requests are scanned.
43
+ app.use(cyberShield.waf);
44
+
45
+ // Example route
46
+ app.get('/', (req, res) => {
47
+ res.send('Your application is protected by CyberShield!');
48
+ });
49
+
50
+ const PORT = process.env.PORT || 3000;
51
+ app.listen(PORT, () => {
52
+ console.log(`Server running on port ${PORT}`);
53
+ });
54
+ ```
55
+
56
+ ## Environment Variables
57
+
58
+ All credentials **must** be stored in a `.env` file at the root of your project — never hardcoded in source code.
59
+
60
+ ### 1. Install dotenv
61
+
62
+ ```bash
63
+ npm install dotenv
64
+ ```
65
+
66
+ ### 2. Create a `.env` file
67
+
68
+ ```env
69
+ # .env (never commit this file — add it to .gitignore)
70
+ CYBERSHIELD_API_KEY=your_cybershield_api_key
71
+ CYBERSHIELD_SITE_URL=https://your-domain.com
72
+ CYBERSHIELD_EMAIL=admin@your-domain.com
73
+ CYBERSHIELD_API_URL=https://your-mern-engine-url.com
74
+ ```
75
+
76
+ ### 3. Load `.env` at the very top of your entry file
77
+
78
+ ```javascript
79
+ require('dotenv').config(); // Must be the FIRST line
80
+
81
+ const express = require('express');
82
+ const cyberShield = require('express-cybershield');
83
+ ```
84
+
85
+ > **Important**: Add `.env` to your `.gitignore` to prevent secrets from being committed.
86
+
87
+ ```gitignore
88
+ # .gitignore
89
+ .env
90
+ ```
91
+
92
+ ## Configuration Options
93
+
94
+ When calling `cyberShield.init(config)`, you can pass the following properties in the `config` object:
95
+
96
+ | Option | Type | Required | Description |
97
+ |--------|------|----------|-------------|
98
+ | `apiKey` | `string` | **Yes** | Your unique CyberShield API Key. |
99
+ | `siteUrl` | `string` | **Yes** | The base URL of the site being protected. |
100
+ | `emailAddress` | `string` | **Yes** | Administrator email address for alert notifications. |
101
+
102
+
103
+ ## Support & Issues
104
+ For issues and feature requests, please visit the [GitHub repository](https://github.com/securas/express-cybershield/issues) or contact [SECURAS](https://securas.cloud).
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "express-cybershield",
3
+ "version": "1.0.34",
4
+ "description": "CyberShield WAF Agent for Express.js — Real-time web application firewall powered by MERN Cloud AI engine. Protects against SQL injection, XSS, brute-force, and more.",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "cybershield",
11
+ "waf",
12
+ "security",
13
+ "firewall",
14
+ "express",
15
+ "middleware",
16
+ "xss",
17
+ "sql-injection",
18
+ "bot-protection",
19
+ "threat-detection",
20
+ "web-security",
21
+ "express-middleware"
22
+ ],
23
+ "author": "SECURAS <contact@securas.cloud> (https://securas.cloud)",
24
+ "license": "ISC",
25
+ "type": "commonjs",
26
+ "engines": {
27
+ "node": ">=14.0.0"
28
+ },
29
+ "files": [
30
+ "src/",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "dependencies": {
35
+ "axios": "^1.13.6"
36
+ },
37
+ "peerDependencies": {
38
+ "express": ">=4.0.0"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/securas/express-cybershield"
43
+ },
44
+ "homepage": "https://securas.cloud",
45
+ "bugs": {
46
+ "url": "https://github.com/securas/express-cybershield/issues"
47
+ }
48
+ }
@@ -0,0 +1,197 @@
1
+ const axios = require('axios');
2
+ const context = require('../core/cybershield-context');
3
+
4
+ /**
5
+ * Client API CyberShield
6
+ *
7
+ * Fait l'interface entre l'agent Node.js local et le moteur centralisé MERN.
8
+ */
9
+ class CyberShieldApiClient {
10
+ constructor() {
11
+ this.apiClient = axios.create({
12
+ timeout: 5000 // 5s max — au-delà, Fail-Open immédiat
13
+ });
14
+
15
+ // Cache interne pour checkStatus — évite un appel MERN à chaque requête
16
+ this._statusCache = { valid: null, expiresAt: 0 };
17
+ this._STATUS_TTL_MS = 30 * 1000; // Re-vérifie toutes les 30 secondes
18
+ // Intercepteur dynamique — résout l'URL MERN depuis le contexte (init() > env var)
19
+ this.apiClient.interceptors.request.use(config => {
20
+ config.baseURL = context.apiUrl || process.env.CYBERSHIELD_API_URL;
21
+ return config;
22
+ });
23
+ }
24
+ /**
25
+ * Authentification initiale du SDK.
26
+ * Correspond à la route "exports.getApiPlugin" sur votre code MERN.
27
+ * Appelé une seule fois au démarrage de l'application cliente.
28
+ */
29
+ async wafVerify() {
30
+ try {
31
+ if (!context.apiKey || !context.siteUrl || !context.emailAddress) {
32
+ console.error('[CyberShield] Clé API, URL ou email manquant. Bouclier inactif.');
33
+ context.isActive = false;
34
+ return false;
35
+ }
36
+ // Construction de la Query String conformément à getApiPlugin
37
+ const queryParams = new URLSearchParams({
38
+ api_key: context.apiKey,
39
+ site_url: context.siteUrl,
40
+ email_address: context.emailAddress
41
+ });
42
+ // GET vers la route d'activation/installation
43
+
44
+ const response = await this.apiClient.get(`/api/checkApiKeyPlugin?${queryParams.toString()}`);
45
+ // Validation de la réponse
46
+ if (response.data && response.data.valid) {
47
+ console.log('[CyberShield] Connexion MERN réussie. Bouclier activé.');
48
+ console.log(`[CyberShield] Message: ${response.data.message}`);
49
+
50
+ // On alimente le contexte (la RAM) avec les informations du MERN
51
+ // updateStateFromMERN gère seul l'état isActive pour éviter toute redondance
52
+ context.updateStateFromMERN(response.data);
53
+ return true;
54
+ } else {
55
+ const errorMsg = response.data ? response.data.message : 'WAF désactivé';
56
+ console.warn(`[CyberShield] Authentification MERN refusée. ${errorMsg}`);
57
+ context.isActive = false;
58
+ return false;
59
+ }
60
+
61
+ } catch (error) {
62
+ if (error.response && error.response.data) {
63
+ console.error(`[CyberShield] Erreur de communication MERN (Initialisation) - ALERTE:`, error.response.data);
64
+ console.log(error.response.data);
65
+ } else {
66
+ console.error('[CyberShield] Erreur de communication MERN (Initialisation):', error.message);
67
+ }
68
+ // Politique "Fail-Open" : En cas d'erreur réseau, on ne bloque pas le site client.
69
+ // Bien qu'il vaille mieux laisser le site tourner, si la validation n'a jamais marché, le WAF reste inactif
70
+ context.isActive = false;
71
+ return false;
72
+ }
73
+ }
74
+ /**
75
+ * Vérification de l'abonnement par le Moteur MERN (Mode Synchrone).
76
+ * Correspond à la route "exports.getApiActive" sur votre code MERN.
77
+ * Appelé AVANT chaque requête du visiteur pour s'assurer que l'abonnement est actif.
78
+ */
79
+ async checkStatus() {
80
+ // --- Cache 30s : évite un appel MERN supplémentaire à chaque requête ---
81
+ if (this._statusCache.valid !== null && Date.now() < this._statusCache.expiresAt) {
82
+ return this._statusCache.valid;
83
+ }
84
+
85
+ try {
86
+ if (!context.apiKey) return false;
87
+
88
+ const queryParams = new URLSearchParams({
89
+ api_key: context.apiKey,
90
+ site_url: context.siteUrl
91
+ });
92
+ if (context.emailAddress) {
93
+ queryParams.append('email_address', context.emailAddress);
94
+ }
95
+
96
+ // GET vers la route de validation
97
+ const response = await this.apiClient.get(`/api/checkApiKeyValide?${queryParams.toString()}`);
98
+
99
+ if (response.data && response.data.valid) {
100
+ console.log(`[CyberShield] Abonnement: ${response.data.message}`);
101
+ context.isActive = true;
102
+ this._statusCache = { valid: true, expiresAt: Date.now() + this._STATUS_TTL_MS };
103
+ return true;
104
+ } else {
105
+ console.warn(`[CyberShield] Abonnement: ${response.data.message}`);
106
+ context.isActive = false;
107
+ this._statusCache = { valid: false, expiresAt: Date.now() + this._STATUS_TTL_MS };
108
+ return false;
109
+ }
110
+ } catch (error) {
111
+ // Si le MERN renvoie spécifiquement une erreur 4xx (ex: 403 Expired)
112
+ if (error.response && error.response.data) {
113
+ console.warn(`[CyberShield] ALERTE: ${error.response.data.message || 'Abonnement expiré.'}`);
114
+ context.isActive = false;
115
+ this._statusCache = { valid: false, expiresAt: Date.now() + this._STATUS_TTL_MS };
116
+ return false;
117
+ }
118
+
119
+ // Fail-Open sur erreur réseau totale (MERN injoignable) —
120
+ // On n'invalide PAS le cache ici pour ne pas spammer le MERN en cas de panne
121
+ console.warn('[CyberShield] checkStatus: MERN injoignable — Fail-Open temporaire.');
122
+ context.isActive = false;
123
+ return false;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Analyse de la requête par le Moteur MERN (Mode Synchrone).
129
+ * Correspond à la route "exports.addLogEntryTest" sur votre code MERN.
130
+ * Appelé à chaque requête du visiteur pour que le MERN prenne la décision.
131
+ *
132
+ */
133
+ async analyzeRequest(requestData) {
134
+ try {
135
+ if (!context.apiKey || !context.siteUrl) return { threat_detected: false };
136
+
137
+ // Prepare safe headers
138
+ const safeHeaders = { ...(requestData.headers || {}) };
139
+ delete safeHeaders.cookie;
140
+ delete safeHeaders.Cookie;
141
+ const payload = {
142
+ ip: requestData.ip,
143
+ user_agent: requestData.headers?.['user-agent'] || 'Unknown',
144
+ query_string: requestData.query_string || '',
145
+ query: requestData.query || {}, // Structured query params (was dropped before)
146
+ request_method: requestData.method,
147
+ body: requestData.body || {},
148
+ headers: safeHeaders,
149
+
150
+ // send cookies separately as data, not as real headers
151
+ cookies: requestData.headers?.cookie || null,
152
+
153
+ site_url: context.siteUrl,
154
+ domain: (() => {
155
+ try {
156
+ return new URL(context.siteUrl).hostname;
157
+ } catch (e) {
158
+ return context.siteUrl;
159
+ }
160
+ })(),
161
+ page: requestData.url
162
+ };
163
+
164
+ if (process.env.CYBERSHIELD_DEBUG === 'true') {
165
+ console.log("[CyberShield DEBUG] PAYLOAD WAF ENVOYÉ AU MERN :", JSON.stringify(payload, null, 2));
166
+ }
167
+
168
+
169
+ // Appel POST vers votre API MERN
170
+ const response = await this.apiClient.post('/log/logEntryTest', payload, {
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ 'x-api-key': context.apiKey,
174
+ },
175
+ });
176
+
177
+
178
+ if (process.env.CYBERSHIELD_DEBUG === 'true') {
179
+ console.log("[CyberShield DEBUG] analyzeRequest response status:", response?.status);
180
+ }
181
+
182
+ if (response) {
183
+ return response.data; // { threat_detected: true/false, option: {...}, setting: '...' }
184
+ }
185
+ return null; // Réponse vide inattendue → Fail-Open dans le WAF
186
+
187
+ } catch (error) {
188
+ console.error('[CyberShield] Impossible de joindre le MERN pour l\'analyse. Fail-Open.');
189
+ // Retourne null → le WAF détecte !result et déclenche next() proprement
190
+ // (évite le cas où toutes les flags sont undefined/false et rien ne bloque)
191
+ return null;
192
+ }
193
+ }
194
+ }
195
+
196
+ const client = new CyberShieldApiClient();
197
+ module.exports = client;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Le "Cerveau" de l'Agent CyberShield.
3
+ */
4
+ class CyberShieldContext {
5
+ constructor() {
6
+ if (CyberShieldContext.instance) {
7
+ return CyberShieldContext.instance;
8
+ }
9
+
10
+ // --- Paramètres d'Identité ---
11
+ this.apiKey = null;
12
+ this.emailAddress = null;
13
+ this.siteUrl = null;
14
+ this.apiUrl = null; // URL du moteur MERN (peut venir de init() ou de process.env)
15
+
16
+ // --- Etat Global (Master Switch) ---
17
+ this.isActive = false;
18
+
19
+ // --- Configuration personnalisée du client JS ---
20
+ this.ignoreRoutes = [];
21
+
22
+ CyberShieldContext.instance = this;
23
+ }
24
+ /**
25
+ * Initialise le contexte lorsque le SDK est lancé par le client.
26
+ */
27
+ init(configData) {
28
+ if (configData.apiKey) {
29
+ this.apiKey = configData.apiKey;
30
+ }
31
+ if (configData.emailAddress) {
32
+ this.emailAddress = configData.emailAddress;
33
+ }
34
+ if (configData.siteUrl) {
35
+ this.siteUrl = configData.siteUrl;
36
+ }
37
+ // apiUrl : init() config takes priority, then falls back to env var
38
+ if (configData.apiUrl) {
39
+ this.apiUrl = configData.apiUrl;
40
+ } else if (process.env.CYBERSHIELD_API_URL) {
41
+ this.apiUrl = process.env.CYBERSHIELD_API_URL;
42
+ }
43
+
44
+ if (Array.isArray(configData.ignoreRoutes)) {
45
+ this.ignoreRoutes = configData.ignoreRoutes;
46
+ }
47
+ }
48
+ /**
49
+ * Met à jour l'état de sécurité après la réponse du moteur MERN
50
+ */
51
+ updateStateFromMERN(apiResponse) {
52
+ if (!apiResponse) return;
53
+ // Mise à jour de l'état global du bouclier selon la validité stricte renvoyée par le MERN
54
+ this.isActive = apiResponse.valid === true;
55
+ }
56
+ }
57
+
58
+ // Export de l'instance Singleton
59
+ const context = new CyberShieldContext();
60
+ module.exports = context;
package/src/index.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Plugin Name: CyberShield Firewall
3
+ * Plugin URI: https://docs.securas.cloud
4
+ * Description: CyberShield Web Application Firewall for Express.js.
5
+ * Requires Node: 14.0.0
6
+ * Version: 1.0.0
7
+ * Author: Securas
8
+ * Author URI: https://docs.securas.cloud
9
+ * License: ISC
10
+ * since 30/11/2024
11
+ * version 1.0.0
12
+ */
13
+
14
+ const context = require('./core/cybershield-context');
15
+ const apiClient = require('./api/cybershield-api');
16
+ const waf = require('./middlewares/cybershield-waf');
17
+
18
+ /**
19
+ * Fichier Principal du SDK Node.js Express-CyberShield
20
+ */
21
+
22
+ class CyberShieldAgent {
23
+ constructor() {
24
+ this.waf = waf;
25
+ this.context = context;
26
+ this.isInitialized = false;
27
+ }
28
+
29
+ /**
30
+ * Initialise l'agent sur le serveur du client.
31
+ * @param {Object} config - { apiKey, siteUrl, emailAddress, ignoreRoutes, customBypassLogic }
32
+ */
33
+ init(config = {}) {
34
+ if (this.isInitialized) {
35
+ console.warn('[CyberShield] Attention: L\'agent a déjà été initialisé.');
36
+ return this;
37
+ }
38
+
39
+ console.log('[CyberShield] Initialisation de l\'Agent Express-CyberShield...');
40
+
41
+ // 1. Initialisation de la mémoire RAM
42
+ context.init(config);
43
+
44
+ // 2. Premier contact avec votre API MERN (`getApiPlugin`) de façon non bloquante
45
+ apiClient.wafVerify().then(authSuccess => {
46
+ if (authSuccess) {
47
+ console.log('[CyberShield] ✓ Bouclier MERN activé en mode de vérification Synchrone.');
48
+ }
49
+ });
50
+
51
+ this.isInitialized = true;
52
+ return this; // Permet de faire du chaînage si besoin
53
+ }
54
+ }
55
+ // On exporte un singleton global
56
+ const agent = new CyberShieldAgent();
57
+ module.exports = agent;
@@ -0,0 +1,197 @@
1
+ const context = require('../core/cybershield-context');
2
+ const apiClient = require('../api/cybershield-api');
3
+ const { sanitizeInput, isSearchBot, cleanParameterPollution, checkJwtIntegrity } = require('../utils/cybershield-utils');
4
+ const { getTelemetryInfo } = require('../telemetry/cybershield-telemetry');
5
+
6
+ /**
7
+ * Cœur d'analyse WAF (MERN)
8
+ */
9
+ const cybershieldWafCore = async (req, res, next) => {
10
+
11
+ // -------------------------------------------------------------
12
+ // ETAPE 1 : VÉRIFICATION MERN STRICTE (Temps-Réel par requête)
13
+ // -------------------------------------------------------------
14
+ // Demande client : On vérifie systématiquement auprès du MERN si la licence
15
+ // est valide AVANT chaque visiteur, au détriment de la performance.
16
+ const isStillValid = await apiClient.checkStatus();
17
+ if (!isStillValid || !context.isActive) {
18
+ return next(); // L'abonnement n'est plus actif ou injoignable -> Fail-Open
19
+ }
20
+ // -------------------------------------------------------------
21
+ // ETAPE 3 : ENVOI AU MERN POUR ANALYSE (Mode Synchrone)
22
+ // -------------------------------------------------------------
23
+ try {
24
+ const rawIp = req.headers['x-forwarded-for'] || req.ip || req.socket.remoteAddress || '127.0.0.1';
25
+ const requestData = {
26
+ ip: (rawIp.includes(',') ? rawIp.split(',')[0] : rawIp).trim(),
27
+ method: req.method,
28
+ url: req.originalUrl,
29
+ headers: req.headers,
30
+ body: req.body,
31
+ query: req.query,
32
+ query_string: (() => {
33
+ try {
34
+ return req.originalUrl.includes('?') ? decodeURIComponent(req.originalUrl.split('?')[1]) : '';
35
+ } catch (e) {
36
+ return req.originalUrl.split('?')[1] || '';
37
+ }
38
+ })()
39
+ };
40
+
41
+ // Appel synchrone vers le moteur MERN (addLogEntryTest)
42
+ const result = await apiClient.analyzeRequest(requestData);
43
+ if (!result || typeof result !== 'object') {
44
+ return next();
45
+ }
46
+ // 4.1 En-tête XSS Protection
47
+ if (result.option && parseInt(result.option.xss_protection) === 1) {
48
+ res.setHeader('X-XSS-Protection', '1');
49
+ }
50
+ // 4.2 Masquer la version du serveur
51
+ if (result.option && parseInt(result.option.hide_node_ver) === 1) {
52
+ res.setHeader('X-Powered-By', 'SECURAS');
53
+ }
54
+ // 4.3 Protection Clickjacking
55
+ if (result.option && parseInt(result.option.clickjacking_protection) === 1) {
56
+ res.setHeader('X-Frame-Options', 'sameorigin');
57
+ }
58
+ // 4.4 Protection MIME Sniffing
59
+ if (result.option && parseInt(result.option.mimemis_protection) === 1) {
60
+ res.setHeader('X-Content-Type-Options', 'nosniff');
61
+ }
62
+ // 4.5 Forcer HTTPS (HSTS)
63
+ if (result.option && parseInt(result.option.force_secure_conn) === 1) {
64
+ res.setHeader('Strict-Transport-Security', 'max-age=15552000; preload');
65
+ }
66
+ // 4.6 Referrer-Policy — Empêche la fuite d'URL vers des sites tiers
67
+ if (result.option && parseInt(result.option.referrer_policy) === 1) {
68
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
69
+ }
70
+ // 4.7 Permissions-Policy — Désactive les fonctionnalités du navigateur
71
+ if (result.option && parseInt(result.option.permissions_policy) === 1) {
72
+ res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
73
+ }
74
+ // 4.8 DNS Prefetch Control — Empêche le navigateur de résoudre les domaines externes
75
+ if (result.option && parseInt(result.option.dns_prefetch_control) === 1) {
76
+ res.setHeader('X-DNS-Prefetch-Control', 'off');
77
+ }
78
+
79
+ // 4.9 Sanitisation des entrées (Nettoyage XSS dans req.body / req.query)
80
+ // Équivalent PHP : $_POST = cybershield_sanitize($_POST);
81
+ if (result.option && parseInt(result.option.sanitize_input) === 1) {
82
+ if (req.body && typeof req.body === 'object') {
83
+ req.body = sanitizeInput(req.body);
84
+ }
85
+ if (req.query && typeof req.query === 'object') {
86
+ req.query = sanitizeInput(req.query);
87
+ }
88
+ if (req.cookies && typeof req.cookies === 'object') {
89
+ req.cookies = sanitizeInput(req.cookies);
90
+ }
91
+ }
92
+
93
+ // 4.10 Blocage des bots de recherche (si option désactivée)
94
+ if (result.option && parseInt(result.option.allow_search_bots) === 0) {
95
+ const userAgent = req.headers['user-agent'] || '';
96
+ if (isSearchBot(userAgent)) {
97
+ res.setHeader('X-Robots-Tag', 'noindex, nofollow');
98
+ }
99
+ }
100
+
101
+ // 4.13 HTTP Parameter Pollution (HPP)
102
+ if (result.option && parseInt(result.option.hpp_protection) === 1) {
103
+ if (req.query && typeof req.query === 'object') {
104
+ req.query = cleanParameterPollution(req.query);
105
+ }
106
+ }
107
+
108
+ // 4.14 Protection JWT Integrity
109
+ if (result.option && parseInt(result.option.jwt_protection) === 1) {
110
+ const authHeader = req.headers['authorization'];
111
+ if (!checkJwtIntegrity(authHeader)) {
112
+ const targetUrl = (result.setting && typeof result.setting === 'string' && result.setting.length > 0)
113
+ ? result.setting
114
+ : 'https://block.cybershield.cloud/';
115
+
116
+ if (req.headers['sec-fetch-mode'] === 'navigate') {
117
+ return res.redirect(302, targetUrl);
118
+ }
119
+ return res.status(403).json({
120
+ blocked: true,
121
+ redirect: targetUrl,
122
+ error: "Forbidden",
123
+ message: "CyberShield: Token JWT malformé ou dangereux détecté."
124
+ });
125
+ }
126
+ }
127
+
128
+ // -----------------------------------------------------------
129
+ // ETAPE 5 : DÉCISION FINALE (Bloquer ou Laisser Passer)
130
+ // -----------------------------------------------------------
131
+
132
+ const isBanned = result.banIP;
133
+ const isCountryBlocked = result.blockCountry;
134
+ const isWhiteIP = result.whiteIP;
135
+ const isWhiteCountry = result.whiteCountry;
136
+ const isThreat = result.threat_detected;
137
+
138
+ // Condition de blocage
139
+ if (
140
+ isBanned ||
141
+ isCountryBlocked ||
142
+ (isThreat && !isWhiteIP && !isWhiteCountry)
143
+ ) {
144
+ console.log(`[CyberShield WAF] Blocage sur ${req.originalUrl} | IP=${requestData.ip} | Ban=${isBanned} Country=${isCountryBlocked} Threat=${isThreat}`);
145
+ // URL configurée dans le Dashboard (ou Fallback si introuvable)
146
+ const targetUrl = (result.setting && typeof result.setting === 'string' && result.setting.length > 0)
147
+ ? result.setting
148
+ : 'https://block.cybershield.cloud/';
149
+ // 1. Navigation classique HTML -> Redirection 302 HTTP
150
+ if (req.headers['sec-fetch-mode'] === 'navigate') {
151
+ return res.redirect(302, targetUrl);
152
+ }
153
+ // 2. Requête API/Angular (AJAX) -> Renvoie un JSON 403 avec l'instruction de redirect
154
+ return res.status(403).json({
155
+ blocked: true,
156
+ redirect: targetUrl,
157
+ error: "Forbidden",
158
+ message: "CyberShield a bloqué cette requête suite à l'analyse de sécurité."
159
+ });
160
+ }
161
+ // Tout est sain → on laisse le backend du client traiter la requête
162
+ next();
163
+ } catch (error) {
164
+ console.error('[CyberShield] Erreur interne dans le WAF (Fail-Open utilisé):', error.message);
165
+ next();
166
+ }
167
+ };
168
+
169
+ /**
170
+ * Wrapper Principal
171
+ * 1. Intercepte la télémétrie sans restriction
172
+ * 2. Bypass les routes ignorées
173
+ * 3. Délègue au cœur du WAF (analyse MERN)
174
+ */
175
+ const cybershieldWaf = (req, res, next) => {
176
+ // INTERCEPTION TÉLÉMÉTRIE
177
+ if (req.method === 'GET' && req.path.includes('/siteinfo.js')) {
178
+ const requestedKey = req.path.split('/').pop();
179
+ if (requestedKey === context.apiKey && context.apiKey) {
180
+ return res.json(getTelemetryInfo());
181
+ } else {
182
+ return res.status(401).json({ success: false, reason: "API key was incorrect" });
183
+ }
184
+ }
185
+
186
+ // ROUTES IGNORÉES
187
+ if (context.ignoreRoutes && Array.isArray(context.ignoreRoutes)) {
188
+ for (const route of context.ignoreRoutes) {
189
+ if (typeof route === 'string' && req.path.startsWith(route)) {
190
+ return next(); // Bypass
191
+ }
192
+ }
193
+ }
194
+ cybershieldWafCore(req, res, next);
195
+ };
196
+
197
+ module.exports = cybershieldWaf;
@@ -0,0 +1,113 @@
1
+ const os = require('os');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const context = require('../core/cybershield-context');
5
+
6
+ /**
7
+ * Rassemble les informations techniques du serveur Node.js et de l'environnement
8
+ * de manière équivalente à class-cybershield.php (qui lit PHP_INFO, SERVER_INFO etc.)
9
+ */
10
+ function getTelemetryInfo() {
11
+ let modulesList = [];
12
+
13
+ try {
14
+ // Tente de lire les dépendances dans le package.json du projet client
15
+ const pkgPath = path.resolve(process.cwd(), 'package.json');
16
+ if (fs.existsSync(pkgPath)) {
17
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
18
+ modulesList = Object.keys(pkg.dependencies || {});
19
+ }
20
+ } catch (e) {
21
+ console.error('[CyberShield] Erreur lors de la lecture du package.json pour la télémétrie', e.message);
22
+ }
23
+
24
+ // POSIX infos (disponible sous Linux/Mac uniquement)
25
+ let uid = null, groups = [];
26
+ if (typeof process.getuid === 'function') {
27
+ uid = process.getuid();
28
+ }
29
+ if (typeof process.getgroups === 'function') {
30
+ groups = process.getgroups();
31
+ }
32
+
33
+ // Storage Info (Utilise fs.statfsSync si Node >= 19.6, sinon N/A)
34
+ let storageInfo = { free: "N/A", total: "N/A" };
35
+ try {
36
+ if (typeof fs.statfsSync === 'function') {
37
+ // Racine / sur Linux ou MacOS, ou le disque actuel pour Windows (par ex via '/')
38
+ const stats = fs.statfsSync('/');
39
+ const freeGb = (stats.bfree * stats.bsize) / (1024 * 1024 * 1024);
40
+ const totalGb = (stats.blocks * stats.bsize) / (1024 * 1024 * 1024);
41
+ storageInfo.free = `${freeGb.toFixed(2)} GB`;
42
+ storageInfo.total = `${totalGb.toFixed(2)} GB`;
43
+ }
44
+ } catch (e) {
45
+ // Fallback muet
46
+ }
47
+
48
+ // Audit Info (Vérification des droits d'écriture)
49
+ let audit = {
50
+ test_writable_document_root: { isWritable: false },
51
+ test_writable_temp_dir: { isWritable: false }
52
+ };
53
+ try {
54
+ fs.accessSync(process.cwd(), fs.constants.W_OK);
55
+ audit.test_writable_document_root.isWritable = true;
56
+ } catch (e) { }
57
+
58
+ try {
59
+ fs.accessSync(os.tmpdir(), fs.constants.W_OK);
60
+ audit.test_writable_temp_dir.isWritable = true;
61
+ } catch (e) { }
62
+
63
+ // Utilitaire pour récupérer l'IP locale (pour NODE_INFO et SERVER_INFO)
64
+ const ip = (() => {
65
+ const nets = os.networkInterfaces();
66
+ for (const name of Object.keys(nets)) {
67
+ for (const net of nets[name]) {
68
+ if (net.family === 'IPv4' && !net.internal) {
69
+ return net.address;
70
+ }
71
+ }
72
+ }
73
+ return "127.0.0.1";
74
+ })();
75
+
76
+ // Structure NODE_INFO et SERVER_INFO demandée
77
+ const nodeInfo = {
78
+ url: context.siteUrl,
79
+ hostname: os.hostname(),
80
+ ip: ip,
81
+ os: os.type() + " " + os.release(),
82
+ arch: os.arch(),
83
+ platform: os.platform(),
84
+ ver: process.version,
85
+ soft: `Node.js/${process.version}`,
86
+ port: process.env.PORT || "Unknown",
87
+ protocol: context.siteUrl && context.siteUrl.includes('https') ? 'https://' : 'http://',
88
+ uptime: process.uptime(),
89
+ cpus: os.cpus().length,
90
+ env: process.env.NODE_ENV || "development",
91
+ pid: process.pid,
92
+ cwd: process.cwd(),
93
+ temp_dir: os.tmpdir(),
94
+ posix_getuid: uid,
95
+ posix_getgroups: groups
96
+ };
97
+
98
+ // Résultat JSON exact
99
+ return {
100
+ api_key: context.apiKey,
101
+ NODE_INFO: nodeInfo,
102
+ SERVER_INFO: nodeInfo,
103
+ MODULES: modulesList,
104
+ MEMORY_INFO: {
105
+ free: `${(os.freemem() / (1024 * 1024 * 1024)).toFixed(2)} GB`,
106
+ total: `${(os.totalmem() / (1024 * 1024 * 1024)).toFixed(2)} GB`
107
+ },
108
+ STORAGE_INFO: storageInfo,
109
+ audit: audit
110
+ };
111
+ }
112
+
113
+ module.exports = { getTelemetryInfo };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Fonctions Utilitaires CyberShield
3
+ * Équivalent Node.js de `global-functions.php` — Fonctions de protection uniquement.
4
+ */
5
+
6
+ // NETTOYAGE HTML BRUT
7
+
8
+ function cleanInput(data) {
9
+ if (typeof data !== 'string') return data;
10
+ let output = data;
11
+ output = output.replace(/<script[^>]*?>.*?<\/script>/gis, ''); // Strip javascript
12
+ output = output.replace(/<[\/\!]*?[^<>]*?>/gis, ''); // Strip HTML tags
13
+ output = output.replace(/<style[^>]*?>.*?<\/style>/gis, ''); // Strip style tags
14
+ output = output.replace(/<!--[\s\S]*?--[ \t\n\r]*>/g, ''); // Strip comments
15
+ return output;
16
+ }
17
+
18
+ // SANITISATION COMPLÈTE
19
+
20
+ function sanitizeInput(data) {
21
+ if (Array.isArray(data)) {
22
+ return data.map(item => sanitizeInput(item));
23
+ }
24
+ if (typeof data === 'object' && data !== null) {
25
+ const sanitized = {};
26
+ for (const [key, value] of Object.entries(data)) {
27
+ sanitized[key] = sanitizeInput(value);
28
+ }
29
+ return sanitized;
30
+ }
31
+ if (typeof data === 'string') {
32
+ let output = data;
33
+ output = output.replace(/"/g, '').replace(/'/g, ''); // str_replace guillemets
34
+ output = cleanInput(output); // cybershield_clean_input
35
+ output = output // htmlentities(ENT_QUOTES)
36
+ .replace(/&/g, '&amp;')
37
+ .replace(/</g, '&lt;')
38
+ .replace(/>/g, '&gt;')
39
+ .replace(/"/g, '&quot;')
40
+ .replace(/'/g, '&#x27;');
41
+ return output;
42
+ }
43
+ return data; // nombres, booléens, null → inchangés
44
+ }
45
+
46
+ // DÉTECTION DES BOTS
47
+
48
+ const SEARCH_BOT_LIST = [
49
+ 'Googlebot', 'Baiduspider', 'YandexBot', 'ia_archiver',
50
+ 'R6_FeedFetcher', 'NetcraftSurveyAgent',
51
+ 'Sogou web spider', 'bingbot', 'Yahoo! Slurp',
52
+ 'facebookexternalhit', 'PrintfulBot', 'msnbot',
53
+ 'Twitterbot', 'UnwindFetchor', 'urlresolver'
54
+ ];
55
+
56
+ /**
57
+ * Vérifie si le User-Agent est un bot de moteur de recherche connu.
58
+ */
59
+ function isSearchBot(userAgent) {
60
+ if (!userAgent) return false;
61
+ const ua = userAgent.toLowerCase();
62
+ return SEARCH_BOT_LIST.some(bot => ua.includes(bot.toLowerCase()));
63
+ }
64
+
65
+
66
+
67
+
68
+ // HTTP PARAMETER POLLUTION (HPP) BLOCKEUR
69
+
70
+ /**
71
+ * Express transforme "?id=1&id=2" en tableau ["1", "2"].
72
+ * Cela fait cracher beaucoup de bases de données (MongoDB, SQL) qui attendent un String.
73
+ * Cette fonction nettoie `req.query` pour ne garder que la dernière valeur.
74
+ *
75
+ */
76
+ function cleanParameterPollution(queryObject) {
77
+ if (!queryObject || typeof queryObject !== 'object') return queryObject;
78
+
79
+ const cleanedQuery = {};
80
+ for (const [key, value] of Object.entries(queryObject)) {
81
+ if (Array.isArray(value)) {
82
+ // Ne garder que la dernière occurrence (comportement PHP natif)
83
+ cleanedQuery[key] = value[value.length - 1];
84
+ } else {
85
+ cleanedQuery[key] = value;
86
+ }
87
+ }
88
+ return cleanedQuery;
89
+ }
90
+
91
+ // JWT INTEGRITY CHECKER (Vérification de sécurité des Tokens)
92
+
93
+ /**
94
+ * Vérifie si le header "Authorization: Bearer <token>" contient un JWT malveillant.
95
+ * Ne vérifie pas la cryptographie (le SDK n'a pas la clé secrète du client),
96
+ * mais bloque les attaques structurelles communes (ex: faille "alg: none").
97
+ *
98
+ * @returns {boolean} true si sain, false si attaque détectée
99
+ */
100
+ function checkJwtIntegrity(authHeader) {
101
+ if (!authHeader || typeof authHeader !== 'string') return true;
102
+
103
+ if (!authHeader.startsWith('Bearer ')) return true;
104
+
105
+ const token = authHeader.split(' ')[1];
106
+ if (!token) return true;
107
+
108
+ const parts = token.split('.');
109
+
110
+ // Un JWT classique doit OBLIGATOIREMENT avoir 3 parties (Header, Payload, Signature)
111
+ if (parts.length !== 3) {
112
+ return false; // Malformé (Tentative de crash du parseur JWT backend)
113
+ }
114
+
115
+ try {
116
+ // Prévention de la faille critique "alg: none" (Token Bypass)
117
+ // On décode la 1ère partie du JWT (Base64)
118
+ const headerBuffer = Buffer.from(parts[0], 'base64');
119
+ const headerStr = headerBuffer.toString('utf-8');
120
+ const headerJson = JSON.parse(headerStr);
121
+
122
+ if (!headerJson.alg || headerJson.alg.toLowerCase() === 'none') {
123
+ return false; // Attaque d'usurpation d'identité interceptée
124
+ }
125
+
126
+ } catch (e) {
127
+ // Si le Base64 est invalide ou que ce n'est pas du JSON, c'est du Fuzzing
128
+ return false;
129
+ }
130
+
131
+ return true; // JWT structurellement sain
132
+ }
133
+
134
+ module.exports = {
135
+ cleanInput,
136
+ sanitizeInput,
137
+ isSearchBot,
138
+ SEARCH_BOT_LIST,
139
+ cleanParameterPollution,
140
+ checkJwtIntegrity
141
+ };