opencode-varlock 0.0.8 → 0.0.9

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 CHANGED
@@ -1,86 +1,39 @@
1
1
  # opencode-varlock
2
2
 
3
- OpenCode plugin that gives agents access to environment variables **without revealing secret values**.
3
+ OpenCode plugin that gives agents access to environment variables without revealing secret values.
4
4
 
5
- ## The Problem
5
+ > Warning
6
+ > This plugin is still early in development, and there is active work underway to improve its security model and edge-case protections. PRs, issue reports, and security feedback are very welcome.
6
7
 
7
- When an AI agent needs secrets (database URLs, API keys, tokens) to run your code, the obvious approach — letting it read `.env` — puts every secret directly into its context window. It can then echo them, log them, or hallucinate them into committed code.
8
+ ## What it does
8
9
 
9
- ## How It Works
10
-
11
- Three layers enforce the boundary:
12
-
13
- ```
14
- ┌──────────────────────────────────────────────────┐
15
- │ Agent context window │
16
- │ │
17
- │ "Loaded 3 vars: DB_URL, API_KEY, REDIS_HOST" │
18
- │ ↑ names only, never values │
19
- │ │
20
- ├──────────────────────────────────────────────────┤
21
- │ Layer 1 — Custom tools (load_env / load_secrets)│
22
- │ Reads files or calls Varlock CLI, injects into │
23
- │ process.env, returns only key names. │
24
- ├──────────────────────────────────────────────────┤
25
- │ Layer 2 — Permission rules │
26
- │ Glob-based deny rules block `cat .env`, │
27
- │ `printenv`, `echo $SECRET`, etc. │
28
- ├──────────────────────────────────────────────────┤
29
- │ Layer 3 — EnvGuard hook │
30
- │ tool.execute.before intercept catches anything │
31
- │ the glob rules miss (python -c, scripting │
32
- │ escapes, indirect reads). │
33
- └──────────────────────────────────────────────────┘
34
- ```
10
+ - provides `load_env` so agents can use `.env` values without seeing them directly
11
+ - provides `load_secrets` and `secret_status` when the Varlock CLI is available
12
+ - blocks direct secret reads with permission presets plus a `tool.execute.before` guard
13
+ - tries to catch common workarounds like interpreter-based env reads
35
14
 
36
15
  ## Install
37
16
 
38
- ### As an npm plugin
39
-
40
- ```bash
41
- npm install opencode-varlock
42
- ```
17
+ Add the package to your `opencode.json` file:
43
18
 
44
19
  ```json
45
- // opencode.json
46
20
  {
47
- "plugin": ["opencode-varlock"]
48
- }
49
- ```
50
-
51
- The published package ships compiled ESM in `dist/`, and the root entry exports only the plugin itself so OpenCode can load it cleanly through the normal npm plugin resolution flow.
52
-
53
- ### As a local plugin
54
-
55
- Reference the package directory locally after building it:
56
-
57
- ```json
58
- {
59
- "plugin": ["./path/to/opencode-varlock"]
21
+ "$schema": "https://opencode.ai/config.json",
22
+ "plugin": ["opencode-varlock@latest"]
60
23
  }
61
24
  ```
62
25
 
63
26
  ## Configuration
64
27
 
65
- All configuration lives in a single `varlock.config.json` file. The plugin searches for it in two locations (merged in order):
66
-
67
- 1. `./varlock.config.json` (project root)
68
- 2. `.opencode/varlock.config.json`
28
+ `varlock.config.json` is optional.
69
29
 
70
- Programmatic overrides passed to `createVarlockPlugin()` take highest priority.
30
+ If you do not provide one, the plugin uses its built-in defaults from [assets/varlock.config.json](assets/varlock.config.json). Create a local config only when you want to override those defaults.
71
31
 
72
- ### Quick start
73
-
74
- Copy the default config into your project:
75
-
76
- ```bash
77
- cp node_modules/opencode-varlock/assets/varlock.config.json ./varlock.config.json
78
- ```
79
-
80
- Or create a minimal one — only the fields you want to change:
32
+ Quick example:
81
33
 
82
34
  ```json
83
35
  {
36
+ "$schema": "https://raw.githubusercontent.com/itlackey/opencode-varlock/main/assets/varlock.schema.json",
84
37
  "varlock": {
85
38
  "enabled": true,
86
39
  "namespace": "myapp"
@@ -88,276 +41,26 @@ Or create a minimal one — only the fields you want to change:
88
41
  }
89
42
  ```
90
43
 
91
- Everything else inherits from the built-in defaults.
44
+ Useful files:
45
+ - default config: `assets/varlock.config.json`
46
+ - JSON schema: `assets/varlock.schema.json`
47
+ - recommended permission configurations: `assets/permissions.json`
92
48
 
93
- The bundled template and permission presets now live in `assets/`:
49
+ ## Docs
94
50
 
95
- ```text
96
- assets/varlock.config.json
97
- assets/varlock.schema.json
98
- assets/permissions.json
99
- ```
100
-
101
- The copied config template points its `$schema` at `./node_modules/opencode-varlock/assets/varlock.schema.json`, so editors can validate and autocomplete it after install.
51
+ - setup and overrides: `docs/configuration.md`
52
+ - security model and limitations: `docs/security.md`
53
+ - tests and validation: `docs/testing.md`
54
+ - exported APIs and tools: `docs/api.md`
55
+ - Docker + pass guide: `docs/docker-pass-guide.md`
102
56
 
103
57
  ## Repo layout
104
58
 
105
59
  ```text
106
- src/ TypeScript source for the plugin entry, config, guard, and tools
107
- assets/ JSON assets shipped with the npm package
108
- docs/ Setup and integration guides
109
- ```
110
-
111
- ## Testing
112
-
113
- ```bash
114
- npm run test:unit
115
- npm run test:integration
116
- npm run test:coverage
117
- ```
118
-
119
- - `test:unit` covers config, guard, tools, and plugin registration
120
- - `test:integration` starts a real OpenCode server through `@opencode-ai/sdk` and verifies the plugin inside real sessions
121
- - `test:coverage` emits text, HTML, and LCOV coverage reports under `coverage/`
122
-
123
- ### Full config reference
124
-
125
- ```json
126
- {
127
- "guard": {
128
- "enabled": true,
129
-
130
- "sensitivePatterns": [
131
- ".env", ".secret", ".pem", ".key", "credentials", ".pgpass"
132
- ],
133
-
134
- "sensitiveGlobs": [
135
- "**/.env",
136
- "**/.env.*",
137
- "**/.env.local",
138
- "**/.env.production",
139
- "**/*.pem",
140
- "**/*.key",
141
- "**/credentials",
142
- "**/credentials.*",
143
- "**/.pgpass",
144
- "secrets/**"
145
- ],
146
-
147
- "bashDenyPatterns": [],
148
-
149
- "blockedReadTools": ["read", "grep", "glob", "view"],
150
- "blockedWriteTools": ["write", "edit"]
151
- },
152
-
153
- "env": {
154
- "enabled": true,
155
- "allowedRoot": "."
156
- },
157
-
158
- "varlock": {
159
- "enabled": false,
160
- "autoDetect": true,
161
- "command": "varlock",
162
- "namespace": "app"
163
- }
164
- }
165
- ```
166
-
167
- ### Config sections
168
-
169
- #### `guard` — EnvGuard hook
170
-
171
- | Field | Type | Default | Description |
172
- |---|---|---|---|
173
- | `enabled` | `boolean` | `true` | Master switch for the `tool.execute.before` hook |
174
- | `sensitivePatterns` | `string[]` | see above | Substring patterns — a path containing any of these is blocked |
175
- | `sensitiveGlobs` | `string[]` | see above | Glob patterns — matched against full paths using `*`, `**`, `?` |
176
- | `bashDenyPatterns` | `string[]` | `[]` | Extra bash substrings to deny (merged with ~30 built-ins) |
177
- | `blockedReadTools` | `string[]` | `["read","grep","glob","view"]` | Tool names that trigger the file-read check |
178
- | `blockedWriteTools` | `string[]` | `["write","edit"]` | Tool names that trigger the file-write check |
179
-
180
- #### `env` — .env file loader
181
-
182
- | Field | Type | Default | Description |
183
- |---|---|---|---|
184
- | `enabled` | `boolean` | `true` | Register the `load_env` tool |
185
- | `allowedRoot` | `string` | `"."` | Path containment boundary (resolved relative to cwd) |
186
-
187
- #### `varlock` — Varlock integration
188
-
189
- | Field | Type | Default | Description |
190
- |---|---|---|---|
191
- | `enabled` | `boolean` | `false` | Explicitly enable Varlock tools |
192
- | `autoDetect` | `boolean` | `true` | Probe for the CLI at startup and enable if found |
193
- | `command` | `string` | `"varlock"` | Path or name of the Varlock binary |
194
- | `namespace` | `string` | `"app"` | Default namespace for `load_secrets` / `secret_status` |
195
-
196
- **Varlock resolution logic:**
197
-
198
- - `enabled: true` → tools are registered (fails at runtime if CLI is missing)
199
- - `enabled: false, autoDetect: true` → probes `which <command>`, enables if found
200
- - `enabled: false, autoDetect: false` → Varlock is fully disabled
201
-
202
- ### Config merge behavior
203
-
204
- Arrays are **replaced**, not concatenated. This means you can fully override the default glob list in your config file without inheriting the defaults:
205
-
206
- ```json
207
- {
208
- "guard": {
209
- "sensitiveGlobs": ["secrets/**", "config/.env.*"]
210
- }
211
- }
212
- ```
213
-
214
- Object sections are deep-merged. Scalar values overwrite.
215
-
216
- ### Programmatic overrides
217
-
218
- For project-specific customization beyond what JSON can express:
219
-
220
- ```typescript
221
- // .opencode/plugin/secrets.ts
222
- import { createVarlockPlugin } from "opencode-varlock/plugin"
223
-
224
- export default createVarlockPlugin({
225
- guard: {
226
- sensitiveGlobs: [
227
- "**/.env",
228
- "**/.env.*",
229
- "infra/secrets/**",
230
- "deploy/*.key",
231
- ],
232
- bashDenyPatterns: ["vault read", "aws secretsmanager"],
233
- },
234
- varlock: {
235
- enabled: true,
236
- command: "/usr/local/bin/varlock",
237
- namespace: "prod",
238
- },
239
- })
240
- ```
241
-
242
- ## Glob patterns
243
-
244
- The guard supports glob patterns alongside the existing substring patterns. Both are checked — a match on either blocks the access.
245
-
246
- ### Supported syntax
247
-
248
- | Pattern | Matches | Example |
249
- |---|---|---|
250
- | `*` | Any characters except `/` | `*.pem` matches `server.pem` |
251
- | `**` | Any characters including `/` | `secrets/**` matches `secrets/prod/db.key` |
252
- | `**/` | Zero or more directory levels | `**/.env` matches `.env` and `config/.env` |
253
- | `?` | Single character except `/` | `?.key` matches `a.key` |
254
-
255
- ### When to use which
256
-
257
- **Substring patterns** are fast and filename-oriented. Use them for extensions and file names that should be blocked everywhere regardless of path:
258
-
259
- ```json
260
- "sensitivePatterns": [".env", ".pem", "credentials"]
261
- ```
262
-
263
- **Glob patterns** are structural and path-aware. Use them for directory-scoped rules and more precise matching:
264
-
265
- ```json
266
- "sensitiveGlobs": [
267
- "secrets/**",
268
- "config/.env.*",
269
- "deploy/**/*.key",
270
- "**/node_modules/**/.env"
271
- ]
272
- ```
273
-
274
- ### How globs are checked
275
-
276
- For file tool calls (`read`, `write`, `edit`, etc.), the glob is matched against the path argument directly.
277
-
278
- For bash commands, the guard extracts file-path-like tokens from the command string and checks each one against the compiled globs. This catches things like `jq . secrets/config.json` even when the substring patterns wouldn't flag it.
279
-
280
- ## Tools
281
-
282
- ### `load_env`
283
-
284
- Parses a `.env` file and sets `process.env`. Returns only variable **names**.
285
-
286
- ```
287
- Agent → load_env(path: ".env")
288
- ← { loaded: ["DATABASE_URL", "REDIS_HOST"], skipped: ["NODE_ENV"] }
289
- ```
290
-
291
- ### `load_secrets` (Varlock)
292
-
293
- Pulls secrets from Varlock and injects into `process.env`.
294
-
295
- ```
296
- Agent → load_secrets(namespace: "prod", keys: ["db_url", "api_key"])
297
- ← { loaded: ["DB_URL", "API_KEY"], source: "varlock/prod" }
298
- ```
299
-
300
- ### `secret_status` (Varlock)
301
-
302
- Read-only check of which secrets exist and which are loaded.
303
-
304
- ```
305
- Agent → secret_status(namespace: "app")
306
- ← { total: 5, loaded: 3, unloaded: 2, keys: [...] }
307
- ```
308
-
309
- ## Permission sets
310
-
311
- The `assets/permissions.json` file contains three tiers (standard, strict, lockdown) plus an example agent definition. Copy the tier that fits your threat model into `opencode.json`.
312
-
313
- These permission rules complement the EnvGuard hook — the rules handle fast-path blocking while the hook catches edge cases the glob-based rules miss.
314
-
315
- ## Architecture
316
-
317
- ### Why three layers?
318
-
319
- **Permissions alone aren't enough.** An agent can try `python3 -c "print(open('.env').read())"` or `python -c "import os; print(os.getenv('API_KEY'))"` - the obvious glob rules won't catch every runtime exfiltration path.
320
-
321
- **Prompt instructions alone aren't enough.** Telling an agent "never read .env" is a soft boundary the model can reason past.
322
-
323
- **The plugin hook is the hard boundary.** `tool.execute.before` runs before every built-in tool call, inspects actual arguments, and throws an error the agent cannot suppress. The error message redirects it to the approved tools.
324
-
325
- ### What the agent sees
326
-
327
- ```
328
- ✓ "Loaded 5 variables: DATABASE_URL, API_KEY, REDIS_HOST, JWT_SECRET, SMTP_PASS"
329
- ✓ Writes code: const db = new Client(process.env.DATABASE_URL)
330
- ✗ cat .env → Blocked: deny pattern
331
- ✗ echo $API_KEY → Blocked: deny pattern
332
- ✗ python -c "os.getenv" → Blocked: runtime env read
333
- ✗ python -c "open..." → Blocked: sensitive file
334
- ✗ jq . secrets/app.json → Blocked: matches glob "secrets/**"
335
- ```
336
-
337
- ## Advanced: composing individual pieces
338
-
339
- Every component is exported for use in custom plugins:
340
-
341
- ```typescript
342
- import { loadConfig, DEFAULT_CONFIG } from "opencode-varlock/config"
343
- import { createVarlockPlugin } from "opencode-varlock/plugin"
344
- import { createEnvGuard, globToRegex } from "opencode-varlock/guard"
345
- import { createLoadEnvTool } from "opencode-varlock/tools"
346
-
347
- // Load config with custom overrides
348
- const config = loadConfig(process.cwd(), {
349
- guard: { sensitiveGlobs: ["my-secrets/**"] },
350
- })
351
-
352
- // Use just the guard
353
- const guard = createEnvGuard(config.guard)
354
-
355
- // Use just the tool
356
- const loadEnv = createLoadEnvTool(config.env)
357
-
358
- // Test a glob pattern
359
- const regex = globToRegex("**/.env.*")
360
- regex.test("config/.env.production") // true
60
+ src/ TypeScript source
61
+ assets/ Packaged JSON assets
62
+ docs/ Guides and reference docs
63
+ tests/ Unit and integration tests
361
64
  ```
362
65
 
363
66
  ## License
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "./node_modules/opencode-varlock/assets/varlock.schema.json",
2
+ "$schema": "https://raw.githubusercontent.com/itlackey/opencode-varlock/main/assets/varlock.schema.json",
3
3
  "$comment": "opencode-varlock configuration. Place in project root or .opencode/",
4
4
 
5
5
  "guard": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-varlock",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for secret management via Varlock with configurable env guard protection",
6
6
  "main": "./dist/index.js",