@chriscode/hush 2.4.1 → 2.4.2
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/README.md +446 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# @chriscode/hush
|
|
2
|
+
|
|
3
|
+
> **The AI-native secrets manager.** Secrets stay encrypted at rest. AI helps—without ever seeing values.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@chriscode/hush)
|
|
6
|
+
[](https://hush-docs.pages.dev)
|
|
7
|
+
|
|
8
|
+
Hush keeps secrets **encrypted at rest** and only decrypts them in memory when running programs. AI assistants can help manage your secrets without ever seeing the actual values—because there are no plaintext files to read.
|
|
9
|
+
|
|
10
|
+
**[Read the full documentation →](https://hush-docs.pages.dev)**
|
|
11
|
+
|
|
12
|
+
## Quick Start (with AI)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @chriscode/hush skill
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That's it. Once installed, ask your AI assistant: *"Set up Hush for this project"* — it knows what to do.
|
|
19
|
+
|
|
20
|
+
## Why Hush?
|
|
21
|
+
|
|
22
|
+
**The Problem:** AI coding assistants are incredibly helpful, but they can accidentally expose your secrets. Even with instructions to "not read .env files", LLMs find creative ways to access them using `cat`, `grep`, or shell tricks.
|
|
23
|
+
|
|
24
|
+
**The Solution:** Hush keeps secrets **encrypted at rest**—there are no plaintext `.env` files to read. When you need to run a program, `hush run -- <command>` decrypts secrets to memory and injects them as environment variables. The secrets never touch the disk.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Encrypted at rest** - No plaintext secrets on disk, ever
|
|
29
|
+
- **Run with secrets** - `hush run -- npm start` decrypts to memory only
|
|
30
|
+
- **AI-safe commands** - `hush inspect`, `hush has`, `hush set` never expose values
|
|
31
|
+
- **Interactive secret input** - `hush set API_KEY` prompts user, AI never sees value
|
|
32
|
+
- **Every framework** - Next.js, Vite, Remix, Expo, Cloudflare Workers, and more
|
|
33
|
+
- **Smart routing** - Route `NEXT_PUBLIC_*` to frontend, server secrets to API
|
|
34
|
+
- **Claude Code Skill** - AI automatically uses safe commands
|
|
35
|
+
|
|
36
|
+
## Framework Support
|
|
37
|
+
|
|
38
|
+
Hush works with **every major framework** out of the box. Use `include`/`exclude` patterns to route the right variables to each target:
|
|
39
|
+
|
|
40
|
+
| Framework | Client Prefix | Example Pattern |
|
|
41
|
+
|-----------|--------------|-----------------|
|
|
42
|
+
| **Next.js** | `NEXT_PUBLIC_*` | `include: [NEXT_PUBLIC_*]` |
|
|
43
|
+
| **Vite** | `VITE_*` | `include: [VITE_*]` |
|
|
44
|
+
| **Create React App** | `REACT_APP_*` | `include: [REACT_APP_*]` |
|
|
45
|
+
| **Vue CLI** | `VUE_APP_*` | `include: [VUE_APP_*]` |
|
|
46
|
+
| **Nuxt** | `NUXT_PUBLIC_*` | `include: [NUXT_PUBLIC_*]` |
|
|
47
|
+
| **Astro** | `PUBLIC_*` | `include: [PUBLIC_*]` |
|
|
48
|
+
| **SvelteKit** | `PUBLIC_*` | `include: [PUBLIC_*]` |
|
|
49
|
+
| **Expo / React Native** | `EXPO_PUBLIC_*` | `include: [EXPO_PUBLIC_*]` |
|
|
50
|
+
| **Gatsby** | `GATSBY_*` | `include: [GATSBY_*]` |
|
|
51
|
+
| **Remix** | (server-only) | No filtering needed |
|
|
52
|
+
| **Cloudflare Workers** | (server-only) | `format: wrangler` |
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add -D @chriscode/hush
|
|
58
|
+
# or
|
|
59
|
+
npm install -D @chriscode/hush
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
brew install sops age
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Optional but recommended:** Install 1Password CLI for automatic key backup:
|
|
69
|
+
```bash
|
|
70
|
+
brew install --cask 1password
|
|
71
|
+
brew install 1password-cli
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
### 1. Initialize Hush (auto-generates keys)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx hush init
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This will:
|
|
83
|
+
- Auto-detect your project structure
|
|
84
|
+
- Generate an age encryption key
|
|
85
|
+
- Back up the key to 1Password (if available)
|
|
86
|
+
- Create `hush.yaml` and `.sops.yaml`
|
|
87
|
+
|
|
88
|
+
**No 1Password?** Keys are saved locally to `~/.config/sops/age/keys/`. Share them securely with your team.
|
|
89
|
+
|
|
90
|
+
### 3. Create your env files
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# .env (shared across environments)
|
|
94
|
+
DATABASE_URL=postgres://localhost/mydb
|
|
95
|
+
STRIPE_SECRET_KEY=sk_test_xxx
|
|
96
|
+
NEXT_PUBLIC_API_URL=${API_BASE}/v1
|
|
97
|
+
|
|
98
|
+
# .env.development
|
|
99
|
+
API_BASE=http://localhost:3000
|
|
100
|
+
DEBUG=true
|
|
101
|
+
|
|
102
|
+
# .env.production
|
|
103
|
+
API_BASE=https://api.example.com
|
|
104
|
+
DEBUG=false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Encrypt and run
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx hush encrypt # Encrypt secrets
|
|
111
|
+
npx hush run -- npm start # Run with secrets (never written to disk!)
|
|
112
|
+
npx hush run -e prod -- npm build # Run with production secrets
|
|
113
|
+
npx hush status # Check your setup
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
### hush.yaml
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
sources:
|
|
122
|
+
shared: .env
|
|
123
|
+
development: .env.development
|
|
124
|
+
production: .env.production
|
|
125
|
+
|
|
126
|
+
targets:
|
|
127
|
+
# Root gets all variables
|
|
128
|
+
- name: root
|
|
129
|
+
path: .
|
|
130
|
+
format: dotenv
|
|
131
|
+
|
|
132
|
+
# Next.js app gets only public variables
|
|
133
|
+
- name: web
|
|
134
|
+
path: ./apps/web
|
|
135
|
+
format: dotenv
|
|
136
|
+
include:
|
|
137
|
+
- NEXT_PUBLIC_*
|
|
138
|
+
|
|
139
|
+
# API gets everything except public variables
|
|
140
|
+
- name: api
|
|
141
|
+
path: ./apps/api
|
|
142
|
+
format: wrangler
|
|
143
|
+
exclude:
|
|
144
|
+
- NEXT_PUBLIC_*
|
|
145
|
+
- VITE_*
|
|
146
|
+
- EXPO_PUBLIC_*
|
|
147
|
+
|
|
148
|
+
# Kubernetes config
|
|
149
|
+
- name: k8s
|
|
150
|
+
path: ./k8s
|
|
151
|
+
format: yaml
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Output Formats
|
|
155
|
+
|
|
156
|
+
| Format | Output File | Use Case |
|
|
157
|
+
|--------|-------------|----------|
|
|
158
|
+
| `dotenv` | `.env.development` | Next.js, Vite, CRA, Vue, Nuxt, Remix, Astro, SvelteKit, Expo, Node.js |
|
|
159
|
+
| `wrangler` | `.dev.vars` | Cloudflare Workers & Pages |
|
|
160
|
+
| `json` | `.env.development.json` | AWS Lambda, serverless functions, custom tooling |
|
|
161
|
+
| `shell` | `.env.development.sh` | CI/CD pipelines, Docker builds, shell scripts |
|
|
162
|
+
| `yaml` | `.env.development.yaml` | Kubernetes ConfigMaps, Docker Compose |
|
|
163
|
+
|
|
164
|
+
### Target Options
|
|
165
|
+
|
|
166
|
+
| Option | Description |
|
|
167
|
+
|--------|-------------|
|
|
168
|
+
| `name` | Identifier for the target |
|
|
169
|
+
| `path` | Directory to write output file |
|
|
170
|
+
| `format` | Output format: `dotenv`, `wrangler`, `json`, `shell`, `yaml` |
|
|
171
|
+
| `include` | Glob patterns to include (e.g., `NEXT_PUBLIC_*`) |
|
|
172
|
+
| `exclude` | Glob patterns to exclude |
|
|
173
|
+
|
|
174
|
+
## Commands
|
|
175
|
+
|
|
176
|
+
| Command | Description | AI-Safe? |
|
|
177
|
+
|---------|-------------|----------|
|
|
178
|
+
| `hush run -- <cmd>` | Run command with secrets in memory | ✅ |
|
|
179
|
+
| `hush set <KEY>` | Set a secret interactively | ✅ |
|
|
180
|
+
| `hush set <KEY> --gui` | Set secret via macOS dialog (for AI agents) | ✅ |
|
|
181
|
+
| `hush edit [env]` | Edit secrets in `$EDITOR` | ✅ |
|
|
182
|
+
| `hush inspect` | List variables (masked values) | ✅ |
|
|
183
|
+
| `hush has <KEY>` | Check if a secret exists | ✅ |
|
|
184
|
+
| `hush init` | Generate config + keys (auto 1Password backup) | ✅ |
|
|
185
|
+
| `hush encrypt` | Encrypt `.env` files | ✅ |
|
|
186
|
+
| `hush keys setup` | Pull key from 1Password or use local | ✅ |
|
|
187
|
+
| `hush keys generate` | Generate new key + backup to 1Password | ✅ |
|
|
188
|
+
| `hush keys list` | List local and 1Password keys | ✅ |
|
|
189
|
+
| `hush push` | Push to Cloudflare Workers | ✅ |
|
|
190
|
+
| `hush status` | Show configuration | ✅ |
|
|
191
|
+
| `hush skill` | Install AI skill | ✅ |
|
|
192
|
+
| `hush check` | Verify encryption sync | ✅ |
|
|
193
|
+
| `hush list` | List variables (shows values!) | ⚠️ |
|
|
194
|
+
| `hush decrypt` | Write secrets to disk (deprecated) | ⚠️ |
|
|
195
|
+
|
|
196
|
+
## AI-Native Design
|
|
197
|
+
|
|
198
|
+
Hush is built for a world where AI helps write code. Traditional secrets management fails because LLMs can read `.env` files using `cat`, `grep`, or other tools—even when told not to.
|
|
199
|
+
|
|
200
|
+
**Hush solves this by keeping secrets encrypted at rest.** There are no plaintext files to read. When you need secrets, `hush run` decrypts them to memory and injects them as environment variables.
|
|
201
|
+
|
|
202
|
+
### `hush run` - Run Programs with Secrets
|
|
203
|
+
|
|
204
|
+
The primary way to use secrets. Decrypts to memory, never writes to disk.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
$ hush run -- npm start # Development
|
|
208
|
+
$ hush run -e prod -- npm build # Production
|
|
209
|
+
$ hush run -t api -- wrangler dev # Filter for specific target
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### `hush set <KEY>` - Add Secrets Safely
|
|
213
|
+
|
|
214
|
+
AI invokes this command, user enters the value. The secret is never visible to the AI.
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
$ hush set DATABASE_URL
|
|
218
|
+
Enter value for DATABASE_URL: ••••••••••••••••
|
|
219
|
+
✓ DATABASE_URL set (45 chars, encrypted)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `hush inspect` - See What's Configured
|
|
223
|
+
|
|
224
|
+
Shows all secrets with **masked values**. AI can see what exists without seeing actual secrets.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
$ hush inspect
|
|
228
|
+
|
|
229
|
+
Secrets for development:
|
|
230
|
+
|
|
231
|
+
DATABASE_URL = post****************... (45 chars)
|
|
232
|
+
STRIPE_SECRET_KEY = sk_t****************... (32 chars)
|
|
233
|
+
API_KEY = (not set)
|
|
234
|
+
|
|
235
|
+
Total: 3 variables
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `hush has <KEY>` - Check Specific Secrets
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
$ hush has DATABASE_URL
|
|
242
|
+
DATABASE_URL is set (45 chars)
|
|
243
|
+
|
|
244
|
+
$ hush has MISSING_KEY
|
|
245
|
+
MISSING_KEY not found
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Claude Code / OpenCode Skill
|
|
249
|
+
|
|
250
|
+
For [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [OpenCode](https://github.com/opencode-ai/opencode) users, Hush includes a ready-to-use **Agent Skill** that automatically teaches the AI to never read `.env` files directly.
|
|
251
|
+
|
|
252
|
+
**Install the skill:**
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx hush skill # Interactive: choose global or local
|
|
256
|
+
npx hush skill --global # Install to ~/.claude/skills/ (all projects)
|
|
257
|
+
npx hush skill --local # Install to ./.claude/skills/ (this project)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Global vs Local:**
|
|
261
|
+
- **Global** (`~/.claude/skills/`) - Works across all your projects. Recommended for personal use.
|
|
262
|
+
- **Local** (`./.claude/skills/`) - Bundled with the project. Recommended for teams (commit to git).
|
|
263
|
+
|
|
264
|
+
**What the skill does:**
|
|
265
|
+
- Detects when you're working with secrets or environment variables
|
|
266
|
+
- Uses `hush inspect` and `hush has` instead of reading `.env` files
|
|
267
|
+
- Guides you through adding or modifying secrets safely
|
|
268
|
+
- Never exposes secret values to the LLM
|
|
269
|
+
|
|
270
|
+
The skill includes `SKILL.md` (core instructions), `REFERENCE.md` (command details), and `examples/workflows.md` (step-by-step guides).
|
|
271
|
+
|
|
272
|
+
## Example: Monorepo with Next.js + Cloudflare Worker
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
# hush.yaml
|
|
276
|
+
sources:
|
|
277
|
+
shared: .env
|
|
278
|
+
development: .env.development
|
|
279
|
+
production: .env.production
|
|
280
|
+
|
|
281
|
+
targets:
|
|
282
|
+
# Next.js frontend - only public vars
|
|
283
|
+
- name: web
|
|
284
|
+
path: ./apps/web
|
|
285
|
+
format: dotenv
|
|
286
|
+
include:
|
|
287
|
+
- NEXT_PUBLIC_*
|
|
288
|
+
|
|
289
|
+
# Cloudflare Worker API - server secrets only
|
|
290
|
+
- name: api
|
|
291
|
+
path: ./apps/api
|
|
292
|
+
format: wrangler
|
|
293
|
+
exclude:
|
|
294
|
+
- NEXT_PUBLIC_*
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# .env
|
|
299
|
+
DATABASE_URL=postgres://...
|
|
300
|
+
STRIPE_SECRET_KEY=sk_live_...
|
|
301
|
+
NEXT_PUBLIC_API_URL=${API_BASE}/v1
|
|
302
|
+
NEXT_PUBLIC_STRIPE_KEY=pk_live_...
|
|
303
|
+
|
|
304
|
+
# .env.development
|
|
305
|
+
API_BASE=http://localhost:8787
|
|
306
|
+
|
|
307
|
+
# .env.production
|
|
308
|
+
API_BASE=https://api.myapp.com
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
When running with `hush run -t web -- npm start`:
|
|
312
|
+
- Web app receives only `NEXT_PUBLIC_*` variables in memory
|
|
313
|
+
- API receives `DATABASE_URL`, `STRIPE_SECRET_KEY` (no public vars)
|
|
314
|
+
|
|
315
|
+
## How It Works
|
|
316
|
+
|
|
317
|
+
### Source File Merging
|
|
318
|
+
|
|
319
|
+
When you run `hush run`:
|
|
320
|
+
|
|
321
|
+
1. **Shared** (`.env.encrypted`) - Base variables
|
|
322
|
+
2. **Environment** (`.env.development.encrypted` or `.env.production.encrypted`) - Overrides
|
|
323
|
+
3. **Local** (`.env.local.encrypted`) - Personal overrides (not committed)
|
|
324
|
+
|
|
325
|
+
Later files override earlier ones for the same key. All decryption happens in memory.
|
|
326
|
+
|
|
327
|
+
### Variable Interpolation
|
|
328
|
+
|
|
329
|
+
Reference other variables with `${VAR}`:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
HOST=localhost
|
|
333
|
+
PORT=3000
|
|
334
|
+
BASE_URL=http://${HOST}:${PORT}
|
|
335
|
+
API_URL=${BASE_URL}/api
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Target Filtering
|
|
339
|
+
|
|
340
|
+
Use `include` and `exclude` patterns to route variables to the right places:
|
|
341
|
+
|
|
342
|
+
```yaml
|
|
343
|
+
targets:
|
|
344
|
+
- name: frontend
|
|
345
|
+
include: [NEXT_PUBLIC_*, VITE_*] # Only client-safe vars
|
|
346
|
+
|
|
347
|
+
- name: backend
|
|
348
|
+
exclude: [NEXT_PUBLIC_*, VITE_*] # Everything except client vars
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Git Hook Integration
|
|
352
|
+
|
|
353
|
+
Prevent committing unencrypted changes with `hush check`:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# .husky/pre-commit
|
|
357
|
+
npx hush check || exit 1
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Bypass when needed: `HUSH_SKIP_CHECK=1 git commit -m "emergency fix"`
|
|
361
|
+
|
|
362
|
+
## File Reference
|
|
363
|
+
|
|
364
|
+
| File | Committed | Purpose |
|
|
365
|
+
|------|-----------|---------|
|
|
366
|
+
| `hush.yaml` | Yes | Configuration |
|
|
367
|
+
| `.sops.yaml` | Yes | SOPS config with public key |
|
|
368
|
+
| `.env.encrypted` | Yes | Encrypted shared secrets |
|
|
369
|
+
| `.env.development.encrypted` | Yes | Encrypted dev secrets |
|
|
370
|
+
| `.env.production.encrypted` | Yes | Encrypted prod secrets |
|
|
371
|
+
| `.env.local.encrypted` | No | Encrypted personal overrides |
|
|
372
|
+
|
|
373
|
+
**Note:** With the new `hush run` command, plaintext `.env` files are no longer generated. Secrets only exist in memory when running programs.
|
|
374
|
+
|
|
375
|
+
## Programmatic Usage
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import {
|
|
379
|
+
loadConfig,
|
|
380
|
+
parseEnvContent,
|
|
381
|
+
interpolateVars,
|
|
382
|
+
filterVarsForTarget,
|
|
383
|
+
mergeVars,
|
|
384
|
+
formatVars,
|
|
385
|
+
} from '@chriscode/hush';
|
|
386
|
+
|
|
387
|
+
const config = loadConfig('/path/to/repo');
|
|
388
|
+
const vars = parseEnvContent(decryptedContent);
|
|
389
|
+
const interpolated = interpolateVars(vars);
|
|
390
|
+
const filtered = filterVarsForTarget(interpolated, config.targets[0]);
|
|
391
|
+
const output = formatVars(filtered, 'dotenv');
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Team Setup
|
|
395
|
+
|
|
396
|
+
### New team member onboarding
|
|
397
|
+
|
|
398
|
+
**With 1Password (recommended):**
|
|
399
|
+
```bash
|
|
400
|
+
npx hush keys setup # Pulls key from 1Password automatically
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Without 1Password:**
|
|
404
|
+
1. Get the private key from a team member (via secure channel)
|
|
405
|
+
2. Save to `~/.config/sops/age/keys/{project}.txt`
|
|
406
|
+
3. Run `npx hush status` to verify
|
|
407
|
+
|
|
408
|
+
### Key management commands
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
hush keys setup # Pull from 1Password or verify local key
|
|
412
|
+
hush keys generate # Generate new key + backup to 1Password
|
|
413
|
+
hush keys pull # Pull key from 1Password
|
|
414
|
+
hush keys push # Push local key to 1Password
|
|
415
|
+
hush keys list # List all keys (local + 1Password)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Troubleshooting
|
|
419
|
+
|
|
420
|
+
### "SOPS is not installed" or "age not found"
|
|
421
|
+
```bash
|
|
422
|
+
brew install sops age
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### "No identity matched"
|
|
426
|
+
Your age key doesn't match the one used to encrypt. Options:
|
|
427
|
+
1. **With 1Password:** Run `hush keys setup` to pull the correct key
|
|
428
|
+
2. **Without 1Password:** Get the private key from a team member
|
|
429
|
+
|
|
430
|
+
### "1Password CLI not available"
|
|
431
|
+
Hush works without 1Password - keys are stored locally. For backup:
|
|
432
|
+
```bash
|
|
433
|
+
brew install --cask 1password
|
|
434
|
+
brew install 1password-cli
|
|
435
|
+
# Enable "Integrate with 1Password CLI" in 1Password settings
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Target not receiving expected variables
|
|
439
|
+
Check your `include`/`exclude` patterns in `hush.yaml`. Run `hush status` to see target configuration.
|
|
440
|
+
|
|
441
|
+
### AI assistant reading .env files directly
|
|
442
|
+
Install the Claude Code skill: `npx hush skill --global`
|
|
443
|
+
|
|
444
|
+
## License
|
|
445
|
+
|
|
446
|
+
MIT
|