Skip to content

Commit a91c0db

Browse files
authored
Merge pull request #594 from cipherstash/docs/stash-cli-skill-refresh
docs(cli): refresh the bundled stash-cli skill; fix the post-cutover read path and the .env.example guard
2 parents f435ce5 + 9c673bb commit a91c0db

7 files changed

Lines changed: 423 additions & 467 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"stash": patch
3+
---
4+
5+
Refresh the bundled `stash-cli` agent skill and the CLI README against the current
6+
command surface. The skills directory ships inside the `stash` tarball and is copied
7+
into the user's `.claude/skills/` / `.codex/skills/` (or inlined into `AGENTS.md`) at
8+
handoff time, so a stale skill becomes stale guidance in the user's project.
9+
10+
- **New `Start here` and `Authentication` sections.** Setup is driven through the CLI:
11+
agents read `stash manifest --json` first, then trigger `stash auth login --json` and
12+
surface the verification URL for a human to approve, then run `stash init`. Authenticating
13+
before `init` matters — `init`'s auth step is interactive and would otherwise try to open
14+
a browser on the agent's host.
15+
- **New `Never read these` invariant**, mirrored into the `AGENTS.md` doctrine: agents must
16+
never read `~/.cipherstash/secretkey.json`, `~/.cipherstash/auth.json`, anything under
17+
`~/.cipherstash/workspaces/`, or `.env*`. The wizard already blocks these paths in code;
18+
the other handoff targets had no written rule.
19+
- **Documents `manifest`, `doctor`, `wizard`, and `auth regions`**, which the skill omitted
20+
entirely, plus the non-interactive interface (per-command escape hatches, exit codes, the
21+
`DATABASE_URL` resolution order, the `auth login --json` NDJSON event contract).
22+
- **Corrects the `db``eql` move.** `db install`, `db upgrade`, and `db status` are
23+
deprecated aliases that warn and forward; `db push`, `db activate`, `db validate`,
24+
`db test-connection`, and `db migrate` remain in the `db` group.
25+
- **Scopes `db push` / `db activate` as EQL v2 + CipherStash Proxy only**, in both the skill
26+
and the README's recommended flow. SDK users hold their encryption config in application
27+
code and don't need them.
28+
- Adds the missing `--database-url`, `--eql-version`, `--prisma-next`, `--proxy`/`--no-proxy`,
29+
and `--region` flags; corrects six programmatic API signatures; fixes the README's claim
30+
that `stash init` ends in an agent-handoff menu (that belongs to `stash plan` / `stash impl`);
31+
and marks `stash env` as the non-functional stub it currently is.
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/cli/README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,39 @@ The single CLI for CipherStash. It handles authentication, project initializatio
1212
```bash
1313
npm install -D stash
1414
npx stash auth login # authenticate with CipherStash
15-
npx stash init # scaffold, introspect, install EQL, hand off to your agent
15+
npx stash init # scaffold, introspect, install EQL
1616
```
1717

18-
`stash init` runs the whole setup as one flow: authenticate, resolve `DATABASE_URL`, introspect your database and let you pick which columns to encrypt, install dependencies, install the EQL extension, and finish by handing off to your local coding agent. At the end it presents a four-option menu:
18+
`stash init` does the scaffold-once work as one flow: authenticate, resolve `DATABASE_URL`, choose Proxy or direct SDK access, introspect your database and scaffold an encryption client, install dependencies, install the EQL extension, and write `.cipherstash/context.json`. It stops there, at a clean save-point, and offers to continue into `stash plan`.
19+
20+
The agent handoff belongs to the next two commands — `stash plan` drafts a reviewable `.cipherstash/plan.md`, and `stash impl` executes it. Both present the same four targets:
1921

2022
- **Hand off to Claude Code** — copies the per-integration set of skills (`stash-encryption`, `stash-<integration>`, `stash-cli`) into `.claude/skills/`, writes `.cipherstash/context.json` and `setup-prompt.md`, then launches `claude` interactively.
2123
- **Hand off to Codex** — copies the same skills into `.codex/skills/`, writes a sentinel-managed `AGENTS.md` (durable doctrine), plus `.cipherstash/` context files, then launches `codex`.
2224
- **Use the CipherStash Agent** — runs the in-house wizard (`@cipherstash/wizard`).
2325
- **Write AGENTS.md** — for editor agents (Cursor / Windsurf / Cline) that don't auto-load skill directories. Writes a single `AGENTS.md` with the doctrine *plus* the relevant skill content inlined under a sentinel block, and stops.
2426

25-
A project-specific action plan is written to `.cipherstash/setup-prompt.md` regardless of which option you pick — it tells the agent exactly what's already done and what's left, with the right commands for your package manager and ORM. The matching context (selected columns, env keys, paths, versions) is at `.cipherstash/context.json`.
27+
Pass `--target <claude-code|codex|agents-md|wizard>` to skip the picker. **It is required when running `plan` or `impl` non-interactively** (CI, pipes, an agent's shell) — the picker reads from `/dev/tty`, so without it the command prints a hint and exits without handing off.
28+
29+
A project-specific action plan is written to `.cipherstash/setup-prompt.md` regardless of which target you pick — it tells the agent exactly what's already done and what's left, with the right commands for your package manager and ORM. The matching context (selected columns, env keys, paths, versions) is at `.cipherstash/context.json`.
2630

27-
If neither `claude` nor `codex` is on PATH, init still writes the rules files and prints install instructions — your progress is never wasted.
31+
If neither `claude` nor `codex` is on PATH, the handoff still writes the rules files and prints install instructions — your progress is never wasted.
2832

2933
---
3034

3135
## Recommended flow
3236

3337
```
3438
npx stash auth login
35-
└── npx stash init ← introspects DB, installs EQL, hands off to your agent
36-
└── Agent edits schema files / generates migrations
37-
└── npx stash db push ← when ready to roll out further changes
39+
└── npx stash init ← introspects DB, installs EQL, writes context.json
40+
└── npx stash plan ← drafts .cipherstash/plan.md for review
41+
└── npx stash impl ← agent edits schema files / generates migrations
42+
└── npx stash status ← where am I?
3843
```
3944

40-
`stash` covers authentication, initialization, EQL install/upgrade/validate/push/migrate, schema introspection, and a `stash wizard` subcommand that thin-wraps [`@cipherstash/wizard`](https://www.npmjs.com/package/@cipherstash/wizard). The wizard package itself is a separate npm install — kept out of the `stash` bundle so the agent SDK doesn't bloat the CLI.
45+
`stash` covers authentication, initialization, EQL install/upgrade/status, schema introspection, the encryption rollout and cutover commands, and a `stash wizard` subcommand that thin-wraps [`@cipherstash/wizard`](https://www.npmjs.com/package/@cipherstash/wizard). The wizard package itself is a separate npm install — kept out of the `stash` bundle so the agent SDK doesn't bloat the CLI.
46+
47+
> `stash db push` is **not** part of the default flow. It registers the encryption config in `public.eql_v2_configuration`, which only [CipherStash Proxy](https://github.com/cipherstash/proxy) reads. SDK users (Drizzle, Supabase, plain PostgreSQL) keep that config in application code and can skip it.
4148
4249
---
4350

packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ This document is the **durable rule book** for any agent working on this codebas
1515

1616
1. **Encrypted columns are nullable `jsonb`.** Never declare them as `text`, `varchar`, `bytea`, or any plaintext type. Never mark them `NOT NULL` at creation — the application writes ciphertext after the column exists, and a `NOT NULL` constraint will break inserts. (DynamoDB equivalent: encrypted attributes are written as the SDK's encrypted-blob shape; do not invent your own scheme.)
1717
2. **Never log plaintext.** Do not add `console.log`, `logger.info`, or test-fixture dumps that print decrypted values. Sensitive fields stay in memory only as long as the request needs them. Encrypted blobs are also not for logs — they reveal which records were touched.
18-
3. **Never read or echo secrets.** Env key *names* (`CS_WORKSPACE_CRN`, `CS_CLIENT_ID`, `CS_CLIENT_KEY`, `CS_CLIENT_ACCESS_KEY`, `DATABASE_URL`) are fine to reference in code and docs. Their *values* are not. New env keys go in `.env.example` with placeholders; instruct the user to add the real value locally.
18+
3. **Never read or echo secrets.** Env key *names* (`CS_WORKSPACE_CRN`, `CS_CLIENT_ID`, `CS_CLIENT_KEY`, `CS_CLIENT_ACCESS_KEY`, `DATABASE_URL`) are fine to reference in code and docs. Their *values* are not. New env keys go in `.env.example` with placeholders; instruct the user to add the real value locally. Never read, `cat`, `grep`, or echo `~/.cipherstash/secretkey.json` (the development key), `~/.cipherstash/auth.json` (OAuth token and JWTs), anything under `~/.cipherstash/workspaces/`, or a value-bearing env file (`.env`, `.env.local`, `.env.production`, …). `.env.example` is the exception — it holds placeholders, not values, and you are expected to edit it. The CLI reads the credentials itself; no command needs you to open them. If a command fails on authentication, re-run `stash auth login` rather than inspecting the profile.
1919
4. **Never invent CipherStash APIs.** If you don't know how a function is called, read the relevant skill (see below) — don't guess. The TypeScript types in `@cipherstash/stack` are the source of truth for what's callable.
2020
5. **Never run database introspection yourself.** Don't run `psql`, `\d`, `pg_dump`, `supabase db dump`, or `drizzle-kit introspect`. The CLI already did this; the result is in `context.json`. If you need fresh introspection, ask the user to re-run `stash init`.
21-
6. **Never modify these files.** `stash.config.ts` (generated by init — edits go in `.env`). `.cipherstash/` (CLI-owned). The `eql_v2` schema and `eql_v2_*` functions (CLI-managed; missing function ⇒ `stash eql upgrade`, not a hand-edit).
21+
6. **Never modify these files.** `stash.config.ts` (generated by init — edits go in `.env`). `.cipherstash/` (CLI-owned). `~/.cipherstash/` (CLI-owned credentials — see invariant 3). The `eql_v2` schema and `eql_v2_*` functions (CLI-managed; missing function ⇒ `stash eql upgrade`, not a hand-edit).
2222
7. **`@cipherstash/stack` must be excluded from any bundler.** The package wraps a native FFI module (`@cipherstash/protect-ffi`) that cannot be bundled. The moment you `npm install @cipherstash/stack` in a project with a bundler, configure the exclusion *before* writing any code that imports it. Concretely: Next.js needs `serverExternalPackages: ['@cipherstash/stack', '@cipherstash/protect-ffi']` in `next.config.{js,ts,mjs}`; webpack needs `externals` entries; esbuild needs `external`; Vite SSR needs `ssr.external`. Skipping this surfaces as `Cannot find module '@cipherstash/protect-ffi-*'` at runtime, often after the user has shipped to production. If you're declaring an encrypted column for the first time in a project, treat configuring this exclusion as part of the same change.
2323

2424
## Migrations — three phases, always reversible

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)