diff --git a/.changeset/quick-bears-tumble.md b/.changeset/quick-bears-tumble.md new file mode 100644 index 000000000..48dcc26b0 --- /dev/null +++ b/.changeset/quick-bears-tumble.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4500 +--- +**Verification examples no longer tell agents to grep .env files** — `verification-patterns.md` and `user-setup.md` documented reading `.env`/`.env.local` directly to verify environment variables, which every covered runtime's secret-read guard denies. The environment-variable checks now read the environment (`printenv`) instead of the file, and a broken placeholder-filter regex (`grep -v "a|b|c"`, where `|` is a literal BRE character) is replaced with a working case-insensitive check. (#4440) diff --git a/gsd-core/references/verification-patterns.md b/gsd-core/references/verification-patterns.md index 953dcba9d..fba266a98 100644 --- a/gsd-core/references/verification-patterns.md +++ b/gsd-core/references/verification-patterns.md @@ -309,14 +309,17 @@ grep -r "$hook_name()" src/ --include="*.tsx" --include="*.ts" | grep -v "$hook_ # .env file exists [ -f ".env" ] || [ -f ".env.local" ] -# Required variable is defined -grep -E "^$VAR_NAME=" .env .env.local 2>/dev/null +# Required variable is defined (in the environment: dotenv/direnv/the framework has loaded it) +printenv "$VAR_NAME" >/dev/null ``` **Substantive check:** ```bash -# Variable has actual value (not placeholder) -grep -E "^$VAR_NAME=.+" .env .env.local 2>/dev/null | grep -v "your-.*-here|xxx|placeholder|TODO" -i +# Variable has an actual value (not a placeholder) -- tests the shape, never prints the value; +# exit 0 = real value, exit 1 = missing or placeholder (case-insensitive) +v=$(printenv "$VAR_NAME"); case "$(printf %s "$v" | tr '[:upper:]' '[:lower:]')" in + ""|*your-*-here*|*xxx*|*placeholder*|*todo*) exit 1;; +esac # Value looks valid for type: # - URLs should start with http @@ -324,6 +327,16 @@ grep -E "^$VAR_NAME=.+" .env .env.local 2>/dev/null | grep -v "your-.*-here|xxx| # - Booleans should be true/false ``` +When the variable is not present in the agent's own environment (a framework that loads +`.env.local` itself at runtime does not export it to the shell that runs these checks), +ask the user to confirm it is set rather than reading `.env` directly. Variable NAMES can +still be checked against `.env.example`, which the secret-read guard exempts from its +protected-file patterns. + +One guard-matching note worth knowing when auditing docs for `.env` mentions: the guard +treats a grep PATTERN whose last path segment is a secret file name as a file operand, so +`grep -n "\.env" file.md` is denied while `grep -n "\.env\b" file.md` is allowed. + **Stub patterns specific to env:** ```bash # RED FLAGS - These are stubs: diff --git a/gsd-core/templates/user-setup.md b/gsd-core/templates/user-setup.md index 260a8552b..05ae663fc 100644 --- a/gsd-core/templates/user-setup.md +++ b/gsd-core/templates/user-setup.md @@ -171,9 +171,6 @@ Use the webhook signing secret from CLI output (starts with `whsec_`). After completing setup: ```bash -# Check env vars are set -grep STRIPE .env.local - # Verify build passes npm run build @@ -232,9 +229,6 @@ Complete these items for Supabase Auth to function. After completing setup: ```bash -# Check env vars -grep SUPABASE .env.local - # Verify connection (run in project directory) npx supabase status ``` @@ -285,9 +279,6 @@ Complete these items for SendGrid email to function. After completing setup: ```bash -# Check env var -grep SENDGRID .env.local - # Test email sending (replace with your test email) curl -X POST http://localhost:3000/api/test-email \ -H "Content-Type: application/json" \