Skip to content

Commit 9c673bb

Browse files
committed
fix(wizard): stop the agent guard from blocking .env.example
Completes the review fix on #594. The doctrine now says `.env.example` is exempt from the secrets read-ban; the wizard's runtime guard still blocked it. `SENSITIVE_FILE_PATTERNS` matched `/\.env($|\.)/`, which tests true against `.env.example`. Because `wizardCanUseTool` applies that guard to `Edit` and `Write` as well as `Read`, the agent could not create or edit the one file the doctrine explicitly instructs it to write. Committed env templates carry placeholder key names, not values. Narrow the rule with an anchored negative lookahead so `.env.example`, `.env.sample` and `.env.template` are reachable, while `.env`, `.env.local`, `.env.production` and near-miss names that do carry values (`.env.example.local`, `.env.example.bak`) stay blocked. `auth.json`, `secretkey.json` and credential files are untouched. Bash remains deliberately stricter: no env file, template or not, is reachable from a shell. `cat` isn't in the bash allowlist anyway, and Read/Write is the sanctioned path -- so there's no reason to widen the riskiest surface. Noted inline. Adds five cases covering the exemption, the near-miss names, and the Bash asymmetry. `pnpm --filter @cipherstash/wizard test` passes (150).
1 parent aad9164 commit 9c673bb

3 files changed

Lines changed: 68 additions & 2 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@cipherstash/wizard": patch
3+
---
4+
5+
Stop the agent guard from blocking `.env.example`.
6+
7+
`SENSITIVE_FILE_PATTERNS` matched `/\.env($|\.)/`, which tests true against
8+
`.env.example`. Because the guard covers `Edit` and `Write` as well as `Read`,
9+
the wizard's agent was blocked from creating or editing the very file the
10+
CipherStash doctrine tells it to write ("New env keys go in `.env.example` with
11+
placeholders"). Committed env templates carry placeholder key names, not values.
12+
13+
`.env.example`, `.env.sample` and `.env.template` are now readable and writable.
14+
Everything else is unchanged: `.env`, `.env.local`, `.env.production`, and
15+
value-bearing files that merely start with a template name
16+
(`.env.example.local`, `.env.example.bak`) stay blocked, as do `auth.json`,
17+
`secretkey.json` and credential files. Bash access to any env file — including
18+
the templates — remains blocked; `Read`/`Write` is the sanctioned path.

‎packages/wizard/src/__tests__/interface.test.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,42 @@ describe('wizardCanUseTool', () => {
7272
})
7373
})
7474

75+
describe('.env templates are not sensitive', () => {
76+
// The doctrine tells the agent to add placeholder keys to `.env.example`.
77+
// The guard covers Edit and Write as well as Read, so a blanket `.env.`
78+
// rule made the file the agent is told to write unreachable.
79+
it.each(['.env.example', '.env.sample', '.env.template'])(
80+
'allows Read/Edit/Write/Glob on %s',
81+
(name) => {
82+
expect(wizardCanUseTool('Read', { file_path: `/project/${name}` })).toBe(
83+
true,
84+
)
85+
expect(wizardCanUseTool('Edit', { file_path: `/project/${name}` })).toBe(
86+
true,
87+
)
88+
expect(
89+
wizardCanUseTool('Write', { file_path: `/project/${name}` }),
90+
).toBe(true)
91+
expect(wizardCanUseTool('Glob', { pattern: name })).toBe(true)
92+
},
93+
)
94+
95+
it('still blocks value-bearing files that only start with a template name', () => {
96+
expect(
97+
wizardCanUseTool('Read', { file_path: '/project/.env.example.local' }),
98+
).toContain('blocked')
99+
expect(
100+
wizardCanUseTool('Write', { file_path: '/project/.env.example.bak' }),
101+
).toContain('blocked')
102+
})
103+
104+
it('still blocks env templates from Bash — Read/Write is the sanctioned path', () => {
105+
expect(
106+
wizardCanUseTool('Bash', { command: 'cat .env.example' }),
107+
).toContain('.env')
108+
})
109+
})
110+
75111
describe('Bash commands', () => {
76112
it('allows allowlisted npm commands', () => {
77113
expect(

‎packages/wizard/src/agent/interface.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,17 @@ const ALLOWED_WRITE_PATHS = [
114114

115115
/** Sensitive file patterns the agent must not read directly. */
116116
const SENSITIVE_FILE_PATTERNS = [
117-
/\.env($|\.)/, // .env, .env.local, .env.production, etc.
117+
// `.env`, `.env.local`, `.env.production`, … but not the committed templates
118+
// `.env.example` / `.env.sample` / `.env.template`. Those carry placeholder
119+
// key names rather than values, and the agent doctrine
120+
// (`packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md`, invariant 3)
121+
// instructs the agent to create and edit them — a blanket `.env.` rule made
122+
// that impossible, since this guard also covers Edit and Write.
123+
//
124+
// The negative lookahead is anchored, so a value-bearing file that merely
125+
// starts with a template name (`.env.example.bak`, `.env.example.local`)
126+
// stays blocked.
127+
/\.env($|\.(?!(example|sample|template)$))/,
118128
/auth\.json$/, // ~/.cipherstash/auth.json
119129
/secretkey\.json$/, // ~/.cipherstash/secretkey.json
120130
/credentials/i, // Various credential files
@@ -178,7 +188,9 @@ export function wizardCanUseTool(
178188
return 'Multi-line commands are not allowed for security reasons.'
179189
}
180190

181-
// Block direct .env access via Bash
191+
// Block direct .env access via Bash. Deliberately stricter than the
192+
// file-path guard above: `.env.example` is reachable through Read/Edit/Write,
193+
// so there is no reason for the agent to touch any env file from a shell.
182194
if (/\.(env|env\.local)/.test(command)) {
183195
return 'Direct .env file access via Bash is blocked. Use the wizard-tools MCP server instead.'
184196
}

0 commit comments

Comments
 (0)