From 9ddcd22aee55e538d53d7d14aaf0ebebce84cae5 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Thu, 23 Apr 2026 21:52:52 -0700 Subject: [PATCH] fix(ce-demo-reel): prevent secrets in recorded demos (#664) Co-authored-by: Claude Opus 4.7 (1M context) --- .../skills/ce-demo-reel/SKILL.md | 12 ++++++++++ .../references/tier-browser-reel.md | 12 ++++++++-- .../references/tier-screenshot-reel.md | 5 ++++ .../references/tier-static-screenshots.md | 2 ++ .../references/tier-terminal-recording.md | 23 +++++++++++++++---- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-demo-reel/SKILL.md b/plugins/compound-engineering/skills/ce-demo-reel/SKILL.md index a8e1963..c0baf5d 100644 --- a/plugins/compound-engineering/skills/ce-demo-reel/SKILL.md +++ b/plugins/compound-engineering/skills/ce-demo-reel/SKILL.md @@ -14,6 +14,18 @@ If real product usage is impractical (requires API keys, cloud deploy, paid serv Never generate fake or placeholder image/GIF URLs. If upload fails, report the failure. +## Never Record Secrets + +Recordings must never contain credentials — not in commands, output, URL bars, or on-screen UI. If the demo needs a credential, set it before the recording starts, outside the recorded region. + +**Core principle:** secrets should affect the environment, not the visible transcript. Hidden *real* setup beats visible *fake* setup — fake setup breaks the demo and still leaks the secret's shape. + +- **Plan it out of frame.** Route every surface where a secret could appear (env exports, CLI flag values, command output, auth headers, URL params, DevTools, config pages) out of the recorded region. Use VHS `Hide`/`Show`; invoke CLIs via env vars, not secret flag values; stay on user-facing pages. Show the authenticated result, not the auth step. +- **Do not substitute placeholders inside the recording.** Typing a fake `sk-xxxxx` produces a misleading artifact; recapture with the real credential set out of frame instead. Two specific failures: + - Re-exporting a fake value visibly (`export API_KEY=REDACTED`) overwrites the real env var, so the demo breaks (401, `Unauthorized`, `0 credits remaining`, empty output). You leak the variable name *and* ship a broken product. + - Planning to blur or crop later. Assume anything shown is leaked; recapture is the only remediation. +- **Scan before upload.** Look for `sk-`, `ghp_`, `ghs_`, `xoxb-`, `Bearer `, `Authorization:`, `?token=`, `api_key=`, long hex/base64 near credential-sounding labels, or visible `.env` contents. If any appear, discard and recapture. Never blur or crop. + ## Arguments Parse `$ARGUMENTS`: diff --git a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-browser-reel.md b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-browser-reel.md index 6c5c50d..1b526c2 100644 --- a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-browser-reel.md +++ b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-browser-reel.md @@ -84,6 +84,12 @@ agent-browser screenshot [RUN_DIR]/frame-01-initial.png - Wait 2-3 seconds after navigation for the page to settle - Capture the full viewport (sidebar, header give reviewers context) +**Keep secrets out of frame:** +- Do not open DevTools, the Network panel, or Application/Storage -- these expose auth headers, cookies, session storage, and tokens in plain view +- Skip pages that display raw credentials (unmasked API-key settings, OAuth consent screens, `.env` viewers, billing/payment detail) +- Check the URL bar before each screenshot -- if it carries a session token or credential query param (`?access_token=`, `?api_key=`, `#id_token=`), navigate to the clean canonical URL first +- Prefer a demo account or seeded fixture data over a real logged-in account when the screenshot will include account identifiers that are themselves sensitive + ## Step 3: Stitch into GIF Use the capture pipeline script to normalize frame dimensions, stitch with two-pass palette, and auto-reduce if over 10 MB: @@ -100,8 +106,10 @@ python3 scripts/capture-demo.py stitch --duration 2.0 [RUN_DIR]/demo.gif [RUN_DI **If stitching fails:** Fall back to static screenshots tier using the individual PNGs already captured. If no PNGs were captured, report the failure. -## Step 4: Cleanup +## Step 4: Secrets Scan and Cleanup -After successful GIF creation, remove individual PNG frames. Keep only the final GIF for upload. +Before uploading, inspect the final GIF for any credential material visible on-screen. If any appears, discard the GIF and recapture with the offending page or state routed out of frame. Do not upload, do not blur. + +After a clean GIF is confirmed, remove individual PNG frames. Keep only the final GIF for upload. Proceed to `references/upload-and-approval.md`. diff --git a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-screenshot-reel.md b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-screenshot-reel.md index 4af01f1..d528443 100644 --- a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-screenshot-reel.md +++ b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-screenshot-reel.md @@ -33,6 +33,11 @@ All checks passed - 3-5 frames is ideal -- enough to tell the story, not so many the GIF is huge - Strip unicode characters that silicon's default font can't render (checkmarks, fancy arrows) +**Never write secrets into the demo text:** +- Do not paste real credentials, API keys, tokens, or session IDs into any frame, even if copied from a real run +- Do not substitute fake-looking credentials like `sk-xxxxxxxxx` either -- that produces a misleading artifact. Instead, rewrite the command to use an env var whose *name* appears without a value (e.g., `your-cli --api-key "$API_KEY"`), or demonstrate a different command that doesn't take a secret +- If a sample output line would include a token, error trace with auth header, or other credential, edit that line out or pick a different scenario -- do not render it + ## Step 2: Split into Frame Files Split the demo content on `---` lines into separate text files, one per frame: diff --git a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-static-screenshots.md b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-static-screenshots.md index e3902e8..841a94b 100644 --- a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-static-screenshots.md +++ b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-static-screenshots.md @@ -7,6 +7,8 @@ Capture individual PNG screenshots. No animation, no stitching. **Label:** "Screenshots" **Required tools:** Varies (agent-browser for web, silicon for CLI, or native screenshot) +**Secrets rule applies here too.** For browser captures, do not open DevTools, do not screenshot URLs carrying tokens, and avoid pages that display unmasked credentials. For CLI captures, render output that was already free of credentials — no env-var dumps, no `--api-key` flag values, no auth headers in error traces. Scan each PNG before uploading; if anything credential-like appears, discard and recapture. + ## Capture by Project Type ### Web app or desktop app (agent-browser available) diff --git a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-terminal-recording.md b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-terminal-recording.md index be3e1d0..35aa335 100644 --- a/plugins/compound-engineering/skills/ce-demo-reel/references/tier-terminal-recording.md +++ b/plugins/compound-engineering/skills/ce-demo-reel/references/tier-terminal-recording.md @@ -15,6 +15,7 @@ Before generating a .tape file, determine: - **Expected output** -- What the terminal should show when the command succeeds. - **Terminal dimensions** -- Wide enough for the longest output line, tall enough to avoid scrolling. - **Timing** -- Target 5-10 seconds total. Enough sleep after each command for output to render. +- **Secret exposure points** -- Any step that could surface a credential: env exports, `source .env`, `printenv`/`env`/`set`, CLIs with `--api-key`/`--token` flags, verbose/debug flags, commands that echo tokens in output or error traces, shell prompts with env-interpolated `$VAR` segments. Set real credentials inside a `Hide` block at the top of the `.tape`, run `clear` at the end of the block to flush the buffer, then `Show`. Use a clean `HOME` (`export HOME=$(mktemp -d)`) inside `Hide` so personal dotfiles, cached CLI tokens, and env-interpolated prompts can't leak. ## Step 2: Generate .tape File @@ -29,16 +30,23 @@ Set Height 500 Set Theme "Catppuccin Mocha" Set TypingSpeed 40ms -# Hide boring setup +# Hidden prelude: clean HOME, set real secrets, any setup that would leak. +# These commands execute for real but never appear in the GIF. +# `clear` at the end flushes the buffer so Show starts on a clean screen. Hide +Type "export HOME=$(mktemp -d)" +Enter +Type "export API_KEY='real-secret-value'" +Enter Type "cd /path/to/project" Enter -Sleep 500ms +Type "clear" +Enter Show -# The demo +# Visible demo: commands consume the env set above, but never re-export, +# echo, or print it. Show the feature working -- not the auth mechanism. Type "your-cli-command --flag value" -Sleep 500ms Enter Sleep 3s @@ -46,6 +54,8 @@ Sleep 3s Sleep 2s ``` +**Why this shape:** success of the visible command is itself evidence the credential was set — no need to show the auth step. Never add a visible `export SECRET=...` with a fake value: it leaks the variable name and breaks the demo. + **Key .tape directives:** - `Output [path]` -- Where to write the GIF (must be first line) - `Set FontSize [14-18]` -- Larger for readability @@ -79,7 +89,10 @@ Read the generated GIF to verify: 1. Commands are visible and readable 2. Output renders completely (not cut off) 3. The feature being demonstrated is clearly shown -4. No secrets, credentials, or sensitive paths are visible + +**Secrets scan (hard gate):** Scan the GIF for credential material. If any appears, discard and re-record with the leaking step wrapped in `Hide`/`Show` or replaced. Do not upload, do not blur. + +**Drift check:** A broken visible command — `401 Unauthorized`, `Invalid API key`, `0 credits remaining`, empty output where data was expected — usually means a visible `export SECRET=...` after `Show` overwrote the real env. Fix the `.tape` so secrets are set in `Hide` only, never re-exported, and re-record. If quality is poor, revise the .tape file and re-record.