secretless-ai 0.7.1 → 0.8.0
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 +156 -161
- package/dist/backends/config.d.ts +19 -0
- package/dist/backends/config.d.ts.map +1 -0
- package/dist/backends/config.js +99 -0
- package/dist/backends/config.js.map +1 -0
- package/dist/backends/factory.d.ts +34 -0
- package/dist/backends/factory.d.ts.map +1 -0
- package/dist/backends/factory.js +112 -0
- package/dist/backends/factory.js.map +1 -0
- package/dist/backends/index.d.ts +8 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +19 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/keychain-linux.d.ts +27 -0
- package/dist/backends/keychain-linux.d.ts.map +1 -0
- package/dist/backends/keychain-linux.js +155 -0
- package/dist/backends/keychain-linux.js.map +1 -0
- package/dist/backends/keychain-macos.d.ts +25 -0
- package/dist/backends/keychain-macos.d.ts.map +1 -0
- package/dist/backends/keychain-macos.js +181 -0
- package/dist/backends/keychain-macos.js.map +1 -0
- package/dist/backends/local.d.ts +2 -2
- package/dist/backends/local.d.ts.map +1 -1
- package/dist/backends/migrate.d.ts +30 -0
- package/dist/backends/migrate.d.ts.map +1 -0
- package/dist/backends/migrate.js +40 -0
- package/dist/backends/migrate.js.map +1 -0
- package/dist/backends/onepassword.d.ts +62 -0
- package/dist/backends/onepassword.d.ts.map +1 -0
- package/dist/backends/onepassword.js +254 -0
- package/dist/backends/onepassword.js.map +1 -0
- package/dist/backends/types.d.ts +12 -2
- package/dist/backends/types.d.ts.map +1 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +590 -4
- package/dist/cli.js.map +1 -1
- package/dist/env-import.d.ts +37 -0
- package/dist/env-import.d.ts.map +1 -0
- package/dist/env-import.js +140 -0
- package/dist/env-import.js.map +1 -0
- package/dist/git-hook.d.ts +28 -0
- package/dist/git-hook.d.ts.map +1 -0
- package/dist/git-hook.js +115 -0
- package/dist/git-hook.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +40 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +125 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp/protect.d.ts +3 -0
- package/dist/mcp/protect.d.ts.map +1 -1
- package/dist/mcp/protect.js +2 -1
- package/dist/mcp/protect.js.map +1 -1
- package/dist/mcp/rewrite.d.ts +2 -1
- package/dist/mcp/rewrite.d.ts.map +1 -1
- package/dist/mcp/rewrite.js +4 -2
- package/dist/mcp/rewrite.js.map +1 -1
- package/dist/mcp/vault.d.ts +17 -2
- package/dist/mcp/vault.d.ts.map +1 -1
- package/dist/mcp/vault.js +23 -4
- package/dist/mcp/vault.js.map +1 -1
- package/dist/mcp-wrapper.js +28 -6
- package/dist/mcp-wrapper.js.map +1 -1
- package/dist/run.d.ts +18 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +54 -0
- package/dist/run.js.map +1 -0
- package/dist/scan-staged.d.ts +20 -0
- package/dist/scan-staged.d.ts.map +1 -0
- package/dist/scan-staged.js +99 -0
- package/dist/scan-staged.js.map +1 -0
- package/dist/secret-store.d.ts +30 -0
- package/dist/secret-store.d.ts.map +1 -0
- package/dist/secret-store.js +78 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/setup.d.ts +33 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +128 -0
- package/dist/setup.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,110 @@ Part of the [OpenA2A](https://opena2a.org) ecosystem — open-source security fo
|
|
|
13
13
|
npx secretless-ai init
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## Secret Storage Backends
|
|
17
|
+
|
|
18
|
+
Secretless stores secrets in your choice of backend. Secrets are never in environment variables, shell profiles, or config files — they exist only in the backend and get injected into process memory at runtime.
|
|
19
|
+
|
|
20
|
+
| Backend | Storage | Sync | Auth | Best For |
|
|
21
|
+
|---------|---------|------|------|----------|
|
|
22
|
+
| `local` | AES-256-GCM encrypted file | None (single machine) | Filesystem | Quick start, simple setups |
|
|
23
|
+
| `keychain` | macOS Keychain / Linux Secret Service | Device-local | OS login | Native OS integration |
|
|
24
|
+
| `1password` | 1Password vault | Cross-device | Biometric (Touch ID) / Service Account | Teams, CI/CD, multi-device |
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx secretless-ai backend # Show available backends
|
|
28
|
+
npx secretless-ai backend set 1password # Switch to 1Password
|
|
29
|
+
npx secretless-ai backend set keychain # Switch to OS keychain
|
|
30
|
+
npx secretless-ai migrate --from local --to 1password # Migrate existing secrets
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 1Password Backend
|
|
34
|
+
|
|
35
|
+
Stores secrets in a dedicated "Secretless" vault using the [`op` CLI](https://developer.1password.com/docs/cli). Secrets never touch disk.
|
|
36
|
+
|
|
37
|
+
**Setup:**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
brew install --cask 1password # Install 1Password desktop app
|
|
41
|
+
brew install --cask 1password-cli # Install op CLI
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then enable CLI integration: **1Password > Settings > Developer > "Integrate with 1Password CLI"**. This allows the CLI to authenticate through the desktop app with biometric unlock (Touch ID / Windows Hello).
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx secretless-ai backend set 1password # Switch backend
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**CI/CD:** Set `OP_SERVICE_ACCOUNT_TOKEN` — same secrets, no code changes. No desktop app needed.
|
|
51
|
+
|
|
52
|
+
## Secret Management
|
|
53
|
+
|
|
54
|
+
Store, list, and inject secrets without exposing them to AI tools.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx secretless-ai secret set STRIPE_KEY=sk_live_... # Store a secret
|
|
58
|
+
npx secretless-ai secret set DATABASE_URL # Read value from stdin
|
|
59
|
+
npx secretless-ai secret list # List secret names (never values)
|
|
60
|
+
npx secretless-ai secret rm STRIPE_KEY # Remove a secret
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Running Commands with Secrets
|
|
64
|
+
|
|
65
|
+
Inject secrets as environment variables into any command. The AI tool sees the command output but never the secret values.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx secretless-ai run -- npm test # Inject all secrets
|
|
69
|
+
npx secretless-ai run --only STRIPE_KEY -- curl -u "$STRIPE_KEY:" https://api.stripe.com/v1/balance
|
|
70
|
+
npx secretless-ai run --only DATABASE_URL -- npm run migrate # Inject specific key
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### AI-Safe by Design
|
|
74
|
+
|
|
75
|
+
When an AI tool tries to read a secret value, secretless blocks it:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
$ npx secretless-ai secret get STRIPE_KEY # (run by AI tool)
|
|
79
|
+
|
|
80
|
+
secretless: Blocked -- secret values cannot be read in non-interactive contexts.
|
|
81
|
+
AI tools capture stdout, which would expose the secret in their context.
|
|
82
|
+
|
|
83
|
+
To inject secrets into a command:
|
|
84
|
+
npx secretless-ai run -- <command>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Direct terminal access (human) works normally. The guard detects non-interactive execution (how AI tools run commands) and refuses to output.
|
|
88
|
+
|
|
89
|
+
### Import from .env Files
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx secretless-ai import .env # Import from specific file
|
|
93
|
+
npx secretless-ai import --detect # Auto-find and import all .env files
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Project Manifests
|
|
97
|
+
|
|
98
|
+
Define required secrets in a `.secretless` file at the project root:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
STRIPE_KEY required Stripe API key for payments
|
|
102
|
+
DATABASE_URL required PostgreSQL connection string
|
|
103
|
+
SENTRY_DSN optional Error tracking
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx secretless-ai setup # Interactive setup for missing secrets
|
|
108
|
+
npx secretless-ai setup --check # CI: fail if required secrets are missing
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Backend Inspection
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx secretless-ai backend list # Show all entries grouped by prefix
|
|
115
|
+
npx secretless-ai backend purge # Dry-run: show what would be deleted
|
|
116
|
+
npx secretless-ai backend purge --yes # Delete all entries
|
|
117
|
+
npx secretless-ai backend purge --prefix mcp --yes # Delete only mcp/ entries
|
|
118
|
+
```
|
|
119
|
+
|
|
16
120
|
## MCP Secret Protection
|
|
17
121
|
|
|
18
122
|
Every MCP server config has plaintext API keys sitting in JSON files on your laptop. The LLM sees them. Secretless encrypts them.
|
|
@@ -42,48 +146,14 @@ npx secretless-ai protect-mcp
|
|
|
42
146
|
|
|
43
147
|
1. Scans MCP configs across Claude Desktop, Cursor, Claude Code, VS Code, and Windsurf
|
|
44
148
|
2. Identifies which env vars are secrets (key name patterns + value regex matching)
|
|
45
|
-
3.
|
|
149
|
+
3. Stores secrets in your configured backend (local, keychain, or 1Password)
|
|
46
150
|
4. Rewrites configs to use the `secretless-mcp` wrapper — decrypts at runtime, injects as env vars
|
|
47
151
|
5. Non-secret env vars (URLs, org names, regions) stay in the config untouched
|
|
48
152
|
|
|
49
|
-
**Before:**
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"mcpServers": {
|
|
53
|
-
"github": {
|
|
54
|
-
"command": "npx",
|
|
55
|
-
"args": ["@github/mcp-server"],
|
|
56
|
-
"env": {
|
|
57
|
-
"GITHUB_TOKEN": "ghp_plaintext_visible_to_LLM",
|
|
58
|
-
"GITHUB_ORG": "my-org"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**After:**
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"mcpServers": {
|
|
69
|
-
"github": {
|
|
70
|
-
"command": "secretless-mcp",
|
|
71
|
-
"args": ["--server", "github", "--client", "claude-desktop", "--", "npx", "@github/mcp-server"],
|
|
72
|
-
"env": {
|
|
73
|
-
"GITHUB_ORG": "my-org"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The secret moves to the encrypted vault. The wrapper decrypts it at startup (<10ms overhead) and passes it to the MCP server as an env var. The LLM never sees it.
|
|
81
|
-
|
|
82
|
-
**Other MCP commands:**
|
|
83
|
-
|
|
84
153
|
```bash
|
|
85
|
-
npx secretless-ai mcp
|
|
86
|
-
npx secretless-ai mcp-
|
|
154
|
+
npx secretless-ai protect-mcp --backend 1password # Store MCP secrets in 1Password
|
|
155
|
+
npx secretless-ai mcp-status # Show which servers are protected/exposed
|
|
156
|
+
npx secretless-ai mcp-unprotect # Restore original configs from backup
|
|
87
157
|
```
|
|
88
158
|
|
|
89
159
|
---
|
|
@@ -117,7 +187,7 @@ npx secretless-ai init
|
|
|
117
187
|
Output:
|
|
118
188
|
|
|
119
189
|
```
|
|
120
|
-
Secretless v0.
|
|
190
|
+
Secretless v0.7.1
|
|
121
191
|
Keeping secrets out of AI
|
|
122
192
|
|
|
123
193
|
Detected:
|
|
@@ -153,40 +223,13 @@ Non-interactive subprocesses (Claude Code's Bash tool, CI/CD, Docker) don't sour
|
|
|
153
223
|
| Linux | bash | `~/.bashrc` | Sourced by interactive bash; most tools source it explicitly |
|
|
154
224
|
| Windows | — | System Environment Variables | Use `setx` or Settings > System > Environment Variables |
|
|
155
225
|
|
|
156
|
-
**
|
|
157
|
-
- **macOS:** Adding `export` lines to `~/.zshrc` instead of `~/.zshenv`. Secretless copies them to the correct file during `init`.
|
|
158
|
-
- **Linux:** Adding exports to `~/.bash_profile` instead of `~/.bashrc`, or placing them after the interactive guard in `.bashrc`. Secretless inserts them before the guard.
|
|
159
|
-
- **Windows:** Setting keys only in PowerShell `$PROFILE` (session-only). Secretless runs `setx` to set persistent user environment variables.
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
# macOS (zsh) — add to ~/.zshenv
|
|
163
|
-
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
164
|
-
export OPENAI_API_KEY="sk-proj-..."
|
|
165
|
-
|
|
166
|
-
# Linux (bash) — add to ~/.bashrc (before the interactive guard)
|
|
167
|
-
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
168
|
-
export OPENAI_API_KEY="sk-proj-..."
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
```powershell
|
|
172
|
-
# Windows — use setx (or Settings > System > Environment Variables)
|
|
173
|
-
setx ANTHROPIC_API_KEY "sk-ant-..."
|
|
174
|
-
setx OPENAI_API_KEY "sk-proj-..."
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Step 2: Remove keys from AI config files**
|
|
178
|
-
|
|
179
|
-
Delete any hardcoded keys from `CLAUDE.md`, `.cursorrules`, `.env`, etc.
|
|
180
|
-
|
|
181
|
-
**Step 3: Run secretless init**
|
|
226
|
+
**Step 2: Run secretless init**
|
|
182
227
|
|
|
183
228
|
```bash
|
|
184
229
|
npx secretless-ai init
|
|
185
230
|
```
|
|
186
231
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
**Step 4: Verify**
|
|
232
|
+
**Step 3: Verify**
|
|
190
233
|
|
|
191
234
|
```bash
|
|
192
235
|
npx secretless-ai verify
|
|
@@ -202,94 +245,52 @@ npx secretless-ai verify
|
|
|
202
245
|
PASS: Secrets are accessible via env vars but hidden from AI context.
|
|
203
246
|
```
|
|
204
247
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
**After:** Claude sees a table saying `$ANTHROPIC_API_KEY` exists and the auth header is `x-api-key: $ANTHROPIC_API_KEY`. It uses `$ANTHROPIC_API_KEY` in shell commands. The shell resolves it. Claude never sees the actual value.
|
|
248
|
+
## Git Protection
|
|
208
249
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
### `npx secretless-ai init`
|
|
212
|
-
|
|
213
|
-
Detects AI tools in your project and installs protections. If API keys are set as env vars, includes a reference table with service names and auth header formats so the AI can use them without seeing values. Safe to run multiple times.
|
|
214
|
-
|
|
215
|
-
### `npx secretless-ai scan`
|
|
216
|
-
|
|
217
|
-
Scans config files for hardcoded credentials — both project-level and global (`~/.claude/CLAUDE.md`). Detects 49 credential patterns including Anthropic, OpenAI, AWS, GitHub, Slack, Google, Stripe, SendGrid, Supabase, Azure, GitLab, Twilio, Mailgun, and more.
|
|
218
|
-
|
|
219
|
-
```
|
|
220
|
-
Found 2 credential(s):
|
|
221
|
-
|
|
222
|
-
[CRIT] Anthropic API Key
|
|
223
|
-
~/.claude/CLAUDE.md:286
|
|
224
|
-
ANTHROPIC_API_KEY=[Anthropic API Key REDACTED]
|
|
225
|
-
|
|
226
|
-
[CRIT] OpenAI Project Key
|
|
227
|
-
~/.claude/CLAUDE.md:284
|
|
228
|
-
OPENAI_API_KEY=[OpenAI Project Key REDACTED]
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### `npx secretless-ai verify`
|
|
232
|
-
|
|
233
|
-
Confirms keys are usable but hidden from AI. Checks that env vars are set AND that the actual key values don't appear in any AI context file.
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
PASS: Secrets are accessible via env vars but hidden from AI context.
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### `npx secretless-ai doctor`
|
|
240
|
-
|
|
241
|
-
Diagnoses shell profile issues that cause "No API keys found" errors. Detects when keys are in an interactive-only profile (like `~/.zshrc`) that non-interactive subprocesses can't see.
|
|
242
|
-
|
|
243
|
-
Use `--fix` to auto-fix: copies export lines from the wrong profile to the correct one (non-destructive, does not modify the original file).
|
|
250
|
+
Prevent secrets from being committed:
|
|
244
251
|
|
|
245
252
|
```bash
|
|
246
|
-
npx secretless-ai
|
|
247
|
-
npx secretless-ai
|
|
253
|
+
npx secretless-ai hook install # Install pre-commit secret scanner
|
|
254
|
+
npx secretless-ai hook status # Check hook installation status
|
|
255
|
+
npx secretless-ai hook uninstall # Remove pre-commit hook
|
|
248
256
|
```
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```
|
|
287
|
-
Protected: Yes
|
|
288
|
-
Tools: Claude Code, Cursor
|
|
289
|
-
Hook: Installed
|
|
290
|
-
Deny rules: 14
|
|
291
|
-
Secrets: 0 found in config files
|
|
292
|
-
```
|
|
258
|
+
## All Commands
|
|
259
|
+
|
|
260
|
+
| Command | Description |
|
|
261
|
+
|---------|-------------|
|
|
262
|
+
| `init` | Set up protections for your AI tools |
|
|
263
|
+
| `scan` | Scan for hardcoded secrets (49 patterns) |
|
|
264
|
+
| `status` | Show protection status |
|
|
265
|
+
| `verify` | Verify keys are usable but hidden from AI |
|
|
266
|
+
| `doctor [--fix]` | Diagnose and auto-fix shell profile issues |
|
|
267
|
+
| `clean [--dry-run]` | Scan and redact credentials in transcripts |
|
|
268
|
+
| `watch` | Monitor transcripts in real-time |
|
|
269
|
+
| **Secret Management** | |
|
|
270
|
+
| `secret set <NAME[=VALUE]>` | Store a secret |
|
|
271
|
+
| `secret list` | List stored secret names |
|
|
272
|
+
| `secret get <NAME>` | Retrieve a secret value (blocked in non-interactive contexts) |
|
|
273
|
+
| `secret rm <NAME>` | Remove a secret |
|
|
274
|
+
| `run [--only K1,K2] -- <cmd>` | Run command with secrets injected as env vars |
|
|
275
|
+
| `import <file>` | Import secrets from .env file |
|
|
276
|
+
| `import --detect` | Auto-find and import .env files |
|
|
277
|
+
| **Project Setup** | |
|
|
278
|
+
| `setup` | Interactive setup from `.secretless` manifest |
|
|
279
|
+
| `setup --check` | CI: fail if required secrets are missing |
|
|
280
|
+
| **Git Protection** | |
|
|
281
|
+
| `hook install` | Install pre-commit secret scanner |
|
|
282
|
+
| `hook uninstall` | Remove pre-commit hook |
|
|
283
|
+
| `hook status` | Check hook installation status |
|
|
284
|
+
| **MCP Protection** | |
|
|
285
|
+
| `protect-mcp [--backend TYPE]` | Encrypt MCP server secrets |
|
|
286
|
+
| `mcp-status` | Show MCP protection status |
|
|
287
|
+
| `mcp-unprotect` | Restore original MCP configs |
|
|
288
|
+
| **Backend Management** | |
|
|
289
|
+
| `backend` | Show current backend status |
|
|
290
|
+
| `backend set <TYPE>` | Set backend (local, keychain, 1password) |
|
|
291
|
+
| `backend list` | List all stored entries |
|
|
292
|
+
| `backend purge [--prefix] [--yes]` | Delete entries from backend |
|
|
293
|
+
| `migrate --from TYPE --to TYPE` | Migrate secrets between backends |
|
|
293
294
|
|
|
294
295
|
## What Gets Blocked
|
|
295
296
|
|
|
@@ -309,19 +310,11 @@ Commands that dump secret files (`cat .env`, `head *.key`) and commands that ech
|
|
|
309
310
|
|
|
310
311
|
For Claude Code, Secretless installs a PreToolUse hook that intercepts every `Read`, `Grep`, `Glob`, `Bash`, `Write`, and `Edit` tool call. The hook runs *before* the tool executes, so secrets never enter the AI context window.
|
|
311
312
|
|
|
312
|
-
```bash
|
|
313
|
-
# .claude/hooks/secretless-guard.sh
|
|
314
|
-
# Runs before every tool call, checks file paths against block list
|
|
315
|
-
# Returns deny decision if a secret file is targeted
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Additionally, Secretless adds `permissions.deny` rules to `.claude/settings.json` as a second layer of defense, and adds instructions to `CLAUDE.md` so Claude understands why certain files are blocked.
|
|
319
|
-
|
|
320
313
|
## Development
|
|
321
314
|
|
|
322
315
|
```bash
|
|
323
316
|
npm run build # Compile TypeScript to dist/
|
|
324
|
-
npm test # Run tests (vitest)
|
|
317
|
+
npm test # Run tests (vitest, 461 tests)
|
|
325
318
|
npm run dev # Watch mode — recompile on file changes
|
|
326
319
|
npm run clean # Remove dist/ directory
|
|
327
320
|
```
|
|
@@ -330,10 +323,12 @@ npm run clean # Remove dist/ directory
|
|
|
330
323
|
|
|
331
324
|
- Node.js 18+
|
|
332
325
|
- A project directory with at least one AI tool configured (or Secretless defaults to Claude Code)
|
|
326
|
+
- **Optional:** 1Password CLI (`op`) for 1Password backend
|
|
327
|
+
- **Optional:** macOS Keychain or `secret-tool` (Linux) for keychain backend
|
|
333
328
|
|
|
334
329
|
## Zero Dependencies
|
|
335
330
|
|
|
336
|
-
Secretless has zero runtime dependencies.
|
|
331
|
+
Secretless has zero runtime dependencies.
|
|
337
332
|
|
|
338
333
|
## OpenA2A Ecosystem
|
|
339
334
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend configuration manager.
|
|
3
|
+
*
|
|
4
|
+
* Reads and writes the user's backend preference from ~/.secretless-ai/config.json.
|
|
5
|
+
* Resolution priority: explicit CLI flag > config file > default ('local').
|
|
6
|
+
*/
|
|
7
|
+
/** Writable backend types that can be selected by the user. */
|
|
8
|
+
export type SelectableBackendType = 'local' | 'keychain' | '1password';
|
|
9
|
+
/** Read the current backend configuration. Returns undefined if no config file exists. */
|
|
10
|
+
export declare function readBackendConfig(): SelectableBackendType | undefined;
|
|
11
|
+
/** Write the backend preference to the config file. */
|
|
12
|
+
export declare function writeBackendConfig(backend: SelectableBackendType): void;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve which backend type to use.
|
|
15
|
+
*
|
|
16
|
+
* Priority: explicit flag > config file > default ('local').
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveBackendType(explicitFlag?: string): SelectableBackendType;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/backends/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC;AAkBvE,0FAA0F;AAC1F,wBAAgB,iBAAiB,IAAI,qBAAqB,GAAG,SAAS,CAWrE;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAiBvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,qBAAqB,CAK/E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Backend configuration manager.
|
|
4
|
+
*
|
|
5
|
+
* Reads and writes the user's backend preference from ~/.secretless-ai/config.json.
|
|
6
|
+
* Resolution priority: explicit CLI flag > config file > default ('local').
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.readBackendConfig = readBackendConfig;
|
|
43
|
+
exports.writeBackendConfig = writeBackendConfig;
|
|
44
|
+
exports.resolveBackendType = resolveBackendType;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const CONFIG_FILENAME = 'config.json';
|
|
48
|
+
const DEFAULT_BACKEND = 'local';
|
|
49
|
+
function configDir() {
|
|
50
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
51
|
+
return path.join(home, '.secretless-ai');
|
|
52
|
+
}
|
|
53
|
+
function configPath() {
|
|
54
|
+
return path.join(configDir(), CONFIG_FILENAME);
|
|
55
|
+
}
|
|
56
|
+
/** Read the current backend configuration. Returns undefined if no config file exists. */
|
|
57
|
+
function readBackendConfig() {
|
|
58
|
+
try {
|
|
59
|
+
const raw = fs.readFileSync(configPath(), 'utf-8');
|
|
60
|
+
const config = JSON.parse(raw);
|
|
61
|
+
if (config.backend === 'local' || config.backend === 'keychain' || config.backend === '1password') {
|
|
62
|
+
return config.backend;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Write the backend preference to the config file. */
|
|
71
|
+
function writeBackendConfig(backend) {
|
|
72
|
+
const dir = configDir();
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
74
|
+
const fp = configPath();
|
|
75
|
+
let config = {};
|
|
76
|
+
try {
|
|
77
|
+
const raw = fs.readFileSync(fp, 'utf-8');
|
|
78
|
+
config = JSON.parse(raw);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// No existing config or invalid JSON — start fresh
|
|
82
|
+
}
|
|
83
|
+
config.backend = backend;
|
|
84
|
+
const tmpPath = fp + '.tmp';
|
|
85
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
86
|
+
fs.renameSync(tmpPath, fp);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve which backend type to use.
|
|
90
|
+
*
|
|
91
|
+
* Priority: explicit flag > config file > default ('local').
|
|
92
|
+
*/
|
|
93
|
+
function resolveBackendType(explicitFlag) {
|
|
94
|
+
if (explicitFlag === 'local' || explicitFlag === 'keychain' || explicitFlag === '1password') {
|
|
95
|
+
return explicitFlag;
|
|
96
|
+
}
|
|
97
|
+
return readBackendConfig() ?? DEFAULT_BACKEND;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/backends/config.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,8CAWC;AAGD,gDAiBC;AAOD,gDAKC;AAnED,uCAAyB;AACzB,2CAA6B;AAM7B,MAAM,eAAe,GAAG,aAAa,CAAC;AACtC,MAAM,eAAe,GAA0B,OAAO,CAAC;AAMvD,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC;IACnE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,CAAC;AACjD,CAAC;AAED,0FAA0F;AAC1F,SAAgB,iBAAiB;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC;QACnD,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,IAAI,MAAM,CAAC,OAAO,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YAClG,OAAO,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,SAAgB,kBAAkB,CAAC,OAA8B;IAC/D,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpD,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,IAAI,MAAM,GAAqB,EAAE,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACzC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;IACrD,CAAC;IAED,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,MAAM,OAAO,GAAG,EAAE,GAAG,MAAM,CAAC;IAC5B,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,YAAqB;IACtD,IAAI,YAAY,KAAK,OAAO,IAAI,YAAY,KAAK,UAAU,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QAC5F,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,OAAO,iBAAiB,EAAE,IAAI,eAAe,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend factory — creates the appropriate WritableSecretBackend based on type.
|
|
3
|
+
*
|
|
4
|
+
* Dispatches 'keychain' to the platform-specific implementation (macOS or Linux).
|
|
5
|
+
* Dispatches '1password' to the OnePasswordBackend (requires `op` CLI).
|
|
6
|
+
* Falls back to 'local' on unsupported platforms with a console message.
|
|
7
|
+
*/
|
|
8
|
+
import type { WritableSecretBackend } from './types';
|
|
9
|
+
import type { SelectableBackendType } from './config';
|
|
10
|
+
/**
|
|
11
|
+
* Create a WritableSecretBackend instance for the given type.
|
|
12
|
+
*
|
|
13
|
+
* @param type - 'local', 'keychain', or '1password'
|
|
14
|
+
* @param config - Backend-specific configuration (e.g. storeDir, key, vault)
|
|
15
|
+
*/
|
|
16
|
+
export declare function createBackend(type: SelectableBackendType, config?: Record<string, unknown>): WritableSecretBackend;
|
|
17
|
+
/**
|
|
18
|
+
* Check if the OS keychain is available on the current platform.
|
|
19
|
+
* Returns a description of the keychain status.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isKeychainAvailable(): {
|
|
22
|
+
available: boolean;
|
|
23
|
+
platform: string;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Check if the 1Password CLI (`op`) is available and authenticated.
|
|
28
|
+
* Returns availability status and an actionable message.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isOnePasswordAvailable(): {
|
|
31
|
+
available: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,qBAAqB,EAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,qBAAqB,CAYvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAgC/F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwBhF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Backend factory — creates the appropriate WritableSecretBackend based on type.
|
|
4
|
+
*
|
|
5
|
+
* Dispatches 'keychain' to the platform-specific implementation (macOS or Linux).
|
|
6
|
+
* Dispatches '1password' to the OnePasswordBackend (requires `op` CLI).
|
|
7
|
+
* Falls back to 'local' on unsupported platforms with a console message.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createBackend = createBackend;
|
|
11
|
+
exports.isKeychainAvailable = isKeychainAvailable;
|
|
12
|
+
exports.isOnePasswordAvailable = isOnePasswordAvailable;
|
|
13
|
+
const local_1 = require("./local");
|
|
14
|
+
const keychain_macos_1 = require("./keychain-macos");
|
|
15
|
+
const keychain_linux_1 = require("./keychain-linux");
|
|
16
|
+
const onepassword_1 = require("./onepassword");
|
|
17
|
+
/**
|
|
18
|
+
* Create a WritableSecretBackend instance for the given type.
|
|
19
|
+
*
|
|
20
|
+
* @param type - 'local', 'keychain', or '1password'
|
|
21
|
+
* @param config - Backend-specific configuration (e.g. storeDir, key, vault)
|
|
22
|
+
*/
|
|
23
|
+
function createBackend(type, config) {
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'keychain':
|
|
26
|
+
return createKeychainBackend(config);
|
|
27
|
+
case '1password':
|
|
28
|
+
return new onepassword_1.OnePasswordBackend(config);
|
|
29
|
+
case 'local':
|
|
30
|
+
default:
|
|
31
|
+
return new local_1.LocalBackend(config);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the OS keychain is available on the current platform.
|
|
36
|
+
* Returns a description of the keychain status.
|
|
37
|
+
*/
|
|
38
|
+
function isKeychainAvailable() {
|
|
39
|
+
const platform = process.platform;
|
|
40
|
+
if (platform === 'darwin') {
|
|
41
|
+
try {
|
|
42
|
+
const { execFileSync } = require('child_process');
|
|
43
|
+
execFileSync('security', ['default-keychain'], { stdio: 'pipe' });
|
|
44
|
+
return { available: true, platform: 'macOS', message: 'macOS Keychain is available' };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { available: false, platform: 'macOS', message: 'macOS Keychain is not accessible' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (platform === 'linux') {
|
|
51
|
+
try {
|
|
52
|
+
const { execFileSync } = require('child_process');
|
|
53
|
+
execFileSync('which', ['secret-tool'], { stdio: 'pipe' });
|
|
54
|
+
return { available: true, platform: 'Linux', message: 'secret-tool is available (Linux Secret Service)' };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {
|
|
58
|
+
available: false,
|
|
59
|
+
platform: 'Linux',
|
|
60
|
+
message: 'secret-tool not found. Install libsecret-tools (Debian/Ubuntu) or libsecret (Fedora/RHEL).',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
available: false,
|
|
66
|
+
platform: platform,
|
|
67
|
+
message: `OS keychain is not supported on ${platform}. Using local encrypted backend.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the 1Password CLI (`op`) is available and authenticated.
|
|
72
|
+
* Returns availability status and an actionable message.
|
|
73
|
+
*/
|
|
74
|
+
function isOnePasswordAvailable() {
|
|
75
|
+
try {
|
|
76
|
+
const { execFileSync } = require('child_process');
|
|
77
|
+
execFileSync('op', ['--version'], { stdio: 'pipe' });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return {
|
|
81
|
+
available: false,
|
|
82
|
+
message: '1Password CLI (op) not found. Install from https://developer.1password.com/docs/cli',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const { execFileSync } = require('child_process');
|
|
87
|
+
execFileSync('op', ['account', 'get', '--format', 'json'], { stdio: 'pipe' });
|
|
88
|
+
return {
|
|
89
|
+
available: true,
|
|
90
|
+
message: '1Password CLI installed and authenticated',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return {
|
|
95
|
+
available: false,
|
|
96
|
+
message: '1Password CLI installed but not signed in. Run `op signin` or set OP_SERVICE_ACCOUNT_TOKEN.',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createKeychainBackend(config) {
|
|
101
|
+
const platform = process.platform;
|
|
102
|
+
if (platform === 'darwin') {
|
|
103
|
+
return new keychain_macos_1.MacOSKeychainBackend(config);
|
|
104
|
+
}
|
|
105
|
+
if (platform === 'linux') {
|
|
106
|
+
return new keychain_linux_1.LinuxKeychainBackend(config);
|
|
107
|
+
}
|
|
108
|
+
// Unsupported platform — fall back to local with a message
|
|
109
|
+
console.error(`secretless: OS keychain not supported on ${platform}. Falling back to local encrypted backend.`);
|
|
110
|
+
return new local_1.LocalBackend(config);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"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"}
|