secenvvault 1.0.1__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.
- secenvvault/__init__.py +60 -0
- secenvvault/cli.py +72 -0
- secenvvault/client.py +91 -0
- secenvvault-1.0.1.dist-info/METADATA +91 -0
- secenvvault-1.0.1.dist-info/RECORD +8 -0
- secenvvault-1.0.1.dist-info/WHEEL +5 -0
- secenvvault-1.0.1.dist-info/entry_points.txt +3 -0
- secenvvault-1.0.1.dist-info/top_level.txt +1 -0
secenvvault/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SecEnvVault Python SDK
|
|
3
|
+
Zero-Knowledge secret manager and runtime environment injector.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from typing import Optional, Dict, Iterator
|
|
9
|
+
from .client import SecEnvVaultClient, find_project_config, load_credentials
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.1"
|
|
12
|
+
__all__ = ["SecEnvVaultClient", "load_secrets", "with_secrets", "find_project_config"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_secrets(
|
|
16
|
+
environment: Optional[str] = None,
|
|
17
|
+
project_id: Optional[str] = None,
|
|
18
|
+
api_url: Optional[str] = None,
|
|
19
|
+
token: Optional[str] = None,
|
|
20
|
+
) -> Dict[str, str]:
|
|
21
|
+
"""
|
|
22
|
+
Loads project secrets into os.environ.
|
|
23
|
+
Ideal for Django, FastAPI, Flask, and Celery applications at startup.
|
|
24
|
+
"""
|
|
25
|
+
client = SecEnvVaultClient(token=token, api_url=api_url, project_id=project_id, environment=environment or "development")
|
|
26
|
+
secrets = client.get_all_secrets(environment)
|
|
27
|
+
|
|
28
|
+
for k, v in secrets.items():
|
|
29
|
+
if k not in os.environ:
|
|
30
|
+
os.environ[k] = str(v)
|
|
31
|
+
|
|
32
|
+
return secrets
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@contextmanager
|
|
36
|
+
def with_secrets(
|
|
37
|
+
environment: Optional[str] = None,
|
|
38
|
+
project_id: Optional[str] = None,
|
|
39
|
+
api_url: Optional[str] = None,
|
|
40
|
+
token: Optional[str] = None,
|
|
41
|
+
) -> Iterator[Dict[str, str]]:
|
|
42
|
+
"""
|
|
43
|
+
Context manager that temporarily injects secrets into os.environ
|
|
44
|
+
and cleanly restores original environment state upon exit.
|
|
45
|
+
"""
|
|
46
|
+
client = SecEnvVaultClient(token=token, api_url=api_url, project_id=project_id, environment=environment or "development")
|
|
47
|
+
secrets = client.get_all_secrets(environment)
|
|
48
|
+
|
|
49
|
+
original_env = dict(os.environ)
|
|
50
|
+
try:
|
|
51
|
+
for k, v in secrets.items():
|
|
52
|
+
os.environ[k] = str(v)
|
|
53
|
+
yield secrets
|
|
54
|
+
finally:
|
|
55
|
+
# Restore environment
|
|
56
|
+
for k in secrets:
|
|
57
|
+
if k in original_env:
|
|
58
|
+
os.environ[k] = original_env[k]
|
|
59
|
+
else:
|
|
60
|
+
os.environ.pop(k, None)
|
secenvvault/cli.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SecEnvVault Python CLI Runner
|
|
3
|
+
Allows executing commands with memory-injected secrets in Python-only environments.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import subprocess
|
|
8
|
+
try:
|
|
9
|
+
from .client import SecEnvVaultClient
|
|
10
|
+
except ImportError:
|
|
11
|
+
from secenvvault.client import SecEnvVaultClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
args = sys.argv[1:]
|
|
16
|
+
if not args or args[0] in ("-h", "--help"):
|
|
17
|
+
print("""
|
|
18
|
+
SecEnvVault CLI (Python Edition)
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
secenvvault run -e <environment> -- <command...>
|
|
22
|
+
sev run -- python app.py
|
|
23
|
+
""")
|
|
24
|
+
sys.exit(0)
|
|
25
|
+
|
|
26
|
+
cmd = args[0]
|
|
27
|
+
if cmd == "run":
|
|
28
|
+
rest = args[1:]
|
|
29
|
+
env = "development"
|
|
30
|
+
if "-e" in rest:
|
|
31
|
+
idx = rest.index("-e")
|
|
32
|
+
env = rest[idx + 1]
|
|
33
|
+
rest = rest[:idx] + rest[idx + 2:]
|
|
34
|
+
elif "--env" in rest:
|
|
35
|
+
idx = rest.index("--env")
|
|
36
|
+
env = rest[idx + 1]
|
|
37
|
+
rest = rest[:idx] + rest[idx + 2:]
|
|
38
|
+
|
|
39
|
+
if "--" in rest:
|
|
40
|
+
dash_idx = rest.index("--")
|
|
41
|
+
subcommand = rest[dash_idx + 1:]
|
|
42
|
+
else:
|
|
43
|
+
subcommand = rest
|
|
44
|
+
|
|
45
|
+
if not subcommand:
|
|
46
|
+
print("Error: No command specified to run.")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
client = SecEnvVaultClient()
|
|
51
|
+
secrets = client.get_all_secrets(env)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Error fetching secrets: {e}", file=sys.stderr)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
import os
|
|
57
|
+
merged_env = os.environ.copy()
|
|
58
|
+
merged_env.update(secrets)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
res = subprocess.run(subcommand, env=merged_env)
|
|
62
|
+
sys.exit(res.returncode)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Execution failed: {e}", file=sys.stderr)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
else:
|
|
67
|
+
print(f"Unknown command: {cmd}. Run with --help for options.")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
main()
|
secenvvault/client.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import urllib.request
|
|
5
|
+
import urllib.error
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional, Any
|
|
8
|
+
|
|
9
|
+
CONFIG_FILE_NAME = ".secenv.json"
|
|
10
|
+
CREDENTIALS_FILE = Path.home() / ".secenv" / "credentials.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_project_config(start_dir: Optional[Path] = None) -> Optional[Dict[str, Any]]:
|
|
14
|
+
curr = (start_dir or Path.cwd()).resolve()
|
|
15
|
+
while True:
|
|
16
|
+
candidate = curr / CONFIG_FILE_NAME
|
|
17
|
+
if candidate.is_file():
|
|
18
|
+
try:
|
|
19
|
+
with open(candidate, "r", encoding="utf-8") as f:
|
|
20
|
+
return json.load(f)
|
|
21
|
+
except Exception:
|
|
22
|
+
return None
|
|
23
|
+
if curr.parent == curr:
|
|
24
|
+
break
|
|
25
|
+
curr = curr.parent
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_credentials() -> Dict[str, str]:
|
|
30
|
+
token = os.environ.get("SECENV_TOKEN") or os.environ.get("SECENV_SERVICE_TOKEN")
|
|
31
|
+
api_url = os.environ.get("SECENV_API_URL")
|
|
32
|
+
|
|
33
|
+
creds: Dict[str, str] = {}
|
|
34
|
+
if CREDENTIALS_FILE.is_file():
|
|
35
|
+
try:
|
|
36
|
+
with open(CREDENTIALS_FILE, "r", encoding="utf-8") as f:
|
|
37
|
+
creds = json.load(f)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"token": token or creds.get("token", ""),
|
|
43
|
+
"api_url": api_url or creds.get("apiUrl", "http://localhost:3000"),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SecEnvVaultClient:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
token: Optional[str] = None,
|
|
51
|
+
api_url: Optional[str] = None,
|
|
52
|
+
project_id: Optional[str] = None,
|
|
53
|
+
environment: str = "development",
|
|
54
|
+
):
|
|
55
|
+
creds = load_credentials()
|
|
56
|
+
self.token = token or creds.get("token")
|
|
57
|
+
self.api_url = (api_url or creds.get("api_url", "http://localhost:3000")).rstrip("/")
|
|
58
|
+
|
|
59
|
+
cfg = find_project_config()
|
|
60
|
+
self.project_id = project_id or (cfg.get("projectId") if cfg else None)
|
|
61
|
+
self.default_environment = environment or (cfg.get("defaultEnvironment") if cfg else "development")
|
|
62
|
+
|
|
63
|
+
def _request(self, endpoint: str) -> Dict[str, str]:
|
|
64
|
+
url = f"{self.api_url}/{endpoint.lstrip('/')}"
|
|
65
|
+
headers = {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
"User-Agent": "secenvvault-python/1.0.1",
|
|
68
|
+
}
|
|
69
|
+
if self.token:
|
|
70
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
71
|
+
|
|
72
|
+
req = urllib.request.Request(url, headers=headers)
|
|
73
|
+
try:
|
|
74
|
+
with urllib.request.urlopen(req) as resp:
|
|
75
|
+
data = resp.read().decode("utf-8")
|
|
76
|
+
return json.loads(data)
|
|
77
|
+
except urllib.error.HTTPError as e:
|
|
78
|
+
err_body = e.read().decode("utf-8")
|
|
79
|
+
raise RuntimeError(f"SecEnvVault API error ({e.code}): {err_body}")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise RuntimeError(f"Connection failed: {str(e)}")
|
|
82
|
+
|
|
83
|
+
def get_all_secrets(self, environment: Optional[str] = None) -> Dict[str, str]:
|
|
84
|
+
env = environment or self.default_environment
|
|
85
|
+
if not self.project_id:
|
|
86
|
+
raise ValueError("No projectId provided or found in .secenv.json")
|
|
87
|
+
return self._request(f"/api/v1/projects/{self.project_id}/environments/{env}/raw?format=json")
|
|
88
|
+
|
|
89
|
+
def get_secret(self, key: str, environment: Optional[str] = None) -> Optional[str]:
|
|
90
|
+
secrets = self.get_all_secrets(environment)
|
|
91
|
+
return secrets.get(key)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secenvvault
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Zero-Knowledge .env & secret orchestration CLI and SDK for developers and teams.
|
|
5
|
+
Author: SecEnvVault Team
|
|
6
|
+
Author-email: SecEnvVault Team <support@secenvvault.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: secrets,env,dotenv,vault,zero-knowledge,security,credentials
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: requires-python
|
|
17
|
+
|
|
18
|
+
# SecEnvVault Python SDK & CLI
|
|
19
|
+
|
|
20
|
+
> Zero-Knowledge .env & secret orchestration CLI and SDK for Python developers, teams, and CI/CD pipelines.
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/secenvvault/)
|
|
23
|
+
[](https://opensource.org/licenses/MIT)
|
|
24
|
+
|
|
25
|
+
SecEnvVault eliminates plaintext `.env` files floating across developer machines and repositories. Secrets are injected directly into process memory with **zero disk writes**.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📦 Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install secenvvault
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Quickstart
|
|
38
|
+
|
|
39
|
+
### 1. Python SDK Usage
|
|
40
|
+
|
|
41
|
+
#### Automatic `os.environ` Injection at Startup (Django, FastAPI, Flask):
|
|
42
|
+
```python
|
|
43
|
+
import os
|
|
44
|
+
from secenvvault import load_secrets
|
|
45
|
+
|
|
46
|
+
# Loads secrets into os.environ before app initialization:
|
|
47
|
+
load_secrets(environment="production")
|
|
48
|
+
|
|
49
|
+
print(os.getenv("DATABASE_URL"))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Scoped Sandbox Execution (Context Manager):
|
|
53
|
+
```python
|
|
54
|
+
from secenvvault import with_secrets
|
|
55
|
+
|
|
56
|
+
# Temporarily injects secrets and restores previous environment on exit:
|
|
57
|
+
with with_secrets(environment="staging"):
|
|
58
|
+
run_database_migrations()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Programmatic Client:
|
|
62
|
+
```python
|
|
63
|
+
from secenvvault import SecEnvVaultClient
|
|
64
|
+
|
|
65
|
+
client = SecEnvVaultClient()
|
|
66
|
+
stripe_key = client.get_secret("STRIPE_SECRET_KEY", environment="production")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## ⚡ CLI Runner
|
|
72
|
+
|
|
73
|
+
The Python package includes the CLI runner for Python-only environments:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
secenvvault run -e production -- python main.py
|
|
77
|
+
# or shorthand:
|
|
78
|
+
sev run -- uvicorn app.main:app --port 8000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🛡️ Security
|
|
84
|
+
* **Zero Third-Party Dependencies**: Built exclusively with Python standard library.
|
|
85
|
+
* **In-Memory Injection**: Process environment variables never touch disk.
|
|
86
|
+
* **Encrypted API Communication**: Direct HTTPS with SecEnvVault backend.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 📄 License
|
|
91
|
+
MIT © SecEnvVault
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
secenvvault/__init__.py,sha256=VHc70fsjixiN73ux0-PgJ_Loevyk4TGSEw-nad0G3W0,1876
|
|
2
|
+
secenvvault/cli.py,sha256=MwPtAl9Rz-syCbFBCQmTlY60-nuxNiB3zVtNMxmYrCQ,1851
|
|
3
|
+
secenvvault/client.py,sha256=PkKlu3g0s0v5uyb1PZ7xF84MZHgZmMZtsRNa-K8o390,3268
|
|
4
|
+
secenvvault-1.0.1.dist-info/METADATA,sha256=93IXzxAE881GcoIU_ep4thvLQdMK3IojUMZ9CQHDsk4,2588
|
|
5
|
+
secenvvault-1.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
secenvvault-1.0.1.dist-info/entry_points.txt,sha256=mcGjw_pm_c2F9UFhuMkgL3o_i42ZJJwdcsB4tNTXzlQ,80
|
|
7
|
+
secenvvault-1.0.1.dist-info/top_level.txt,sha256=anrZO7VLMkFl4NfPwSKlrGmDJiDE8Ls63HL0Y4arMAs,12
|
|
8
|
+
secenvvault-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
secenvvault
|