zen-ai-pentest 2.0.0__py3-none-any.whl

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 (75) hide show
  1. agents/__init__.py +28 -0
  2. agents/agent_base.py +239 -0
  3. agents/agent_orchestrator.py +346 -0
  4. agents/analysis_agent.py +225 -0
  5. agents/cli.py +258 -0
  6. agents/exploit_agent.py +224 -0
  7. agents/integration.py +211 -0
  8. agents/post_scan_agent.py +937 -0
  9. agents/react_agent.py +384 -0
  10. agents/react_agent_enhanced.py +616 -0
  11. agents/react_agent_vm.py +298 -0
  12. agents/research_agent.py +176 -0
  13. api/__init__.py +11 -0
  14. api/auth.py +123 -0
  15. api/main.py +1027 -0
  16. api/schemas.py +357 -0
  17. api/websocket.py +97 -0
  18. autonomous/__init__.py +122 -0
  19. autonomous/agent.py +253 -0
  20. autonomous/agent_loop.py +1370 -0
  21. autonomous/exploit_validator.py +1537 -0
  22. autonomous/memory.py +448 -0
  23. autonomous/react.py +339 -0
  24. autonomous/tool_executor.py +488 -0
  25. backends/__init__.py +16 -0
  26. backends/chatgpt_direct.py +133 -0
  27. backends/claude_direct.py +130 -0
  28. backends/duckduckgo.py +138 -0
  29. backends/openrouter.py +120 -0
  30. benchmarks/__init__.py +149 -0
  31. benchmarks/benchmark_engine.py +904 -0
  32. benchmarks/ci_benchmark.py +785 -0
  33. benchmarks/comparison.py +729 -0
  34. benchmarks/metrics.py +553 -0
  35. benchmarks/run_benchmarks.py +809 -0
  36. ci_cd/__init__.py +2 -0
  37. core/__init__.py +17 -0
  38. core/async_pool.py +282 -0
  39. core/asyncio_fix.py +222 -0
  40. core/cache.py +472 -0
  41. core/container.py +277 -0
  42. core/database.py +114 -0
  43. core/input_validator.py +353 -0
  44. core/models.py +288 -0
  45. core/orchestrator.py +611 -0
  46. core/plugin_manager.py +571 -0
  47. core/rate_limiter.py +405 -0
  48. core/secure_config.py +328 -0
  49. core/shield_integration.py +296 -0
  50. modules/__init__.py +46 -0
  51. modules/cve_database.py +362 -0
  52. modules/exploit_assist.py +330 -0
  53. modules/nuclei_integration.py +480 -0
  54. modules/osint.py +604 -0
  55. modules/protonvpn.py +554 -0
  56. modules/recon.py +165 -0
  57. modules/sql_injection_db.py +826 -0
  58. modules/tool_orchestrator.py +498 -0
  59. modules/vuln_scanner.py +292 -0
  60. modules/wordlist_generator.py +566 -0
  61. risk_engine/__init__.py +99 -0
  62. risk_engine/business_impact.py +267 -0
  63. risk_engine/business_impact_calculator.py +563 -0
  64. risk_engine/cvss.py +156 -0
  65. risk_engine/epss.py +190 -0
  66. risk_engine/example_usage.py +294 -0
  67. risk_engine/false_positive_engine.py +1073 -0
  68. risk_engine/scorer.py +304 -0
  69. web_ui/backend/main.py +471 -0
  70. zen_ai_pentest-2.0.0.dist-info/METADATA +795 -0
  71. zen_ai_pentest-2.0.0.dist-info/RECORD +75 -0
  72. zen_ai_pentest-2.0.0.dist-info/WHEEL +5 -0
  73. zen_ai_pentest-2.0.0.dist-info/entry_points.txt +2 -0
  74. zen_ai_pentest-2.0.0.dist-info/licenses/LICENSE +21 -0
  75. zen_ai_pentest-2.0.0.dist-info/top_level.txt +10 -0
@@ -0,0 +1,566 @@
1
+ """
2
+ Wordlist Generator Module
3
+
4
+ Generates targeted wordlists for penetration testing:
5
+ - Company-specific wordlists
6
+ - Target-based permutations
7
+ - Pattern-based generation
8
+ - Leaked password mutations
9
+
10
+ Author: SHAdd0WTAka + Kimi AI
11
+ """
12
+
13
+ import hashlib
14
+ import itertools
15
+ import random
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional, Set
19
+
20
+
21
+ @dataclass
22
+ class WordlistConfig:
23
+ """Configuration for wordlist generation"""
24
+
25
+ min_length: int = 4
26
+ max_length: int = 20
27
+ include_numbers: bool = True
28
+ include_special: bool = False
29
+ include_years: bool = True
30
+ years_range: List[int] = field(
31
+ default_factory=lambda: [2020, 2021, 2022, 2023, 2024, 2025]
32
+ )
33
+ mutations: bool = True
34
+ mutation_level: str = "medium" # low, medium, high
35
+
36
+
37
+ class WordlistGenerator:
38
+ """
39
+ Generate targeted wordlists for password attacks.
40
+
41
+ Features:
42
+ - Target-specific word generation
43
+ - Pattern-based permutations
44
+ - Common password mutations
45
+ - Seasonal/year-based passwords
46
+ """
47
+
48
+ COMMON_SUFFIXES = [
49
+ "1",
50
+ "12",
51
+ "123",
52
+ "1234",
53
+ "12345",
54
+ "01",
55
+ "02",
56
+ "03",
57
+ "04",
58
+ "05",
59
+ "06",
60
+ "07",
61
+ "08",
62
+ "09",
63
+ "10",
64
+ "11",
65
+ "12",
66
+ "13",
67
+ "14",
68
+ "15",
69
+ "16",
70
+ "17",
71
+ "18",
72
+ "19",
73
+ "20",
74
+ "00",
75
+ "99",
76
+ "88",
77
+ "77",
78
+ "66",
79
+ "55",
80
+ "44",
81
+ "33",
82
+ "22",
83
+ "11",
84
+ "007",
85
+ "666",
86
+ "777",
87
+ "888",
88
+ "999",
89
+ "000",
90
+ "!",
91
+ "!!",
92
+ "@",
93
+ "#",
94
+ "$",
95
+ "%",
96
+ "&",
97
+ "*",
98
+ "2023",
99
+ "2024",
100
+ "2025",
101
+ "23",
102
+ "24",
103
+ "25",
104
+ ]
105
+
106
+ COMMON_PREFIXES = ["", "The", "My", "Mr", "Ms", "Dr", "Admin", "User", "Test"]
107
+
108
+ SPECIAL_CHARS = ["!", "@", "#", "$", "%", "&", "*", "?", "_", "-"]
109
+
110
+ SEASONS = ["Spring", "Summer", "Fall", "Autumn", "Winter"]
111
+ MONTHS = [
112
+ "Jan",
113
+ "Feb",
114
+ "Mar",
115
+ "Apr",
116
+ "May",
117
+ "Jun",
118
+ "Jul",
119
+ "Aug",
120
+ "Sep",
121
+ "Oct",
122
+ "Nov",
123
+ "Dec",
124
+ ]
125
+
126
+ LEET_SPEAK = {
127
+ "a": ["4", "@"],
128
+ "e": ["3"],
129
+ "i": ["1", "!"],
130
+ "o": ["0"],
131
+ "s": ["5", "$"],
132
+ "t": ["7"],
133
+ "l": ["1"],
134
+ "g": ["9"],
135
+ "b": ["8"],
136
+ }
137
+
138
+ def __init__(self, config: Optional[WordlistConfig] = None):
139
+ self.config = config or WordlistConfig()
140
+ self.generated_words: Set[str] = set()
141
+
142
+ def generate_company_wordlist(
143
+ self,
144
+ company_name: str,
145
+ industry: Optional[str] = None,
146
+ locations: Optional[List[str]] = None,
147
+ extras: Optional[List[str]] = None,
148
+ ) -> List[str]:
149
+ """
150
+ Generate wordlist based on company information.
151
+
152
+ Args:
153
+ company_name: Company name
154
+ industry: Industry type (tech, finance, etc.)
155
+ locations: Office locations
156
+ extras: Additional keywords
157
+ """
158
+ words = set()
159
+
160
+ # Base words
161
+ base_words = self._extract_base_words(company_name)
162
+ words.update(base_words)
163
+
164
+ # Add industry terms
165
+ if industry:
166
+ industry_words = self._get_industry_words(industry)
167
+ words.update(industry_words)
168
+
169
+ # Add locations
170
+ if locations:
171
+ words.update(locations)
172
+
173
+ # Add extras
174
+ if extras:
175
+ words.update(extras)
176
+
177
+ # Generate permutations
178
+ all_words = set(words)
179
+ for word in words:
180
+ all_words.update(self._generate_permutations(word))
181
+ all_words.update(self._add_numbers(word))
182
+ all_words.update(self._add_special(word))
183
+
184
+ if self.config.mutations:
185
+ all_words.update(self._mutate_word(word))
186
+
187
+ # Add years
188
+ if self.config.include_years:
189
+ for word in list(all_words)[:50]: # Limit to prevent explosion
190
+ for year in self.config.years_range:
191
+ all_words.add(f"{word}{year}")
192
+ all_words.add(f"{year}{word}")
193
+
194
+ # Filter by length
195
+ filtered = {
196
+ w
197
+ for w in all_words
198
+ if self.config.min_length <= len(w) <= self.config.max_length
199
+ }
200
+
201
+ return sorted(list(filtered))
202
+
203
+ def generate_targeted_wordlist(self, target_info: Dict[str, Any]) -> List[str]:
204
+ """
205
+ Generate wordlist from target information.
206
+
207
+ target_info can include:
208
+ - first_name, last_name
209
+ - birthdate
210
+ - pet_names
211
+ - hobbies
212
+ - favorite_things
213
+ """
214
+ words = set()
215
+
216
+ # Extract all information
217
+ keywords = []
218
+
219
+ if "first_name" in target_info:
220
+ keywords.append(target_info["first_name"])
221
+ keywords.append(target_info["first_name"].lower())
222
+ keywords.append(target_info["first_name"].capitalize())
223
+
224
+ if "last_name" in target_info:
225
+ keywords.append(target_info["last_name"])
226
+ keywords.append(target_info["last_name"].lower())
227
+ keywords.append(target_info["last_name"].capitalize())
228
+
229
+ if "birthdate" in target_info:
230
+ bd = target_info["birthdate"]
231
+ # Various date formats
232
+ keywords.extend(
233
+ [
234
+ bd.replace("-", ""),
235
+ bd.replace("-", "")[2:],
236
+ bd.split("-")[0], # year
237
+ bd.split("-")[1], # month
238
+ bd.split("-")[2], # day
239
+ ]
240
+ )
241
+
242
+ if "pet_names" in target_info:
243
+ keywords.extend(target_info["pet_names"])
244
+
245
+ if "hobbies" in target_info:
246
+ keywords.extend(target_info["hobbies"])
247
+
248
+ if "favorite_things" in target_info:
249
+ keywords.extend(target_info["favorite_things"])
250
+
251
+ # Generate combinations
252
+ base_words = set(keywords)
253
+
254
+ # Add combinations of first + last name
255
+ if "first_name" in target_info and "last_name" in target_info:
256
+ first = target_info["first_name"]
257
+ last = target_info["last_name"]
258
+ combinations = [
259
+ f"{first}{last}",
260
+ f"{first}.{last}",
261
+ f"{first}_{last}",
262
+ f"{first[0]}{last}",
263
+ f"{first}{last[0]}",
264
+ f"{last}{first}",
265
+ f"{first[0]}.{last}",
266
+ f"{first}{last[0]}",
267
+ ]
268
+ base_words.update(combinations)
269
+
270
+ # Generate permutations for all words
271
+ all_words = set(base_words)
272
+ for word in base_words:
273
+ all_words.update(self._generate_permutations(word))
274
+ all_words.update(self._add_numbers(word))
275
+
276
+ if self.config.mutations:
277
+ all_words.update(self._mutate_word(word))
278
+
279
+ return sorted(list(all_words))
280
+
281
+ def generate_pattern_wordlist(
282
+ self, pattern: str, values: Dict[str, List[str]]
283
+ ) -> List[str]:
284
+ """
285
+ Generate wordlist from pattern.
286
+
287
+ Pattern example: "{word}{number}{special}"
288
+ Values: {"word": ["Pass", "Secret"], "number": ["1", "2"], "special": ["!", "@"]}
289
+ """
290
+ import re
291
+
292
+ # Find all placeholders
293
+ placeholders = re.findall(r"\{(\w+)\}", pattern)
294
+
295
+ # Get value lists for each placeholder
296
+ value_lists = []
297
+ for ph in placeholders:
298
+ if ph in values:
299
+ value_lists.append(values[ph])
300
+ else:
301
+ value_lists.append([f"{{{ph}}}"])
302
+
303
+ # Generate all combinations
304
+ words = set()
305
+ for combo in itertools.product(*value_lists):
306
+ word = pattern
307
+ for ph, val in zip(placeholders, combo):
308
+ word = word.replace(f"{{{ph}}}", val, 1)
309
+ words.add(word)
310
+
311
+ return sorted(list(words))
312
+
313
+ def mutate_password(self, base_password: str) -> List[str]:
314
+ """
315
+ Generate common password mutations.
316
+
317
+ Mutations include:
318
+ - Case variations
319
+ - Leet speak
320
+ - Appending numbers/special chars
321
+ - Common substitutions
322
+ """
323
+ mutations = set()
324
+ mutations.add(base_password)
325
+
326
+ # Case variations
327
+ mutations.add(base_password.lower())
328
+ mutations.add(base_password.upper())
329
+ mutations.add(base_password.capitalize())
330
+ mutations.add(base_password.swapcase())
331
+
332
+ # Leet speak
333
+ leet_versions = self._apply_leet_speak(base_password)
334
+ mutations.update(leet_versions)
335
+
336
+ # Add numbers
337
+ mutations.update(self._add_numbers(base_password))
338
+
339
+ # Add special chars
340
+ if self.config.include_special:
341
+ mutations.update(self._add_special(base_password))
342
+
343
+ # Reverse
344
+ mutations.add(base_password[::-1])
345
+
346
+ # Duplicate
347
+ mutations.add(base_password * 2)
348
+ mutations.add(base_password * 3)
349
+
350
+ # Common substitutions
351
+ subs = [
352
+ ("a", "@"),
353
+ ("a", "4"),
354
+ ("e", "3"),
355
+ ("i", "1"),
356
+ ("i", "!"),
357
+ ("o", "0"),
358
+ ("s", "$"),
359
+ ("s", "5"),
360
+ ("t", "7"),
361
+ ]
362
+
363
+ for old, new in subs:
364
+ mutated = base_password.replace(old, new).replace(old.upper(), new)
365
+ mutations.add(mutated)
366
+ mutations.add(mutated.capitalize())
367
+
368
+ return sorted(list(mutations))
369
+
370
+ def generate_common_passwords(self, count: int = 10000) -> List[str]:
371
+ """Generate list of common passwords with variations"""
372
+ common_bases = [
373
+ "password",
374
+ "123456",
375
+ "12345678",
376
+ "qwerty",
377
+ "abc123",
378
+ "monkey",
379
+ "letmein",
380
+ "dragon",
381
+ "111111",
382
+ "baseball",
383
+ "iloveyou",
384
+ "trustno1",
385
+ "sunshine",
386
+ "princess",
387
+ "admin",
388
+ "welcome",
389
+ "shadow",
390
+ "ashley",
391
+ "football",
392
+ "jesus",
393
+ "michael",
394
+ "ninja",
395
+ "mustang",
396
+ "password1",
397
+ "123456789",
398
+ "adobe123",
399
+ "admin123",
400
+ "root",
401
+ "toor",
402
+ "guest",
403
+ "default",
404
+ "changeme",
405
+ "p@ssw0rd",
406
+ "Passw0rd",
407
+ "Password1",
408
+ ]
409
+
410
+ passwords = set(common_bases)
411
+
412
+ # Add mutations
413
+ for base in common_bases[:50]: # Top 50
414
+ passwords.update(self.mutate_password(base))
415
+
416
+ # Add years
417
+ for base in common_bases[:20]:
418
+ for year in self.config.years_range:
419
+ passwords.add(f"{base}{year}")
420
+ passwords.add(f"{base}{str(year)[2:]}")
421
+
422
+ return sorted(list(passwords))[:count]
423
+
424
+ def save_wordlist(self, words: List[str], filepath: str):
425
+ """Save wordlist to file"""
426
+ path = Path(filepath)
427
+ path.parent.mkdir(parents=True, exist_ok=True)
428
+
429
+ with open(path, "w", encoding="utf-8") as f:
430
+ for word in words:
431
+ f.write(f"{word}\n")
432
+
433
+ return path.absolute()
434
+
435
+ def _extract_base_words(self, text: str) -> Set[str]:
436
+ """Extract base words from company name"""
437
+ words = set()
438
+
439
+ # Original
440
+ words.add(text)
441
+ words.add(text.lower())
442
+ words.add(text.upper())
443
+ words.add(text.capitalize())
444
+
445
+ # Remove spaces
446
+ words.add(text.replace(" ", ""))
447
+ words.add(text.replace(" ", "").lower())
448
+
449
+ # Remove special chars
450
+ clean = "".join(c for c in text if c.isalnum())
451
+ words.add(clean)
452
+ words.add(clean.lower())
453
+
454
+ # Acronyms
455
+ if " " in text:
456
+ acronym = "".join(word[0] for word in text.split() if word)
457
+ words.add(acronym)
458
+ words.add(acronym.upper())
459
+ words.add(acronym.lower())
460
+
461
+ return words
462
+
463
+ def _get_industry_words(self, industry: str) -> Set[str]:
464
+ """Get common words for industry"""
465
+ industry_terms = {
466
+ "tech": ["tech", "software", "digital", "data", "cloud", "cyber", "it"],
467
+ "finance": ["finance", "bank", "money", "capital", "invest", "trade"],
468
+ "healthcare": ["health", "medical", "care", "clinic", "patient"],
469
+ "education": ["edu", "school", "learn", "student", "academy"],
470
+ "retail": ["shop", "store", "retail", "sale", "market"],
471
+ }
472
+
473
+ return set(industry_terms.get(industry.lower(), []))
474
+
475
+ def _generate_permutations(self, word: str) -> Set[str]:
476
+ """Generate case and format permutations"""
477
+ perms = {
478
+ word,
479
+ word.lower(),
480
+ word.upper(),
481
+ word.capitalize(),
482
+ word.swapcase(),
483
+ }
484
+
485
+ # CamelCase
486
+ if " " in word:
487
+ camel = "".join(w.capitalize() for w in word.split())
488
+ perms.add(camel)
489
+ perms.add(camel.lower())
490
+
491
+ return perms
492
+
493
+ def _add_numbers(self, word: str) -> Set[str]:
494
+ """Add number suffixes/prefixes"""
495
+ results = set()
496
+
497
+ for suffix in self.COMMON_SUFFIXES:
498
+ results.add(f"{word}{suffix}")
499
+ results.add(f"{suffix}{word}")
500
+
501
+ return results
502
+
503
+ def _add_special(self, word: str) -> Set[str]:
504
+ """Add special characters"""
505
+ results = set()
506
+
507
+ for char in self.SPECIAL_CHARS:
508
+ results.add(f"{word}{char}")
509
+ results.add(f"{char}{word}")
510
+ results.add(f"{word}{char}{char}")
511
+
512
+ return results
513
+
514
+ def _mutate_word(self, word: str) -> Set[str]:
515
+ """Apply word mutations"""
516
+ mutations = set()
517
+
518
+ # Add years
519
+ for year in self.config.years_range:
520
+ mutations.add(f"{word}{year}")
521
+ mutations.add(f"{word}{str(year)[2:]}")
522
+
523
+ # Add seasons
524
+ for season in self.SEASONS:
525
+ mutations.add(f"{word}{season}")
526
+ mutations.add(f"{season}{word}")
527
+
528
+ # Add months
529
+ for month in self.MONTHS:
530
+ mutations.add(f"{word}{month}")
531
+ mutations.add(f"{month}{word}")
532
+
533
+ return mutations
534
+
535
+ def _apply_leet_speak(self, text: str) -> Set[str]:
536
+ """Apply leet speak transformations"""
537
+ results = set()
538
+
539
+ # Simple replacements
540
+ for char, replacements in self.LEET_SPEAK.items():
541
+ for replacement in replacements:
542
+ leet = text.replace(char, replacement).replace(
543
+ char.upper(), replacement
544
+ )
545
+ results.add(leet)
546
+
547
+ return results
548
+
549
+
550
+ # Convenience functions
551
+ def generate_company_wordlist(company: str, **kwargs) -> List[str]:
552
+ """Quick company wordlist generation"""
553
+ gen = WordlistGenerator()
554
+ return gen.generate_company_wordlist(company, **kwargs)
555
+
556
+
557
+ def generate_password_mutations(password: str) -> List[str]:
558
+ """Quick password mutation"""
559
+ gen = WordlistGenerator()
560
+ return gen.mutate_password(password)
561
+
562
+
563
+ def generate_target_wordlist(info: Dict[str, Any]) -> List[str]:
564
+ """Quick targeted wordlist"""
565
+ gen = WordlistGenerator()
566
+ return gen.generate_targeted_wordlist(info)
@@ -0,0 +1,99 @@
1
+ """
2
+ Risk Engine für das Zen-AI-Pentest Framework.
3
+
4
+ Dieses Paket enthält:
5
+ - FalsePositiveEngine: Multi-Faktor-Validierung und FP-Reduzierung
6
+ - BusinessImpactCalculator: Geschäftliche Impact-Bewertung
7
+ - CVSS Scoring und EPSS Integration
8
+ - Compliance-Risiko-Analyse
9
+
10
+ Usage:
11
+ from risk_engine import FalsePositiveEngine, BusinessImpactCalculator
12
+ from risk_engine import Finding, RiskFactors, ConfidenceLevel
13
+
14
+ # False Positive Engine
15
+ fp_engine = FalsePositiveEngine()
16
+ result = await fp_engine.validate_finding(finding)
17
+
18
+ # Business Impact Calculator
19
+ impact_calc = BusinessImpactCalculator(
20
+ organization_size="large",
21
+ annual_revenue=100000000,
22
+ industry="finance"
23
+ )
24
+ impact = impact_calc.calculate_overall_impact(
25
+ asset_context=asset,
26
+ finding_type="sql_injection",
27
+ severity="critical"
28
+ )
29
+ """
30
+
31
+ from .false_positive_engine import (
32
+ FalsePositiveEngine,
33
+ Finding,
34
+ ValidationResult,
35
+ RiskFactors,
36
+ CVSSData,
37
+ EPSSData,
38
+ ConfidenceLevel,
39
+ FindingStatus,
40
+ VulnerabilityType,
41
+ create_finding_from_scan_result,
42
+ FalsePositiveDatabase,
43
+ BayesianFilter,
44
+ LLMVotingEngine,
45
+ )
46
+
47
+ from .business_impact_calculator import (
48
+ BusinessImpactCalculator,
49
+ BusinessImpactResult,
50
+ AssetContext,
51
+ AssetCriticality,
52
+ DataClassification,
53
+ ComplianceFramework,
54
+ FinancialImpact,
55
+ ComplianceImpact,
56
+ ReputationImpact,
57
+ get_calculator,
58
+ )
59
+
60
+ # Additional exports for backwards compatibility
61
+ from .scorer import RiskScorer
62
+ from .cvss import CVSSCalculator
63
+ from .epss import EPSSClient
64
+
65
+ __all__ = [
66
+ # False Positive Engine
67
+ "FalsePositiveEngine",
68
+ "Finding",
69
+ "ValidationResult",
70
+ "RiskFactors",
71
+ "CVSSData",
72
+ "EPSSData",
73
+ "ConfidenceLevel",
74
+ "FindingStatus",
75
+ "VulnerabilityType",
76
+ "create_finding_from_scan_result",
77
+ "FalsePositiveDatabase",
78
+ "BayesianFilter",
79
+ "LLMVotingEngine",
80
+
81
+ # Business Impact Calculator
82
+ "BusinessImpactCalculator",
83
+ "BusinessImpactResult",
84
+ "AssetContext",
85
+ "AssetCriticality",
86
+ "DataClassification",
87
+ "ComplianceFramework",
88
+ "FinancialImpact",
89
+ "ComplianceImpact",
90
+ "ReputationImpact",
91
+ "get_calculator",
92
+
93
+ # Additional scoring modules
94
+ "RiskScorer",
95
+ "CVSSCalculator",
96
+ "EPSSClient",
97
+ ]
98
+
99
+ __version__ = "2.0.0"