Skip to main content

create-channel

Facilitates human-in-the-loop approval for AI workflows by creating a FinalApproval channel for actions requiring validation.

Install this skill

or
0/100

Security score

The create-channel skill was audited on May 13, 2026 and we found 89 security issues across 4 threat categories, including 5 high-severity. Review the findings below before installing.

Categories Tested

Security Issues

high line 27

Template literal with variable interpolation in command context

SourceSKILL.md
27- Minimal cards like `<p>${data.subject}</p>` — wastes the reviewer's cognitive budget, looks unprofessional.
medium line 67

Template literal with variable interpolation in command context

SourceSKILL.md
67```bash
high line 258

Template literal with variable interpolation in command context

SourceSKILL.md
258If `$SCOPE_ENV` is not `production`, suffix the env var name with the environment so dev/staging/prod keys coexist cleanly: `FINALAPPROVAL_API_KEY_DEV`, `FINALAPPROVAL_API_KEY_STAGING`. This way the d
medium line 351

Template literal with variable interpolation in command context

SourceSKILL.md
351```html
medium line 376

Template literal with variable interpolation in command context

SourceSKILL.md
376```html
medium line 395

Template literal with variable interpolation in command context

SourceSKILL.md
395```html
medium line 434

Template literal with variable interpolation in command context

SourceSKILL.md
434const response = await fetch(`${baseUrl}/api/v1/approvals`, {
medium line 437

Template literal with variable interpolation in command context

SourceSKILL.md
437"Authorization": `Bearer ${process.env.FINALAPPROVAL_API_KEY}`,
medium line 441

Template literal with variable interpolation in command context

SourceSKILL.md
441title: `Send email to ${data.recipient}`,
medium line 511

Template literal with variable interpolation in command context

SourceSKILL.md
511.update(`${timestamp}.${body}`)
medium line 538

Template literal with variable interpolation in command context

SourceSKILL.md
538console.log(`Denied: ${approval.title}`);
medium line 540

Template literal with variable interpolation in command context

SourceSKILL.md
540console.log(`Reason: ${approval.denial_reason}`);
medium line 9

Webhook reference - potential data exfiltration

SourceSKILL.md
9Wire human-in-the-loop approval into an agent's action. The end result: the agent's code submits a request, a human approves or denies in the dashboard, and the webhook fires back into the agent's cod
medium line 15

Webhook reference - potential data exfiltration

SourceSKILL.md
15The FinalApproval API accepts two fields per approval: `body` (HTML the human sees) and `data` (JSON your webhook receives). Today the body is sent per-approval, but the intent is that **every approva
medium line 22

Webhook reference - potential data exfiltration

SourceSKILL.md
224. **Data flows separately.** The `data` field carries the machine-readable payload the webhook returns to your code. Keep it clean JSON — don't duplicate HTML fragments into it, don't stringify objec
medium line 44

Webhook reference - potential data exfiltration

SourceSKILL.md
442. **The runtime data fields** — the values that change per request. These define the TypeScript interface, the HTML template, and the webhook payload contract. If the action exists in the codebase, i
medium line 45

Webhook reference - potential data exfiltration

SourceSKILL.md
453. **The webhook destination** — a publicly reachable HTTPS URL. If the user doesn't have one, help them get one (existing route, tunnel, scaffold, or serverless function) — see step 3.
medium line 57

Webhook reference - potential data exfiltration

SourceSKILL.md
57- Propose, don't interrogate. "I see `sendEmail()` in `src/email.ts` — gating that one with fields `to/subject/body/priority`. Webhook URL?" beats a six-question form.
medium line 215

Webhook reference - potential data exfiltration

SourceSKILL.md
215### 3. Lock in the webhook URL
medium line 222

Webhook reference - potential data exfiltration

SourceSKILL.md
222| **(b)** Local + tunnel | Run `ngrok http 3000` (or `cloudflared tunnel --url http://localhost:3000`), capture the `https://*.ngrok-free.app` URL, append `/webhooks/finalapproval`. Tell them they can
medium line 223

Webhook reference - potential data exfiltration

SourceSKILL.md
223| **(c)** Scaffold Express | Use `http://localhost:3000/webhooks/finalapproval` only as a placeholder; immediately set up a tunnel (ngrok) to expose it. Without a public URL the channel can't deliver.
medium line 224

Webhook reference - potential data exfiltration

SourceSKILL.md
224| **(d)** Serverless | Deploy the route first (Vercel `app/api/webhooks/finalapproval/route.ts`, Cloudflare Worker, etc.), capture the deployed URL, use that. |
low line 241

Webhook reference - potential data exfiltration

SourceSKILL.md
241\"webhook_url\": \"https://your-server.com/webhooks/finalapproval\",
medium line 249

Webhook reference - potential data exfiltration

SourceSKILL.md
249- `webhook_secret` (`whsec_...`) — for verifying webhook signatures. Only returned when `webhook_url` is provided. **Shown once.**
medium line 254

Webhook reference - potential data exfiltration

SourceSKILL.md
254The bearer token already lives at `~/.finalapproval/token.json`. The channel-scoped `fa_` key and webhook secret belong in the **project's** `.env`:
low line 264

Webhook reference - potential data exfiltration

SourceSKILL.md
264FINALAPPROVAL_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
medium line 267

Webhook reference - potential data exfiltration

SourceSKILL.md
267Both credentials are shown once and cannot be retrieved again. If lost, the API key requires creating a new channel; the webhook secret can be regenerated by updating the webhook URL in channel settin
low line 284

Webhook reference - potential data exfiltration

SourceSKILL.md
284"body": "<div class=\"space-y-3\"><div class=\"rounded-lg border p-3\"><p class=\"text-xs text-gray-500\">This is a test</p><p class=\"font-medium\">A real approval from your agent will look like this
medium line 291

Webhook reference - potential data exfiltration

SourceSKILL.md
291Resolving the sample also exercises the webhook delivery path you configured in step 3 — useful for confirming end-to-end connectivity once step 7 is wired up.
medium line 297

Webhook reference - potential data exfiltration

SourceSKILL.md
2972. Includes the structured data for programmatic use in the webhook
medium line 454

Webhook reference - potential data exfiltration

SourceSKILL.md
454- `body` is the HTML template (what the human sees). `data` is structured JSON (what the webhook returns to your code). Always include both.
low line 473

Webhook reference - potential data exfiltration

SourceSKILL.md
473// The webhook (step 7) fires when the human decides and runs the real action
medium line 476

Webhook reference - potential data exfiltration

SourceSKILL.md
476### 7. Wire up the webhook receiver — close the loop
medium line 480

Webhook reference - potential data exfiltration

SourceSKILL.md
480When a human approves or denies, FinalApproval POSTs the decision to the webhook URL configured in step 3. **This is where the gated action actually runs.**
medium line 482

Webhook reference - potential data exfiltration

SourceSKILL.md
482First, search the codebase for existing webhook handlers:
medium line 483

Webhook reference - potential data exfiltration

SourceSKILL.md
483- Look for routes like `/webhooks`, `/api/hooks`, or similar patterns
medium line 487

Webhook reference - potential data exfiltration

SourceSKILL.md
487If no webhook infrastructure exists, scaffold a receiver based on what was decided in step 3:
medium line 491

Webhook reference - potential data exfiltration

SourceSKILL.md
491- **Serverless** — use the platform's HTTP handler signature (Vercel `app/api/webhooks/finalapproval/route.ts`, Cloudflare Worker `fetch()`, Lambda Function URL handler). The verification logic is ide
low line 500

Webhook reference - potential data exfiltration

SourceSKILL.md
500function verifyWebhook(headers: Record<string, string>, body: string): boolean {
low line 501

Webhook reference - potential data exfiltration

SourceSKILL.md
501const secret = process.env.FINALAPPROVAL_WEBHOOK_SECRET!;
low line 520

Webhook reference - potential data exfiltration

SourceSKILL.md
520// Webhook route
low line 521

Webhook reference - potential data exfiltration

SourceSKILL.md
521app.post("/webhooks/finalapproval", express.raw({ type: "application/json" }), (req, res) => {
low line 524

Webhook reference - potential data exfiltration

SourceSKILL.md
524if (!verifyWebhook(req.headers as Record<string, string>, rawBody)) {
medium line 549

Webhook reference - potential data exfiltration

SourceSKILL.md
549**The webhook handler closes the loop.** The submission function (step 6) sends the request; the webhook handler (this step) receives the decision and runs the action. Both use `approval.data` as the
medium line 555

Webhook reference - potential data exfiltration

SourceSKILL.md
555FinalApproval may retry webhook deliveries on failure (network blips, 5xx responses). Make the handler idempotent: track which `approval.id`s you've already processed (a Set in memory for ephemeral ha
medium line 557

Webhook reference - potential data exfiltration

SourceSKILL.md
557#### Webhook payload schema
medium line 590

Webhook reference - potential data exfiltration

SourceSKILL.md
5904. **Webhook fires on approval** — approve the request in the dashboard, confirm the webhook handler receives it (check logs) and executes the action (the email actually sends, the deploy actually run
medium line 591

Webhook reference - potential data exfiltration

SourceSKILL.md
5915. **Webhook fires on denial** — deny a request with a reason, confirm the handler receives `denial_reason` and the action is *not* executed
medium line 592

Webhook reference - potential data exfiltration

SourceSKILL.md
5926. **Test from settings** — open the channel settings in the dashboard and click "Test" to send a synthetic webhook delivery. Confirm signature verification passes
medium line 597

Webhook reference - potential data exfiltration

SourceSKILL.md
597- Webhook URL is reachable from the FinalApproval server
medium line 598

Webhook reference - potential data exfiltration

SourceSKILL.md
598- Webhook secret matches (regenerate by updating the webhook URL in channel settings)
low line 626

Webhook reference - potential data exfiltration

SourceSKILL.md
626**Webhook handler:** `<path/to/webhook/route>` — executes on `approval.resolved`
low line 627

Webhook reference - potential data exfiltration

SourceSKILL.md
627**Secrets:** `FINALAPPROVAL_API_KEY`, `FINALAPPROVAL_WEBHOOK_SECRET` in `.env`
low line 640

Webhook reference - potential data exfiltration

SourceSKILL.md
6404. **`body` is for humans, `data` is for code.** Never stringify objects into `body`. Never duplicate HTML fragments into `data`. The webhook handler reads `approval.data` — keep it clean JSON that ma
low line 641

Webhook reference - potential data exfiltration

SourceSKILL.md
6415. **Handle both approve and deny paths in the webhook.** Denied requests must surface back to the caller (error, status row, notification) — silent drops leave the agent confused.
low line 642

Webhook reference - potential data exfiltration

SourceSKILL.md
6426. **Webhook handler must be idempotent.** Track processed `approval.id`s; FinalApproval may retry on 5xx or network failure.
medium line 649

Webhook reference - potential data exfiltration

SourceSKILL.md
649- `.cursor/rules/finalapproval.mdc`: wrap with YAML frontmatter (`---\ndescription: ...\nglobs: <paths>\n---`) scoped to the submission and webhook paths.
high line 222

Ngrok tunnel reference

SourceSKILL.md
222| **(b)** Local + tunnel | Run `ngrok http 3000` (or `cloudflared tunnel --url http://localhost:3000`), capture the `https://*.ngrok-free.app` URL, append `/webhooks/finalapproval`. Tell them they can
high line 223

Ngrok tunnel reference

SourceSKILL.md
223| **(c)** Scaffold Express | Use `http://localhost:3000/webhooks/finalapproval` only as a placeholder; immediately set up a tunnel (ngrok) to expose it. Without a public URL the channel can't deliver.
high line 490

Ngrok tunnel reference

SourceSKILL.md
490- **Local dev with tunnel (ngrok/cloudflared)** — add the same route to your local server; the tunnel forwards traffic to it
medium line 61

Access to hidden dotfiles in home directory

SourceSKILL.md
61The rest of this skill needs a bearer token. Tokens are stored at `~/.finalapproval/token.json`.
low line 71

Access to hidden dotfiles in home directory

SourceSKILL.md
71if [ -f ~/.finalapproval/token.json ]; then
low line 72

Access to hidden dotfiles in home directory

SourceSKILL.md
72TOKEN=$(jq -r .token ~/.finalapproval/token.json)
low line 78

Access to hidden dotfiles in home directory

SourceSKILL.md
78[ -z "$CURRENT_EMAIL" ] && rm ~/.finalapproval/token.json
medium line 85

Access to hidden dotfiles in home directory

SourceSKILL.md
85- If they want to switch → `rm ~/.finalapproval/token.json` and fall through to 2b. The device flow starts fresh, no extra confirmation.
low line 140

Access to hidden dotfiles in home directory

SourceSKILL.md
140mkdir -p ~/.finalapproval
low line 141

Access to hidden dotfiles in home directory

SourceSKILL.md
141echo "{\"token\":\"$TOKEN\"}" > ~/.finalapproval/token.json
low line 142

Access to hidden dotfiles in home directory

SourceSKILL.md
142chmod 600 ~/.finalapproval/token.json
low line 233

Access to hidden dotfiles in home directory

SourceSKILL.md
233TOKEN=$(jq -r .token ~/.finalapproval/token.json)
medium line 254

Access to hidden dotfiles in home directory

SourceSKILL.md
254The bearer token already lives at `~/.finalapproval/token.json`. The channel-scoped `fa_` key and webhook secret belong in the **project's** `.env`:
medium line 595

Access to hidden dotfiles in home directory

SourceSKILL.md
595- `~/.finalapproval/token.json` exists and the token still works (re-run step 2 if not)
medium line 254

Access to .env file

SourceSKILL.md
254The bearer token already lives at `~/.finalapproval/token.json`. The channel-scoped `fa_` key and webhook secret belong in the **project's** `.env`:
medium line 256

Access to .env file

SourceSKILL.md
256Check the developer's `.env` first — they may already have `FINALAPPROVAL_API_KEY` from a previous channel. Each channel gets its own key, so use a channel-specific name if multiple channels exist (e.
medium line 258

Access to .env file

SourceSKILL.md
258If `$SCOPE_ENV` is not `production`, suffix the env var name with the environment so dev/staging/prod keys coexist cleanly: `FINALAPPROVAL_API_KEY_DEV`, `FINALAPPROVAL_API_KEY_STAGING`. This way the d
low line 433

Access to .env file

SourceSKILL.md
433const baseUrl = process.env.FINALAPPROVAL_URL ?? "https://www.finalapproval.ai";
low line 437

Access to .env file

SourceSKILL.md
437"Authorization": `Bearer ${process.env.FINALAPPROVAL_API_KEY}`,
low line 501

Access to .env file

SourceSKILL.md
501const secret = process.env.FINALAPPROVAL_WEBHOOK_SECRET!;
medium line 596

Access to .env file

SourceSKILL.md
596- `FINALAPPROVAL_API_KEY` is set in the project's `.env` (starts with `fa_`)
low line 627

Access to .env file

SourceSKILL.md
627**Secrets:** `FINALAPPROVAL_API_KEY`, `FINALAPPROVAL_WEBHOOK_SECRET` in `.env`
low line 63

External URL reference

SourceSKILL.md
63The default host is `https://www.finalapproval.ai`. Override with `FINALAPPROVAL_URL` (e.g. `http://localhost:3001` for local dev).
low line 68

External URL reference

SourceSKILL.md
68FINALAPPROVAL_URL="${FINALAPPROVAL_URL:-https://www.finalapproval.ai}"
low line 222

External URL reference

SourceSKILL.md
222| **(b)** Local + tunnel | Run `ngrok http 3000` (or `cloudflared tunnel --url http://localhost:3000`), capture the `https://*.ngrok-free.app` URL, append `/webhooks/finalapproval`. Tell them they can
low line 223

External URL reference

SourceSKILL.md
223| **(c)** Scaffold Express | Use `http://localhost:3000/webhooks/finalapproval` only as a placeholder; immediately set up a tunnel (ngrok) to expose it. Without a public URL the channel can't deliver.
low line 241

External URL reference

SourceSKILL.md
241\"webhook_url\": \"https://your-server.com/webhooks/finalapproval\",
low line 330

External URL reference

SourceSKILL.md
330- **Imagery:** `<img src="https://…" alt="…" loading="lazy" class="rounded-lg">` — HTTPS only, but it works. Use for brand marks, sender avatars (Gravatar), product thumbnails, OG previews.
low line 358

External URL reference

SourceSKILL.md
358<img src="https://www.gravatar.com/avatar/${gravatarHash}?s=64" alt="" class="rounded-full" loading="lazy" />
low line 433

External URL reference

SourceSKILL.md
433const baseUrl = process.env.FINALAPPROVAL_URL ?? "https://www.finalapproval.ai";
low line 663

External URL reference

SourceSKILL.md
663- **`<img src="https://…">`** — perfect for brand marks, sender avatars (Gravatar via `https://www.gravatar.com/avatar/<md5>`), product thumbnails, OG previews, screenshots hosted on your CDN. `alt` a
low line 680

External URL reference

SourceSKILL.md
680**Images:** `src` must use `https://`. Other schemes (`http://`, `data:`, relative paths) are stripped at render time.
Scanned on May 13, 2026
View Security Dashboard
Installation guide →