diff --git a/setup-deploy/SKILL.md.tmpl b/setup-deploy/SKILL.md.tmpl index 6ac0d4092..94e1688b4 100644 --- a/setup-deploy/SKILL.md.tmpl +++ b/setup-deploy/SKILL.md.tmpl @@ -99,7 +99,14 @@ Ask the user to confirm the production URL. Some Fly apps use custom domains. If `render.yaml` detected: 1. Extract service name and type from render.yaml -2. Check for Render API key: `echo $RENDER_API_KEY | head -c 4` (don't expose the full key) +2. Check only whether the Render API key is set. Never print any key bytes, including a prefix: +```bash +if [ -n "${RENDER_API_KEY:-}" ]; then + echo "RENDER_API_KEY: set" +else + echo "RENDER_API_KEY: not set" +fi +``` 3. Infer URL: `https://{service-name}.onrender.com` 4. Render deploys automatically on push to the connected branch — no deploy workflow needed 5. Set health check: the inferred URL diff --git a/test/setup-deploy-key-presence.test.ts b/test/setup-deploy-key-presence.test.ts new file mode 100644 index 000000000..bf93fbe52 --- /dev/null +++ b/test/setup-deploy-key-presence.test.ts @@ -0,0 +1,35 @@ +/** + * D4 (#1078): /setup-deploy ran `echo $RENDER_API_KEY | head -c 4`, printing + * the key's first bytes into the transcript. It now reports presence only. + */ +import { describe, test, expect } from 'bun:test'; +import { spawnSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; + +const SKILL = fs.readFileSync(path.resolve(import.meta.dir, '..', 'setup-deploy', 'SKILL.md'), 'utf-8'); +const KEY = 'rnd_D4testKeyValue1234567890'; + +describe('D4: setup-deploy reports Render key presence, never key bytes', () => { + const at = SKILL.indexOf('Check only whether the Render API key is set'); + const block = SKILL.slice(at).match(/```bash\n([\s\S]*?)\n```/)?.[1] ?? ''; + + test('the check runs and prints only set / not set', () => { + expect(at).toBeGreaterThanOrEqual(0); + const run = (env: Record) => spawnSync('bash', ['-c', block], { + env: { PATH: process.env.PATH!, ...env }, encoding: 'utf-8', timeout: 10_000, + }); + const set = run({ RENDER_API_KEY: KEY }); + expect(set.stdout).toBe('RENDER_API_KEY: set\n'); + expect(set.stdout).not.toContain(KEY.slice(0, 4)); + expect(run({}).stdout).toBe('RENDER_API_KEY: not set\n'); + }); + + test('no setup-deploy command prints a credential variable value', () => { + const fences = [...SKILL.matchAll(/```bash\n([\s\S]*?)\n```/g)].map(m => m[1]).join('\n'); + const inline = [...SKILL.matchAll(/`([^`\n]*)`/g)].map(m => m[1]).join('\n'); + for (const text of [fences, inline]) { + expect(text).not.toMatch(/(echo|printf)[^\n|;]*\$\{?[A-Z0-9_]*(API_KEY|TOKEN|SECRET)\b/); + } + }); +});