kryptorious-envguard 1.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.
envguard/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """EnvGuard — Environment variable management CLI."""
2
+ __version__ = "1.0.0"
envguard/cli.py ADDED
@@ -0,0 +1,231 @@
1
+ """EnvGuard CLI — Protect your environment variables."""
2
+
3
+ import os
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Dict, Set
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ console = Console()
14
+
15
+ # Common patterns for secrets
16
+ SECRET_PATTERNS = [
17
+ (r'(?i)(api[_-]?key|apikey|secret|password|token|auth|credential|private[_-]?key)',
18
+ "Potential secret"),
19
+ (r'[A-Za-z0-9+/]{32,}={0,2}', "Base64-encoded value"),
20
+ (r'sk-[A-Za-z0-9]{32,}', "Stripe/OpenAI-style API key"),
21
+ (r'ghp_[A-Za-z0-9]{36}', "GitHub personal access token"),
22
+ (r'AKIA[0-9A-Z]{16}', "AWS access key"),
23
+ ]
24
+
25
+ COMMON_REQUIRED = [
26
+ "DATABASE_URL", "SECRET_KEY", "API_KEY", "DEBUG",
27
+ "ALLOWED_HOSTS", "CORS_ORIGINS", "LOG_LEVEL",
28
+ "REDIS_URL", "CELERY_BROKER_URL", "SENTRY_DSN",
29
+ ]
30
+
31
+
32
+ def _load_env(path: str = ".env") -> Dict[str, str]:
33
+ """Load .env file, return dict of variables."""
34
+ env_path = Path(path)
35
+ if not env_path.exists():
36
+ return {}
37
+
38
+ vars_dict = {}
39
+ with open(env_path, encoding="utf-8") as f:
40
+ for line in f:
41
+ line = line.strip()
42
+ if not line or line.startswith("#"):
43
+ continue
44
+ if "=" in line:
45
+ key, _, value = line.partition("=")
46
+ key = key.strip()
47
+ # Remove quotes
48
+ value = value.strip().strip('"').strip("'")
49
+ vars_dict[key] = value
50
+ return vars_dict
51
+
52
+
53
+ def _find_env_files(start: str = ".") -> list:
54
+ """Find all .env files in project."""
55
+ env_files = []
56
+ for root, dirs, files in os.walk(start):
57
+ # Skip node_modules, .git, venv
58
+ dirs[:] = [d for d in dirs if d not in (".git", "node_modules", "venv", ".venv", "__pycache__")]
59
+ for f in files:
60
+ if f == ".env" or f.endswith(".env") or f == ".env.example" or f == ".env.local":
61
+ env_files.append(os.path.join(root, f))
62
+ return env_files
63
+
64
+
65
+ @click.group()
66
+ @click.version_option(version="1.0.0", prog_name="envguard")
67
+ def main():
68
+ """EnvGuard — Validate, generate, and secure your .env files.
69
+
70
+ Stop shipping broken configs. One command to check everything.
71
+ """
72
+ pass
73
+
74
+
75
+ @main.command()
76
+ @click.option("--path", "-p", default=".", help="Project root to scan")
77
+ @click.option("--strict/--lenient", default=False, help="Fail on warnings (premium)")
78
+ def check(path, strict):
79
+ """Check .env files for issues.
80
+
81
+ Validates syntax, finds missing values, detects hardcoded secrets.
82
+ """
83
+ console.print()
84
+ console.print(Panel("[bold]EnvGuard Check[/bold]", border_style="blue"))
85
+
86
+ env_files = _find_env_files(path)
87
+ if not env_files:
88
+ console.print("[yellow]No .env files found.[/yellow]")
89
+ return
90
+
91
+ console.print(f"Found [bold]{len(env_files)}[/bold] .env file(s)\n")
92
+
93
+ total_issues = 0
94
+
95
+ for env_path in env_files:
96
+ env_vars = _load_env(env_path)
97
+ issues = []
98
+
99
+ # Check for empty values
100
+ empty_vars = [k for k, v in env_vars.items() if not v]
101
+ if empty_vars:
102
+ for v in empty_vars:
103
+ issues.append(("warning", f"Empty value: {v}"))
104
+
105
+ # Check for placeholder values
106
+ placeholders = ["changeme", "todo", "xxx", "your-", "example", "placeholder"]
107
+ for k, v in env_vars.items():
108
+ if v and any(p in v.lower() for p in placeholders):
109
+ issues.append(("warning", f"Placeholder value: {k}={v[:40]}..."))
110
+
111
+ # Check for secrets in plaintext
112
+ for k, v in env_vars.items():
113
+ if not v:
114
+ continue
115
+ for pattern, desc in SECRET_PATTERNS:
116
+ if re.match(pattern, v):
117
+ issues.append(("error", f"Hardcoded secret: {k} ({desc})"))
118
+ break
119
+
120
+ # Check for common missing variables
121
+ for var in COMMON_REQUIRED:
122
+ if var not in env_vars:
123
+ issues.append(("info", f"Missing common variable: {var}"))
124
+
125
+ # Display
126
+ short_path = os.path.relpath(env_path, path) if path != "." else env_path
127
+ if not issues:
128
+ console.print(f" [green]✓[/green] {short_path} — {len(env_vars)} variables, clean")
129
+ else:
130
+ total_issues += len(issues)
131
+ console.print(f" [yellow]{short_path}[/yellow] — {len(env_vars)} variables, {len(issues)} issues:")
132
+ for severity, msg in issues:
133
+ icon = {"error": "[red]✗[/red]", "warning": "[yellow]![/yellow]", "info": "[blue]ℹ[/blue]"}.get(severity, "?")
134
+ console.print(f" {icon} {msg}")
135
+
136
+ console.print()
137
+ if total_issues == 0:
138
+ console.print("[green]All .env files clean.[/green]")
139
+ else:
140
+ console.print(f"[yellow]{total_issues} issue(s) found.[/yellow]")
141
+
142
+ if strict:
143
+ console.print("[yellow]Strict mode requires premium. Upgrade at https://kryptorious.gumroad.com/l/jbvet[/yellow]")
144
+
145
+
146
+ @main.command()
147
+ @click.option("--path", "-p", default=".env", help="Source .env file")
148
+ @click.option("--output", "-o", default=".env.example", help="Output file")
149
+ def generate(path, output):
150
+ """Generate .env.example from .env (values removed, keys kept).
151
+
152
+ Creates a safe-to-commit template from your .env file.
153
+ """
154
+ console.print()
155
+ console.print(Panel("[bold]EnvGuard Generate[/bold]", border_style="blue"))
156
+
157
+ env_vars = _load_env(path)
158
+ if not env_vars:
159
+ console.print(f"[red]No variables found in {path}[/red]")
160
+ return
161
+
162
+ lines = []
163
+ with open(path, encoding="utf-8") as f:
164
+ for line in f:
165
+ stripped = line.strip()
166
+ if not stripped or stripped.startswith("#"):
167
+ lines.append(line.rstrip())
168
+ continue
169
+ if "=" in stripped:
170
+ key = stripped.split("=", 1)[0].strip()
171
+ lines.append(f"{key}=")
172
+ else:
173
+ lines.append(line.rstrip())
174
+
175
+ content = "\n".join(lines) + "\n"
176
+ Path(output).write_text(content, encoding="utf-8")
177
+ console.print(f"[green]✓[/green] Generated {output} with {len(env_vars)} keys (values removed)")
178
+
179
+
180
+ @main.command()
181
+ @click.option("--path", "-p", default=".", help="Project root")
182
+ def audit(path, strict=False):
183
+ """Full security audit of environment configuration (premium).
184
+
185
+ Checks for: secrets in git history, inconsistent .env files across
186
+ environments, missing encryption, and CI/CD exposure risks.
187
+ """
188
+ console.print()
189
+ console.print("[yellow]Full security audit is a premium feature.[/yellow]")
190
+ console.print("Upgrade at https://kryptorious.gumroad.com/l/jbvet")
191
+ console.print()
192
+
193
+ # Run basic check as free preview
194
+ ctx = click.Context(check)
195
+ check.invoke(ctx)
196
+
197
+
198
+ @main.command()
199
+ @click.argument("file1")
200
+ @click.argument("file2")
201
+ def diff(file1, file2):
202
+ """Compare two .env files and show differences (premium).
203
+
204
+ \b
205
+ Example:
206
+ envguard diff .env .env.production
207
+ """
208
+ console.print()
209
+ console.print("[yellow]env diff is a premium feature.[/yellow]")
210
+ console.print("Upgrade at https://kryptorious.gumroad.com/l/jbvet")
211
+
212
+ v1 = _load_env(file1)
213
+ v2 = _load_env(file2)
214
+
215
+ all_keys = set(v1.keys()) | set(v2.keys())
216
+ only_in_1 = set(v1.keys()) - set(v2.keys())
217
+ only_in_2 = set(v2.keys()) - set(v1.keys())
218
+ different = {k for k in all_keys if k in v1 and k in v2 and v1[k] != v2[k]}
219
+
220
+ console.print(f"\n[bold]Preview:[/bold] {len(all_keys)} total keys")
221
+ if only_in_1:
222
+ console.print(f" Only in {file1}: {', '.join(sorted(only_in_1)[:5])}")
223
+ if only_in_2:
224
+ console.print(f" Only in {file2}: {', '.join(sorted(only_in_2)[:5])}")
225
+ if different:
226
+ console.print(f" Different values: {len(different)} keys")
227
+ console.print("\n[dim]Full diff requires premium upgrade.[/dim]")
228
+
229
+
230
+ if __name__ == "__main__":
231
+ main()
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: kryptorious-envguard
3
+ Version: 1.0.0
4
+ Summary: Environment variable management CLI — validate, generate, audit, and encrypt .env files.
5
+ Author: Kryptorious Quantum Biosciences, Inc.
6
+ License: MIT
7
+ Project-URL: Homepage, https://devflow.sh
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: click>=8.0
11
+ Requires-Dist: rich>=13.0
12
+ Requires-Dist: python-dotenv>=1.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
@@ -0,0 +1,7 @@
1
+ envguard/__init__.py,sha256=543ushF0dGjMf7cZE3YJbEX2mQd06DD9VkhYeD5z9hA,78
2
+ envguard/cli.py,sha256=YllqeIw_lCNfQWgp5ckxN9m_apEHcSLU_yylDJKoPyY,7881
3
+ kryptorious_envguard-1.0.0.dist-info/METADATA,sha256=r6Y9U8XgvpZ3KBotioUNrpe7M_IZL2Stm6nb9Il7G8w,491
4
+ kryptorious_envguard-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ kryptorious_envguard-1.0.0.dist-info/entry_points.txt,sha256=u3t7YuHqdCbAkAbK2_uy0bM0ERne6xgpvh-K26aplAI,47
6
+ kryptorious_envguard-1.0.0.dist-info/top_level.txt,sha256=yu47J3wd6FhM7bR522MayR5X1amyGhCGDH9fX4879M4,9
7
+ kryptorious_envguard-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ envguard = envguard.cli:main
@@ -0,0 +1 @@
1
+ envguard