@envsave/cli 1.0.37
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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cli.js +1604 -0
- package/dist/index.js +159 -0
- package/package.json +48 -0
- package/python/envsave/__init__.py +79 -0
- package/python/evault.py +85 -0
- package/python/pyproject.toml +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose C Sancho
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# EnvSave
|
|
2
|
+
|
|
3
|
+
A local encrypted vault for secrets (API keys, tokens, passwords) designed to be safe for use with LLM agents. Secrets are accessed via opaque keys (`es_...`) — agents can read values but can't discover or enumerate other secrets.
|
|
4
|
+
|
|
5
|
+
## The problem EnvSave solves
|
|
6
|
+
|
|
7
|
+
API keys, access tokens, database URLs, webhook secrets — **these must never be visible to AI assistants or coding agents you run on your codebase.** If an LLM can read a real secret, so can anyone watching the LLM:
|
|
8
|
+
|
|
9
|
+
- **Exfiltration.** An agent with tool access (shell, file read, HTTP) can quietly send keys to an external endpoint, log them into a transcript cached by a third party, or commit them to a public repo. You won't notice until the bill arrives.
|
|
10
|
+
- **Abuse by strangers.** A leaked OpenAI / Anthropic / Stripe / AWS key is an open tab on your credit card. Attackers scrape keys out of GitHub, npm tarballs, Discord pastes, and LLM transcripts within minutes of exposure.
|
|
11
|
+
- **Prompt injection.** A malicious webpage, PR description, or document pulled into the agent's context can instruct it to read `.env`, list environment variables, or print secrets as "debug output." If the secret is in your code or your environment, a compromised agent will surface it.
|
|
12
|
+
- **Context-window leakage.** Once a key is in the LLM's context it can be logged, embedded, cached, and potentially used to train future models — you've lost control of it.
|
|
13
|
+
- **Painful rotation.** Rotating a leaked production key means downtime, redeploys, and hunting down every place it was pasted.
|
|
14
|
+
|
|
15
|
+
**EnvSave is how you stop giving LLMs your real secrets.** Instead of exposing `sk-proj-...` or reading `.env` in your code, you hand the LLM an *opaque key* — a deterministic HMAC hash like `es_7a3f2b1e9c8d4f6b2a1c3d5f` — that only resolves to the real value through the encrypted vault on your machine. The agent can read your code, your `.env`, even the session cache, and still learn nothing: opaque keys can't be reversed, enumerated, or brute-forced without the master password.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// ❌ Bad — the LLM sees the real key in your code
|
|
19
|
+
const openai = new OpenAI({ apiKey: "sk-proj-abc123..." });
|
|
20
|
+
|
|
21
|
+
// ❌ Also bad — .env still contains the plaintext key, and agents read .env
|
|
22
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
23
|
+
|
|
24
|
+
// ✅ EnvSave — the agent only ever sees "es_7a3f...", useless outside your vault
|
|
25
|
+
import { get } from "@envsave/cli";
|
|
26
|
+
const openai = new OpenAI({ apiKey: get("es_7a3f2b1e9c8d4f6b2a1c3d5f") });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This is the single problem this project is built around. Everything else — the CLI, the license server, the Python bridge, the cloud backup — exists to make opaque-key-based secrets practical to use day to day.
|
|
30
|
+
|
|
31
|
+
## Free tier
|
|
32
|
+
|
|
33
|
+
Up to **5 secrets** with no license. Beyond that you'll need to activate a license (`envsave activate <token>`) — get yours at https://envsave.com.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Global CLI (binary will be available as `envsave`)
|
|
39
|
+
npm install -g @envsave/cli
|
|
40
|
+
|
|
41
|
+
# Or use it as a library
|
|
42
|
+
npm install @envsave/cli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Python users:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install envsave
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Activation
|
|
52
|
+
|
|
53
|
+
### Online (production)
|
|
54
|
+
|
|
55
|
+
Requires the license server running at `https://api.envsave.com`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. Register on the webapp to get a token
|
|
59
|
+
# 2. Activate
|
|
60
|
+
envsave activate <token>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The CLI calls `/api/license/validate` to verify the token's HMAC signature, expiry, and DB status.
|
|
64
|
+
|
|
65
|
+
### Offline (dev/testing)
|
|
66
|
+
|
|
67
|
+
Skips server validation. Only allowed for a small set of whitelisted emails maintained in `src/license.ts`.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
envsave activate <token> --offline
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This parses the signed token locally, checks the embedded email is in the allowed list, verifies expiry, and writes `~/.envsave/license.json`.
|
|
74
|
+
|
|
75
|
+
## Setup
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Initialize vault (creates ~/.envsave/vault.enc)
|
|
79
|
+
envsave init
|
|
80
|
+
|
|
81
|
+
# Optional: initialize with an existing .env file
|
|
82
|
+
envsave init --env .env
|
|
83
|
+
|
|
84
|
+
# Optional: add SSH key as second factor
|
|
85
|
+
envsave init --ssh-key ~/.ssh/id_ed25519
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You'll be prompted to set and confirm a master password.
|
|
89
|
+
|
|
90
|
+
## Storing Secrets
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
envsave set OPENAI_API_KEY sk-abc123
|
|
94
|
+
# Output:
|
|
95
|
+
# OPENAI_API_KEY → es_7a3f2b1e9c8d4f6b
|
|
96
|
+
# Use this in your code to access OPENAI_API_KEY:
|
|
97
|
+
# envsave.get("es_7a3f2b1e9c8d4f6b")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The opaque key (`es_...`) is an HMAC-SHA256 hash — no trace of the original key name.
|
|
101
|
+
|
|
102
|
+
## Retrieving Secrets
|
|
103
|
+
|
|
104
|
+
### CLI
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# By opaque key (tries session cache first, then prompts for password)
|
|
108
|
+
envsave get es_7a3f2b1e9c8d4f6b
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Node.js / Bun
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { get, has } from "@envsave/cli";
|
|
115
|
+
|
|
116
|
+
const apiKey = get("es_7a3f2b1e9c8d4f6b");
|
|
117
|
+
|
|
118
|
+
if (has("es_7a3f2b1e9c8d4f6b")) {
|
|
119
|
+
// secret exists
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Python
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from envsave import get
|
|
127
|
+
|
|
128
|
+
api_key = get("es_7a3f2b1e9c8d4f6b")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Other Commands
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Show opaque key for a named secret (requires password)
|
|
135
|
+
envsave reveal OPENAI_API_KEY
|
|
136
|
+
|
|
137
|
+
# Reveal multiple keys at once
|
|
138
|
+
envsave reveal OPENAI_API_KEY DATABASE_URL STRIPE_KEY
|
|
139
|
+
|
|
140
|
+
# List all stored key names (requires password)
|
|
141
|
+
envsave list
|
|
142
|
+
|
|
143
|
+
# Delete a secret (requires password)
|
|
144
|
+
envsave delete OPENAI_API_KEY
|
|
145
|
+
|
|
146
|
+
# Bulk import from .env file (requires password)
|
|
147
|
+
envsave import .env
|
|
148
|
+
|
|
149
|
+
# Export all secrets as .env format (requires password)
|
|
150
|
+
envsave export
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Cloud Backup & Sync
|
|
154
|
+
|
|
155
|
+
The encrypted vault can be backed up to the EnvSave cloud server for disaster recovery or syncing across machines. The server only stores the encrypted bytes — it never sees plaintext secrets.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Upload encrypted vault to cloud
|
|
159
|
+
envsave backup
|
|
160
|
+
|
|
161
|
+
# Download vault backup from cloud (prompts before overwriting)
|
|
162
|
+
envsave restore
|
|
163
|
+
|
|
164
|
+
# Smart sync — auto-detects which direction to sync
|
|
165
|
+
envsave sync
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Sync behavior
|
|
169
|
+
|
|
170
|
+
| Scenario | Action |
|
|
171
|
+
|---|---|
|
|
172
|
+
| Both match (same checksum) | "Already in sync" |
|
|
173
|
+
| Local only, no cloud backup | Auto-pushes to cloud |
|
|
174
|
+
| Cloud only, no local vault | Auto-pulls from cloud |
|
|
175
|
+
| Both exist but differ | Asks: pull (p), push (l), or cancel (c) |
|
|
176
|
+
| Neither exists | Tells user to run `envsave init` |
|
|
177
|
+
|
|
178
|
+
### Setting up on a new machine or remote server
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# 1. Install envsave
|
|
182
|
+
bun install @envsave/cli
|
|
183
|
+
|
|
184
|
+
# 2. Activate with same license token
|
|
185
|
+
envsave activate <token>
|
|
186
|
+
|
|
187
|
+
# 3. Pull vault from cloud
|
|
188
|
+
envsave sync
|
|
189
|
+
# or: envsave restore
|
|
190
|
+
|
|
191
|
+
# 4. Verify
|
|
192
|
+
envsave list
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Security Model
|
|
196
|
+
|
|
197
|
+
1. Vault is encrypted with AES-256-GCM + PBKDF2 key derivation
|
|
198
|
+
2. Stored at `~/.envsave/vault.enc` (binary: salt + iv + encrypted data + auth tag)
|
|
199
|
+
3. Key names are HMAC-SHA256 hashes — fully opaque, no trace of original name
|
|
200
|
+
4. Session cache stores decrypted secrets in memory — no password needed for repeated reads
|
|
201
|
+
5. Optional SSH key adds a "something you have" hardware factor
|
|
202
|
+
6. LLM agents only see opaque keys — they can read values but can't discover or enumerate secrets
|
|
203
|
+
|
|
204
|
+
## License Tokens
|
|
205
|
+
|
|
206
|
+
Tokens are self-contained signed strings: `es_<base64url(email|created_ts|expires_ts|hmac)>`
|
|
207
|
+
|
|
208
|
+
- Dates are embedded inside the token (opaque to users)
|
|
209
|
+
- HMAC signature prevents tampering (verified server-side)
|
|
210
|
+
- Server also checks DB for revocation support
|
|
211
|
+
- Offline mode reads expiry from the token directly without server call
|
|
212
|
+
|
|
213
|
+
### Auto-Renewal
|
|
214
|
+
|
|
215
|
+
When a license token expires, the CLI automatically contacts the server to renew it — no user action needed. The renewal succeeds if the user's subscription is still active (user exists in DB and hasn't been revoked). The new token is saved locally and the command proceeds normally.
|
|
216
|
+
|
|
217
|
+
## File Locations
|
|
218
|
+
|
|
219
|
+
| File | Purpose |
|
|
220
|
+
|------|---------|
|
|
221
|
+
| `~/.envsave/vault.enc` | Encrypted vault |
|
|
222
|
+
| `~/.envsave/license.json` | Activated license |
|
|
223
|
+
| `~/.envsave/session.json` | Session cache (decrypted secrets) |
|