tzero-mcp 10.7.0 → 10.7.1
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/bin/tzero-mcp.js +9 -16
- package/bin/tzero.js +9 -16
- package/index.js +7 -2
- package/package.json +2 -1
- package/src/addmcp.py +1194 -0
- package/src/main.py +60 -0
- package/src/siber_akademi_icon.py +77 -0
- package/src/tzero/jarvis/semantic_compressor.py +373 -0
- package/src/tzero.py +58 -0
- package/src/tzero_benchmark.py +475 -0
- package/src/tzero_bridge.py +362 -0
- package/src/tzero_ci.py +153 -0
- package/src/tzero_commit.py +192 -0
- package/src/tzero_cost.py +163 -0
- package/src/tzero_dashboard.py +1448 -0
- package/src/tzero_deps.py +287 -0
- package/src/tzero_drift.py +208 -0
- package/src/tzero_explain.py +240 -0
- package/src/tzero_features.py +3022 -0
- package/src/tzero_fix.py +228 -0
- package/src/tzero_fixer/__init__.py +63 -0
- package/src/tzero_fixer/amalgamation.py +334 -0
- package/src/tzero_fixer/ast_syntax_healer.py +464 -0
- package/src/tzero_fixer/ast_type_modernizer.py +288 -0
- package/src/tzero_fixer/async_io_modernizer.py +127 -0
- package/src/tzero_fixer/automated_regression_suite.py +600 -0
- package/src/tzero_fixer/benchmarks_and_diagnostics.py +174 -0
- package/src/tzero_fixer/ci_cd_self_healing_gate.py +246 -0
- package/src/tzero_fixer/code_duplicity_and_refactorer.py +151 -0
- package/src/tzero_fixer/code_smells_healer.py +260 -0
- package/src/tzero_fixer/cwe_remediator.py +171 -0
- package/src/tzero_fixer/dead_code_eliminator.py +150 -0
- package/src/tzero_fixer/dependency_vulnerability_auditor.py +309 -0
- package/src/tzero_fixer/git_advanced_surgeon.py +280 -0
- package/src/tzero_fixer/hardware_and_disk_doctor.py +325 -0
- package/src/tzero_fixer/import_graph_healer.py +412 -0
- package/src/tzero_fixer/interactive_tui.py +91 -0
- package/src/tzero_fixer/knowledge_base.py +295 -0
- package/src/tzero_fixer/knowledge_base_extended.py +940 -0
- package/src/tzero_fixer/linters_and_static_analyzer.py +307 -0
- package/src/tzero_fixer/mcp_server.py +447 -0
- package/src/tzero_fixer/models.py +385 -0
- package/src/tzero_fixer/orchestrator.py +375 -0
- package/src/tzero_fixer/os_doctor.py +502 -0
- package/src/tzero_fixer/packaging_abi_surgeon.py +323 -0
- package/src/tzero_fixer/pc_fixer_doctor.py +599 -0
- package/src/tzero_fixer/performance_and_memory_profiler.py +274 -0
- package/src/tzero_fixer/polyglot_ast_parsers.py +313 -0
- package/src/tzero_fixer/polyglot_build_healer.py +364 -0
- package/src/tzero_fixer/polyglot_config_healer.py +318 -0
- package/src/tzero_fixer/polyglot_doctor.py +372 -0
- package/src/tzero_fixer/polyglot_linters.py +619 -0
- package/src/tzero_fixer/polyglot_schema_validator.py +305 -0
- package/src/tzero_fixer/security_cwe_database.py +444 -0
- package/src/tzero_fixer/security_deep_rules.py +114 -0
- package/src/tzero_fixer/security_rules_catalog.py +110 -0
- package/src/tzero_fixer/security_zero_leak.py +326 -0
- package/src/tzero_fixer/smart_patch_recommender.py +223 -0
- package/src/tzero_fixer/synthetic_workspace_generator.py +220 -0
- package/src/tzero_fixer/system_diagnostics_extended.py +493 -0
- package/src/tzero_fixer/system_repair_recipes.py +447 -0
- package/src/tzero_fixer/venv_doctor.py +309 -0
- package/src/tzero_gateway.py +885 -0
- package/src/tzero_guard.py +243 -0
- package/src/tzero_infra.py +274 -0
- package/src/tzero_jarvis/__init__.py +124 -0
- package/src/tzero_jarvis/causal_graph.py +310 -0
- package/src/tzero_jarvis/graph_visualizer.py +395 -0
- package/src/tzero_jarvis/hybrid_search.py +214 -0
- package/src/tzero_jarvis/latent_press.py +137 -0
- package/src/tzero_jarvis/memory_consolidation.py +212 -0
- package/src/tzero_jarvis/minhash_lsh.py +184 -0
- package/src/tzero_jarvis/orchestrator.py +335 -0
- package/src/tzero_jarvis/self_healing.py +308 -0
- package/src/tzero_jarvis/semantic_compressor.py +887 -0
- package/src/tzero_jarvis/swarm.py +261 -0
- package/src/tzero_jarvis/swarm_bft.py +144 -0
- package/src/tzero_jarvis/workspace_orchestrator.py +1167 -0
- package/src/tzero_jarvis/zero_key_router.py +173 -0
- package/src/tzero_mcp.py +4858 -0
- package/src/tzero_neural_cortex.py +1425 -0
- package/src/tzero_packer.py +370 -0
- package/src/tzero_patch.py +624 -0
- package/src/tzero_pc_fixer.py +12657 -0
- package/src/tzero_pipe.py +95 -0
- package/src/tzero_polyglot.py +422 -0
- package/src/tzero_reachability.py +272 -0
- package/src/tzero_replay.py +141 -0
- package/src/tzero_security.py +570 -0
- package/src/tzero_snapshot.py +258 -0
- package/src/tzero_stubs.py +313 -0
- package/src/tzero_superdesign.py +774 -0
- package/src/tzero_sync.py +241 -0
- package/src/tzero_task_benchmark.py +363 -0
- package/src/tzero_tui.py +244 -0
- package/src/tzero_v3.py +9435 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Siber Akademi — T-Zero 100% Zero-Leak Security & Shannon Entropy Engine.
|
|
4
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
5
|
+
License: MIT
|
|
6
|
+
|
|
7
|
+
Centralized, high-throughput offline security engine providing:
|
|
8
|
+
1. Signature-based secret scanning across 20+ cloud, AI, payment, and VCS providers.
|
|
9
|
+
2. Shannon Entropy mathematical anomaly detection for raw un-prefixed cryptographic keys.
|
|
10
|
+
3. AST-level and textual Zero-Leak secret redaction ([ZERO_LEAK_MASKED_...]).
|
|
11
|
+
4. Git pre-commit, MCP, and CLI verification integrations.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import ast
|
|
18
|
+
from collections import Counter
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ==============================================================================
|
|
24
|
+
# 1. SHANNON ENTROPY DETECTOR
|
|
25
|
+
# ==============================================================================
|
|
26
|
+
|
|
27
|
+
def calculate_shannon_entropy(data: str) -> float:
|
|
28
|
+
"""Calculates Shannon entropy (bits per symbol) of a given string.
|
|
29
|
+
|
|
30
|
+
Higher entropy indicates higher randomness (e.g. cryptographic keys,
|
|
31
|
+
base64-encoded secrets, high-entropy tokens).
|
|
32
|
+
Standard English text: ~2.5 - 3.5
|
|
33
|
+
Source code identifiers: ~3.0 - 3.8
|
|
34
|
+
Random Base64 / Hex secrets (32+ chars): ~4.5 - 6.0
|
|
35
|
+
"""
|
|
36
|
+
if not data:
|
|
37
|
+
return 0.0
|
|
38
|
+
length = len(data)
|
|
39
|
+
counts = Counter(data)
|
|
40
|
+
entropy = 0.0
|
|
41
|
+
for count in counts.values():
|
|
42
|
+
p = count / length
|
|
43
|
+
entropy -= p * math.log2(p)
|
|
44
|
+
return entropy
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Ignore words and markers that produce high false positives
|
|
48
|
+
COMMON_ENTROPY_WHITELIST = {
|
|
49
|
+
"undefined", "constructor", "classmethod", "staticmethod", "authorization",
|
|
50
|
+
"content-type", "application/json", "multipart/form-data", "x-www-form-urlencoded",
|
|
51
|
+
"abcdefghijklmnopqrstuvwxyz", "0123456789abcdef", "placeholder", "your_secret_here"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
PLACEHOLDER_SUBSTRINGS = (
|
|
55
|
+
"YOUR_API_KEY", "YOUR-API-KEY", "YOUR_SECRET", "YOUR_TOKEN",
|
|
56
|
+
"YOUR-", "YOUR_", "ENTER_YOUR", "PLACEHOLDER", "CHANGEME", "INSERT_HERE",
|
|
57
|
+
"000000000000", "EXAMPLE_KEY", "EXAMPLEKEY", "EXAMPLE_SECRET",
|
|
58
|
+
"MOCK_KEY_PLACEHOLDER", "MOCK", "DEMO"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ==============================================================================
|
|
63
|
+
# 2. COMPREHENSIVE SECRET SIGNATURE DATABASE
|
|
64
|
+
# ==============================================================================
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class SecretRule:
|
|
68
|
+
rule_id: str
|
|
69
|
+
provider: str
|
|
70
|
+
description: str
|
|
71
|
+
pattern: re.Pattern
|
|
72
|
+
severity: str = "CRITICAL"
|
|
73
|
+
mask_tag: str = "[ZERO_LEAK_MASKED_SECRET]"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
SECRET_RULES: List[SecretRule] = [
|
|
77
|
+
# --- AI & Cloud Providers ---
|
|
78
|
+
SecretRule(
|
|
79
|
+
rule_id="SEC-AI-002",
|
|
80
|
+
provider="Anthropic",
|
|
81
|
+
description="Anthropic Claude API Key (sk-ant-...)",
|
|
82
|
+
pattern=re.compile(r"\b(sk-ant-[a-zA-Z0-9_\-]{30,})\b"),
|
|
83
|
+
mask_tag="[ZERO_LEAK_MASKED_ANTHROPIC_KEY]"
|
|
84
|
+
),
|
|
85
|
+
SecretRule(
|
|
86
|
+
rule_id="SEC-AI-001",
|
|
87
|
+
provider="OpenAI",
|
|
88
|
+
description="OpenAI API Key (sk-...)",
|
|
89
|
+
pattern=re.compile(r"\b(sk-(?!ant-)[a-zA-Z0-9_\-]{20,})\b"),
|
|
90
|
+
mask_tag="[ZERO_LEAK_MASKED_OPENAI_KEY]"
|
|
91
|
+
),
|
|
92
|
+
SecretRule(
|
|
93
|
+
rule_id="SEC-AI-003",
|
|
94
|
+
provider="Google",
|
|
95
|
+
description="Google Cloud / Gemini API Key (AIza...)",
|
|
96
|
+
pattern=re.compile(r"\b(AIza[0-9A-Za-z\-_]{30,45})\b"),
|
|
97
|
+
mask_tag="[ZERO_LEAK_MASKED_GEMINI_KEY]"
|
|
98
|
+
),
|
|
99
|
+
SecretRule(
|
|
100
|
+
rule_id="SEC-AI-004",
|
|
101
|
+
provider="HuggingFace",
|
|
102
|
+
description="HuggingFace User Access Token (hf_...)",
|
|
103
|
+
pattern=re.compile(r"\b(hf_[a-zA-Z0-9]{34,})\b"),
|
|
104
|
+
mask_tag="[ZERO_LEAK_MASKED_HF_TOKEN]"
|
|
105
|
+
),
|
|
106
|
+
SecretRule(
|
|
107
|
+
rule_id="SEC-AI-005",
|
|
108
|
+
provider="NVIDIA",
|
|
109
|
+
description="NVIDIA NIM API Key (nvapi-...)",
|
|
110
|
+
pattern=re.compile(r"\b(nvapi-[a-zA-Z0-9_\-]{30,})\b"),
|
|
111
|
+
mask_tag="[ZERO_LEAK_MASKED_NVIDIA_KEY]"
|
|
112
|
+
),
|
|
113
|
+
SecretRule(
|
|
114
|
+
rule_id="SEC-AI-006",
|
|
115
|
+
provider="Groq",
|
|
116
|
+
description="Groq Cloud API Key (gsk_...)",
|
|
117
|
+
pattern=re.compile(r"\b(gsk_[a-zA-Z0-9]{48,})\b"),
|
|
118
|
+
mask_tag="[ZERO_LEAK_MASKED_GROQ_KEY]"
|
|
119
|
+
),
|
|
120
|
+
SecretRule(
|
|
121
|
+
rule_id="SEC-AI-007",
|
|
122
|
+
provider="OpenRouter",
|
|
123
|
+
description="OpenRouter API Key (sk-or-v1-...)",
|
|
124
|
+
pattern=re.compile(r"\b(sk-or-v1-[a-f0-9]{64})\b"),
|
|
125
|
+
mask_tag="[ZERO_LEAK_MASKED_OPENROUTER_KEY]"
|
|
126
|
+
),
|
|
127
|
+
SecretRule(
|
|
128
|
+
rule_id="SEC-AI-008",
|
|
129
|
+
provider="Perplexity",
|
|
130
|
+
description="Perplexity AI API Key (pplx-...)",
|
|
131
|
+
pattern=re.compile(r"\b(pplx-[a-zA-Z0-9]{48,})\b"),
|
|
132
|
+
mask_tag="[ZERO_LEAK_MASKED_PERPLEXITY_KEY]"
|
|
133
|
+
),
|
|
134
|
+
SecretRule(
|
|
135
|
+
rule_id="SEC-AI-009",
|
|
136
|
+
provider="Replicate",
|
|
137
|
+
description="Replicate API Token (r8_...)",
|
|
138
|
+
pattern=re.compile(r"\b(r8_[a-zA-Z0-9]{32,})\b"),
|
|
139
|
+
mask_tag="[ZERO_LEAK_MASKED_REPLICATE_KEY]"
|
|
140
|
+
),
|
|
141
|
+
SecretRule(
|
|
142
|
+
rule_id="SEC-AI-010",
|
|
143
|
+
provider="Cohere",
|
|
144
|
+
description="Cohere API Key (co-...)",
|
|
145
|
+
pattern=re.compile(r"\b(co-[a-zA-Z0-9]{40,})\b"),
|
|
146
|
+
mask_tag="[ZERO_LEAK_MASKED_COHERE_KEY]"
|
|
147
|
+
),
|
|
148
|
+
SecretRule(
|
|
149
|
+
rule_id="SEC-REG-001",
|
|
150
|
+
provider="NPM",
|
|
151
|
+
description="NPM Registry Access Token (npm_...)",
|
|
152
|
+
pattern=re.compile(r"\b(npm_[a-zA-Z0-9]{32,40})\b"),
|
|
153
|
+
mask_tag="[ZERO_LEAK_MASKED_NPM_TOKEN]"
|
|
154
|
+
),
|
|
155
|
+
|
|
156
|
+
# --- Cloud Infrastructure & VCS ---
|
|
157
|
+
SecretRule(
|
|
158
|
+
rule_id="SEC-AWS-001",
|
|
159
|
+
provider="AWS",
|
|
160
|
+
description="AWS Access Key ID (AKIA...)",
|
|
161
|
+
pattern=re.compile(r"\b(AKIA[0-9A-Z]{16})\b"),
|
|
162
|
+
mask_tag="[ZERO_LEAK_MASKED_AWS_KEY]"
|
|
163
|
+
),
|
|
164
|
+
SecretRule(
|
|
165
|
+
rule_id="SEC-AWS-002",
|
|
166
|
+
provider="AWS",
|
|
167
|
+
description="AWS Secret Access Key",
|
|
168
|
+
pattern=re.compile(r"(?i)aws_secret_access_key\s*[:=]\s*['\"]([0-9a-zA-Z/+]{40})['\"]"),
|
|
169
|
+
mask_tag="[ZERO_LEAK_MASKED_AWS_SECRET]"
|
|
170
|
+
),
|
|
171
|
+
SecretRule(
|
|
172
|
+
rule_id="SEC-GH-001",
|
|
173
|
+
provider="GitHub",
|
|
174
|
+
description="GitHub Personal Access Token (Classic: ghp_...)",
|
|
175
|
+
pattern=re.compile(r"\b(ghp_[a-zA-Z0-9]{36})\b"),
|
|
176
|
+
mask_tag="[ZERO_LEAK_MASKED_GITHUB_PAT]"
|
|
177
|
+
),
|
|
178
|
+
SecretRule(
|
|
179
|
+
rule_id="SEC-GH-002",
|
|
180
|
+
provider="GitHub",
|
|
181
|
+
description="GitHub OAuth Token (gho_...)",
|
|
182
|
+
pattern=re.compile(r"\b(gho_[a-zA-Z0-9]{36})\b"),
|
|
183
|
+
mask_tag="[ZERO_LEAK_MASKED_GITHUB_OAUTH]"
|
|
184
|
+
),
|
|
185
|
+
SecretRule(
|
|
186
|
+
rule_id="SEC-GH-003",
|
|
187
|
+
provider="GitHub",
|
|
188
|
+
description="GitHub Fine-Grained Personal Access Token (github_pat_...)",
|
|
189
|
+
pattern=re.compile(r"\b(github_pat_[a-zA-Z0-9_]{50,})\b"),
|
|
190
|
+
mask_tag="[ZERO_LEAK_MASKED_GITHUB_FINE_PAT]"
|
|
191
|
+
),
|
|
192
|
+
SecretRule(
|
|
193
|
+
rule_id="SEC-SLACK-001",
|
|
194
|
+
provider="Slack",
|
|
195
|
+
description="Slack Bot/User Token (xoxb/xoxp-...)",
|
|
196
|
+
pattern=re.compile(r"\b(xox[baprs]-(?:[0-9a-zA-Z]{9,}-)+[a-zA-Z0-9]{20,})\b"),
|
|
197
|
+
mask_tag="[ZERO_LEAK_MASKED_SLACK_TOKEN]"
|
|
198
|
+
),
|
|
199
|
+
SecretRule(
|
|
200
|
+
rule_id="SEC-STRIPE-001",
|
|
201
|
+
provider="Stripe",
|
|
202
|
+
description="Stripe Live Secret Key (sk_live_...)",
|
|
203
|
+
pattern=re.compile(r"\b((?:sk|rk)_live_[0-9a-zA-Z]{24,})\b"),
|
|
204
|
+
mask_tag="[ZERO_LEAK_MASKED_STRIPE_KEY]"
|
|
205
|
+
),
|
|
206
|
+
|
|
207
|
+
# --- Cryptographic & Auth Artifacts ---
|
|
208
|
+
SecretRule(
|
|
209
|
+
rule_id="SEC-CRYPTO-001",
|
|
210
|
+
provider="PKI",
|
|
211
|
+
description="Private Key Header (RSA, EC, OPENSSH, DSA, PGP)",
|
|
212
|
+
pattern=re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----"),
|
|
213
|
+
mask_tag="[ZERO_LEAK_MASKED_PRIVATE_KEY]"
|
|
214
|
+
),
|
|
215
|
+
SecretRule(
|
|
216
|
+
rule_id="SEC-AUTH-001",
|
|
217
|
+
provider="JWT",
|
|
218
|
+
description="JSON Web Token (JWT Header + Payload + Signature)",
|
|
219
|
+
pattern=re.compile(r"\b(eyJ[a-zA-Z0-9_\-]{10,}\.eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,})\b"),
|
|
220
|
+
mask_tag="[ZERO_LEAK_MASKED_JWT]"
|
|
221
|
+
),
|
|
222
|
+
SecretRule(
|
|
223
|
+
rule_id="SEC-AUTH-002",
|
|
224
|
+
provider="HTTP",
|
|
225
|
+
description="Bearer Authorization Token Header",
|
|
226
|
+
pattern=re.compile(r"(?i)Bearer\s+([a-zA-Z0-9_\.\-]{24,})"),
|
|
227
|
+
mask_tag="Bearer [ZERO_LEAK_MASKED_TOKEN]"
|
|
228
|
+
),
|
|
229
|
+
SecretRule(
|
|
230
|
+
rule_id="SEC-DB-001",
|
|
231
|
+
provider="Database",
|
|
232
|
+
description="Database Connection URI with Credentials",
|
|
233
|
+
pattern=re.compile(r"(?:postgres|postgresql|mysql|mongodb(?:\+srv)?|redis)://[^:\s]+:[^@\s]+@[^\s\"']+"),
|
|
234
|
+
mask_tag="[ZERO_LEAK_MASKED_DB_URL]"
|
|
235
|
+
),
|
|
236
|
+
SecretRule(
|
|
237
|
+
rule_id="SEC-GENERIC-001",
|
|
238
|
+
provider="Generic",
|
|
239
|
+
description="Hardcoded Secret Assignment (api_key/secret/password)",
|
|
240
|
+
pattern=re.compile(r"""(?i)(?:api_?key|secret_?key|app_?secret|db_?pass(?:word)?|auth_?token)\s*[:=]\s*['"]([^'"]{12,})['"]"""),
|
|
241
|
+
mask_tag="[ZERO_LEAK_MASKED_VALUE]"
|
|
242
|
+
),
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ==============================================================================
|
|
247
|
+
# 3. DETECTION & SCANNING INTERFACES
|
|
248
|
+
# ==============================================================================
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class SecretFinding:
|
|
252
|
+
rule_id: str
|
|
253
|
+
provider: str
|
|
254
|
+
description: str
|
|
255
|
+
severity: str
|
|
256
|
+
line_no: int
|
|
257
|
+
preview: str
|
|
258
|
+
entropy: float = 0.0
|
|
259
|
+
is_entropy_anomaly: bool = False
|
|
260
|
+
secret_value: str = ""
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ZeroLeakEngine:
|
|
264
|
+
"""Enterprise 100% Zero-Leak security scanner and sanitizer."""
|
|
265
|
+
|
|
266
|
+
MIN_ENTROPY_THRESHOLD: float = 4.75
|
|
267
|
+
MIN_ENTROPY_LENGTH: int = 28
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def is_placeholder(cls, s: str) -> bool:
|
|
271
|
+
"""Determines if a candidate string is an intentional placeholder/mock."""
|
|
272
|
+
up = s.upper()
|
|
273
|
+
return any(ph in up for ph in PLACEHOLDER_SUBSTRINGS)
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def is_natural_or_identifier(cls, s: str) -> bool:
|
|
277
|
+
"""Rejects common false positives (all-lowercase slugs, public project/team IDs)."""
|
|
278
|
+
# Exclude public identifiers
|
|
279
|
+
if s.startswith(("prj_", "team_", "col_", "tbl_", "usr_", "pkg_", "sub_")):
|
|
280
|
+
return True
|
|
281
|
+
# Natural language smushed slugs without variety
|
|
282
|
+
if s.islower() or s.isupper():
|
|
283
|
+
return True
|
|
284
|
+
# Check character class diversity (needs at least uppercase, lowercase, and digit/special)
|
|
285
|
+
has_upper = any(c.isupper() for c in s)
|
|
286
|
+
has_lower = any(c.islower() for c in s)
|
|
287
|
+
has_digit = any(c.isdigit() for c in s)
|
|
288
|
+
has_punct = any(not c.isalnum() for c in s)
|
|
289
|
+
diversity = sum([has_upper, has_lower, (has_digit or has_punct)])
|
|
290
|
+
return diversity < 2
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def scan_text(cls, text: str, check_entropy: bool = True) -> List[SecretFinding]:
|
|
294
|
+
"""Scans in-memory text for known secrets and high-entropy secret anomalies."""
|
|
295
|
+
if not text:
|
|
296
|
+
return []
|
|
297
|
+
|
|
298
|
+
findings: List[SecretFinding] = []
|
|
299
|
+
lines = text.splitlines()
|
|
300
|
+
|
|
301
|
+
for lineno, line in enumerate(lines, 1):
|
|
302
|
+
stripped = line.strip()
|
|
303
|
+
if not stripped or stripped.startswith(("#", "//", "*", "/*")):
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
# 1. Signature Rule Matching
|
|
307
|
+
has_specific_provider_match = False
|
|
308
|
+
for rule in SECRET_RULES:
|
|
309
|
+
# If a specific provider rule matched, avoid duplicate generic rule hit on same line
|
|
310
|
+
if rule.rule_id == "SEC-GENERIC-001" and has_specific_provider_match:
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
for match in rule.pattern.finditer(line):
|
|
314
|
+
val = match.group(0)
|
|
315
|
+
if cls.is_placeholder(val):
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
ent = calculate_shannon_entropy(val)
|
|
319
|
+
findings.append(SecretFinding(
|
|
320
|
+
rule_id=rule.rule_id,
|
|
321
|
+
provider=rule.provider,
|
|
322
|
+
description=rule.description,
|
|
323
|
+
severity=rule.severity,
|
|
324
|
+
line_no=lineno,
|
|
325
|
+
preview=cls.mask_preview(line),
|
|
326
|
+
entropy=round(ent, 2),
|
|
327
|
+
is_entropy_anomaly=False,
|
|
328
|
+
secret_value=val
|
|
329
|
+
))
|
|
330
|
+
if rule.provider != "Generic":
|
|
331
|
+
has_specific_provider_match = True
|
|
332
|
+
|
|
333
|
+
# 2. Shannon Entropy Scanner for Unmatched Candidate Tokens
|
|
334
|
+
if check_entropy:
|
|
335
|
+
# Candidate secrets must be single compact tokens without whitespace, slashes, or URL schemas
|
|
336
|
+
candidate_tokens = re.findall(r"""['"]([A-Za-z0-9+/=_\-]{28,})['"]""", line)
|
|
337
|
+
for tok in candidate_tokens:
|
|
338
|
+
# Filter out URLs, file paths, and common non-secrets
|
|
339
|
+
if any(prefix in tok for prefix in ("http:", "https:", "/", "\\", "@", ".com", ".org", ".net")):
|
|
340
|
+
continue
|
|
341
|
+
# Ignore SRI cryptographic package hashes and data URIs (e.g. package-lock.json integrity hashes)
|
|
342
|
+
if tok.startswith(("sha512-", "sha384-", "sha256-", "md5-", "urn:", "data:")):
|
|
343
|
+
continue
|
|
344
|
+
if any(kw in line.lower() for kw in ('"integrity"', '"resolved"', '"hash"', 'sha512-', 'sha256-')):
|
|
345
|
+
continue
|
|
346
|
+
if cls.is_placeholder(tok) or tok.lower() in COMMON_ENTROPY_WHITELIST:
|
|
347
|
+
continue
|
|
348
|
+
if cls.is_natural_or_identifier(tok):
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
ent = calculate_shannon_entropy(tok)
|
|
352
|
+
if ent >= cls.MIN_ENTROPY_THRESHOLD and len(tok) >= cls.MIN_ENTROPY_LENGTH:
|
|
353
|
+
already_reported = any(f.line_no == lineno for f in findings)
|
|
354
|
+
if not already_reported:
|
|
355
|
+
findings.append(SecretFinding(
|
|
356
|
+
rule_id="SEC-ENTROPY-001",
|
|
357
|
+
provider="EntropyAnomaly",
|
|
358
|
+
description=f"High-Entropy Secret Candidate (Shannon H={ent:.2f} >= {cls.MIN_ENTROPY_THRESHOLD})",
|
|
359
|
+
severity="HIGH",
|
|
360
|
+
line_no=lineno,
|
|
361
|
+
preview=cls.mask_preview(line),
|
|
362
|
+
entropy=round(ent, 2),
|
|
363
|
+
is_entropy_anomaly=True,
|
|
364
|
+
secret_value=tok
|
|
365
|
+
))
|
|
366
|
+
|
|
367
|
+
return findings
|
|
368
|
+
|
|
369
|
+
@classmethod
|
|
370
|
+
def mask_preview(cls, line: str, max_len: int = 70) -> str:
|
|
371
|
+
"""Sanitizes line for safe terminal output and logging."""
|
|
372
|
+
sanitized = cls.sanitize_text(line)
|
|
373
|
+
if len(sanitized) > max_len:
|
|
374
|
+
return sanitized[:max_len] + "..."
|
|
375
|
+
return sanitized
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def sanitize_text(cls, text: str) -> str:
|
|
379
|
+
"""Sanitizes text by masking all detected secrets with Zero-Leak tokens."""
|
|
380
|
+
if not text:
|
|
381
|
+
return ""
|
|
382
|
+
|
|
383
|
+
sanitized = text
|
|
384
|
+
|
|
385
|
+
# 1. Mask specific patterns
|
|
386
|
+
for rule in SECRET_RULES:
|
|
387
|
+
def _replace_match(m):
|
|
388
|
+
full_val = m.group(0)
|
|
389
|
+
if cls.is_placeholder(full_val):
|
|
390
|
+
return full_val
|
|
391
|
+
return rule.mask_tag
|
|
392
|
+
sanitized = rule.pattern.sub(_replace_match, sanitized)
|
|
393
|
+
|
|
394
|
+
# 2. Mask high entropy quoted literals
|
|
395
|
+
def _replace_entropy(m):
|
|
396
|
+
token = m.group(1)
|
|
397
|
+
if not cls.is_placeholder(token) and calculate_shannon_entropy(token) >= 4.4:
|
|
398
|
+
return f'"{token[:4]}...[ZERO_LEAK_HIGH_ENTROPY_SECRET]"'
|
|
399
|
+
return m.group(0)
|
|
400
|
+
|
|
401
|
+
sanitized = re.sub(r"""['"]([^'"]{20,})['"]""", _replace_entropy, sanitized)
|
|
402
|
+
return sanitized
|
|
403
|
+
|
|
404
|
+
@classmethod
|
|
405
|
+
def sanitize_ast(cls, tree: ast.AST) -> ast.AST:
|
|
406
|
+
"""Walks an AST tree and replaces hardcoded string literals containing secrets."""
|
|
407
|
+
for node in ast.walk(tree):
|
|
408
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
409
|
+
val = node.value
|
|
410
|
+
# Check if raw value itself or wrapped in quotes triggers detection
|
|
411
|
+
if cls.scan_text(f'val = "{val}"'):
|
|
412
|
+
node.value = cls.sanitize_text(val)
|
|
413
|
+
return tree
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def scan_files_map(cls, files_map: Dict[str, str]) -> Dict[str, Any]:
|
|
417
|
+
"""Scans in-memory files map {relative_path: file_content}."""
|
|
418
|
+
violations = []
|
|
419
|
+
files_with_violations = 0
|
|
420
|
+
|
|
421
|
+
for path, content in files_map.items():
|
|
422
|
+
findings = cls.scan_text(content)
|
|
423
|
+
if findings:
|
|
424
|
+
files_with_violations += 1
|
|
425
|
+
for f in findings:
|
|
426
|
+
violations.append({
|
|
427
|
+
"file": path,
|
|
428
|
+
"line": f.line_no,
|
|
429
|
+
"type": f.description,
|
|
430
|
+
"provider": f.provider,
|
|
431
|
+
"severity": f.severity,
|
|
432
|
+
"preview": f.preview,
|
|
433
|
+
"entropy": f.entropy
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
"clean": len(violations) == 0,
|
|
438
|
+
"violations": violations,
|
|
439
|
+
"violation_count": len(violations),
|
|
440
|
+
"files_scanned": len(files_map),
|
|
441
|
+
"files_with_violations": files_with_violations,
|
|
442
|
+
"status": "100% ZERO-LEAK VERIFIED" if not violations else "PROHIBITED CREDENTIALS DETECTED"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
@classmethod
|
|
446
|
+
def scan_workspace(cls, workspace_dir: str = ".") -> dict:
|
|
447
|
+
"""Scans a workspace directory for any hardcoded secrets and returns violations dict."""
|
|
448
|
+
files_map = {}
|
|
449
|
+
for root, dirs, files in os.walk(workspace_dir):
|
|
450
|
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ("__pycache__", "venv", ".venv", "node_modules", "dist", "build", ".git")]
|
|
451
|
+
for f in files:
|
|
452
|
+
if f.endswith((".py", ".js", ".ts", ".json", ".yaml", ".yml", ".env.example", ".sh", ".toml", ".txt")):
|
|
453
|
+
fp = os.path.join(root, f)
|
|
454
|
+
try:
|
|
455
|
+
with open(fp, "r", encoding="utf-8", errors="ignore") as fh:
|
|
456
|
+
files_map[fp] = fh.read()
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
return cls.scan_files_map(files_map)
|
|
460
|
+
|
|
461
|
+
@classmethod
|
|
462
|
+
def auto_migrate_to_env(
|
|
463
|
+
cls,
|
|
464
|
+
code_str: str,
|
|
465
|
+
env_file_path: Optional[str] = None,
|
|
466
|
+
gitignore_path: Optional[str] = None
|
|
467
|
+
) -> Tuple[str, str, int, str]:
|
|
468
|
+
"""Automatically extracts detected hardcoded secrets into .env format and refactors code with os.environ.get."""
|
|
469
|
+
findings = cls.scan_text(code_str)
|
|
470
|
+
valid_findings = [f for f in findings if f.secret_value and not cls.is_placeholder(f.secret_value)]
|
|
471
|
+
|
|
472
|
+
if not valid_findings:
|
|
473
|
+
return code_str, "# No secrets detected.", 0, "### 100% Zero-Leak Certified\nNo hardcoded credentials found in source code."
|
|
474
|
+
|
|
475
|
+
migrated_code = code_str
|
|
476
|
+
env_entries: Dict[str, str] = {}
|
|
477
|
+
used_vars: Set[str] = set()
|
|
478
|
+
counter = 1
|
|
479
|
+
|
|
480
|
+
# Sort findings descending by length to avoid partial substrings
|
|
481
|
+
unique_findings = []
|
|
482
|
+
seen_vals = set()
|
|
483
|
+
for f in valid_findings:
|
|
484
|
+
if f.secret_value not in seen_vals:
|
|
485
|
+
seen_vals.add(f.secret_value)
|
|
486
|
+
unique_findings.append(f)
|
|
487
|
+
unique_findings.sort(key=lambda x: len(x.secret_value), reverse=True)
|
|
488
|
+
|
|
489
|
+
provider_map = {
|
|
490
|
+
"OpenAI": "OPENAI_API_KEY",
|
|
491
|
+
"Anthropic": "ANTHROPIC_API_KEY",
|
|
492
|
+
"Google": "GEMINI_API_KEY",
|
|
493
|
+
"HuggingFace": "HF_TOKEN",
|
|
494
|
+
"NVIDIA": "NVIDIA_API_KEY",
|
|
495
|
+
"AWS": "AWS_SECRET_ACCESS_KEY",
|
|
496
|
+
"GitHub": "GITHUB_TOKEN",
|
|
497
|
+
"GitLab": "GITLAB_TOKEN",
|
|
498
|
+
"Stripe": "STRIPE_SECRET_KEY",
|
|
499
|
+
"Slack": "SLACK_BOT_TOKEN",
|
|
500
|
+
"Database": "DATABASE_URL",
|
|
501
|
+
"JWT": "JWT_SECRET_TOKEN",
|
|
502
|
+
"PrivateKey": "PRIVATE_KEY_PEM",
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
report_lines = [
|
|
506
|
+
"### T-Zero Zero-Leak Auto-Vault Migration",
|
|
507
|
+
f"Extracted and secured **{len(unique_findings)}** hardcoded secret(s) into environment configuration:\n",
|
|
508
|
+
"| Provider | Variable Assigned | Redacted Secret |",
|
|
509
|
+
"| :--- | :--- | :--- |",
|
|
510
|
+
]
|
|
511
|
+
|
|
512
|
+
for f in unique_findings:
|
|
513
|
+
base_var = provider_map.get(f.provider, "")
|
|
514
|
+
if not base_var:
|
|
515
|
+
clean_desc = re.sub(r"[^A-Z0-9_]", "_", f.description.upper()[:20])
|
|
516
|
+
base_var = f"{clean_desc}_TOKEN"
|
|
517
|
+
|
|
518
|
+
var_name = base_var
|
|
519
|
+
while var_name in used_vars:
|
|
520
|
+
var_name = f"{base_var}_{counter}"
|
|
521
|
+
counter += 1
|
|
522
|
+
used_vars.add(var_name)
|
|
523
|
+
|
|
524
|
+
env_entries[var_name] = f.secret_value
|
|
525
|
+
redacted = f.secret_value[:6] + "..." + f.secret_value[-4:] if len(f.secret_value) > 12 else "***"
|
|
526
|
+
report_lines.append(f"| `{f.provider}` | `{var_name}` | `{redacted}` |")
|
|
527
|
+
|
|
528
|
+
# Replace quotes in code
|
|
529
|
+
pat_d = f'"{f.secret_value}"'
|
|
530
|
+
pat_s = f"'{f.secret_value}'"
|
|
531
|
+
replacement = f'os.environ.get("{var_name}", "")'
|
|
532
|
+
if pat_d in migrated_code:
|
|
533
|
+
migrated_code = migrated_code.replace(pat_d, replacement)
|
|
534
|
+
elif pat_s in migrated_code:
|
|
535
|
+
migrated_code = migrated_code.replace(pat_s, replacement)
|
|
536
|
+
else:
|
|
537
|
+
migrated_code = migrated_code.replace(f.secret_value, replacement)
|
|
538
|
+
|
|
539
|
+
# Check if os is imported
|
|
540
|
+
if "os.environ" in migrated_code and "import os" not in migrated_code and "from os import" not in migrated_code:
|
|
541
|
+
migrated_code = "import os\n" + migrated_code
|
|
542
|
+
|
|
543
|
+
# Build .env text
|
|
544
|
+
env_lines = ["# Auto-Vault Generated by T-Zero Zero-Leak Engine", "# DO NOT COMMIT THIS FILE TO PUBLIC REPOSITORIES", ""]
|
|
545
|
+
for k, v in env_entries.items():
|
|
546
|
+
env_lines.append(f"{k}={v}")
|
|
547
|
+
env_content = "\n".join(env_lines) + "\n"
|
|
548
|
+
|
|
549
|
+
# If file paths provided, update physical .env and .gitignore
|
|
550
|
+
if env_file_path:
|
|
551
|
+
try:
|
|
552
|
+
with open(env_file_path, "a", encoding="utf-8") as ef:
|
|
553
|
+
ef.write("\n" + "\n".join(f"{k}={v}" for k, v in env_entries.items()) + "\n")
|
|
554
|
+
except Exception:
|
|
555
|
+
pass
|
|
556
|
+
|
|
557
|
+
if gitignore_path:
|
|
558
|
+
try:
|
|
559
|
+
gi_content = ""
|
|
560
|
+
if os.path.exists(gitignore_path):
|
|
561
|
+
with open(gitignore_path, "r", encoding="utf-8") as gf:
|
|
562
|
+
gi_content = gf.read()
|
|
563
|
+
if ".env" not in gi_content:
|
|
564
|
+
with open(gitignore_path, "a", encoding="utf-8") as gf:
|
|
565
|
+
gf.write("\n.env\n*.env\n")
|
|
566
|
+
except Exception:
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
return migrated_code, env_content, len(unique_findings), "\n".join(report_lines)
|
|
570
|
+
|