
- Claude Code
- Codex
- Copilot
- +2
Pre-Launch Security Review
An eight-point check for the mistakes that get small apps breached in month one, with PASS/FAIL per item.
8 results

An eight-point check for the mistakes that get small apps breached in month one, with PASS/FAIL per item.
Assumption: no n8n-MCP server is connected in this session, so I took the skill's fallback path: I wrote the workflow JSON directly from the bundled rules and checked it by review (a rule-check script plus a dry run of every expression). The real validator (`validate_workflow`) was not run, and nothing was deployed.
How the skill routed this
| Step | Sub-skill used | What it decided |
|---|---|---|
| Shape | `n8n-workflow-patterns` | Webhook processing pattern: Webhook, then Validate, then Action, then Respond |
| Node parameters | `n8n-node-configuration` | Slack post needs a channel and text. `responseMode: "responseNode"`. `options.responseCode` set on every Respond node. UUID node ids, no placeholder credentials |
| Expressions | `n8n-expression-syntax` | Webhook data lives under `.body`. Use `$('Webhook').item.json.body.*` once past the IF node. `=` prefix on every expression field |
| Error paths | `n8n-error-handling` | Slack gets `onError: "continueErrorOutput"` and its `main[1]` is wired to the 500 responder. `retryOnFail` is on. Bad input is caught upstream and gets a 400 |
| Validation | `n8n-validation-expert` | Runtime-profile checks done by review. Security warnings get fixed (header auth on the webhook) |
```
Webhook ──> Validate Input ──true──> Post to #signups ──success (main[0])──> Respond 200 OK
└──false──> Respond 400 Invalid Input
Post to #signups ──error (main[1])────> Respond 500 Slack Failed
```
Validation result (excerpt)
```
PASS Webhook responseMode is responseNode (Respond nodes control the reply)
PASS Every branch (success and each error) ends at a Respond to Webhook node: no hanging branch
PASS 'Post to #signups': onError=continueErrorOutput AND main[1] wired
PASS 'Post to #signups': retryOnFail with maxTries <= 5 and waitBetweenTries <= 5000
PASS Every {{ }} field starts with = (no 'Expression requires = prefix' error)
PASS Webhook fields read from .body, never $json.name / $json.email / $json.plan
…
43 passed, 0 failed
```
Expression dry run against sample webhook items:
```
valid IF -> true (Slack) Slack text: New signup: Ana Lima (ana@example.com) on pro
missing_plan IF -> false (400)
bad_email IF -> false (400)
no_body IF -> false (400)
500 body: {"error":"upstream_error","message":"The signup was received but could not be posted to Slack. Please retry later."}
```
Not verified without the server: the node typeVersions (Webhook 2.2, IF 2.3, Slack 2.3, Respond 1.5) and the Slack v2 parameter names; confirm by opening the nodes after import.
[The full answer also holds the six-node list, the importable workflow JSON and the expressions table with the reason behind each one.]Use it to have your coding agent build, configure and validate n8n workflows without the usual expression and node-config mistakes.

Idea to approved design to test-first plan to working code, with an approval gate between each stage.

24 named design moves for your coding agent: bolder, quieter, typeset, distill, polish, plus a heuristic critique and a technical audit.

Your agent interviews you about a plan until nothing is assumed, then writes the spec and tracer-bullet tickets.

One-line brief in, full design system out: page pattern, style, colour tokens, font pairing and anti-patterns from a local database.

Turns the commits since your last tag into user-facing release notes, grouped and in your voice.
Walkthrough Lines 1–8 pull `items` from the cache with no validation. Lines 10–18 loop and sum `item.price * item.qty`. Lines 20–22 return the total as a float. Assumptions 1. The cache always returns a list → None raises AttributeError at line 10. 2. Every item has `price` and `qty` → legacy rows without `qty` raise KeyError mid-loop; partial sums are lost. 3. `price` is numeric → a string price silently concatenates on some paths. Dangerous. 4. The list is not mutated during iteration → a concurrent cache refresh mutates it; totals come back inconsistent with no error. Dangerous. 5. An empty list means a zero total → returns 0 silently; callers cannot tell "no items" from "free". Dangerous. Verdict on your concerns: concurrency — unsafe; error handling — none; empty list — silent zero. Tests - items = None → a clear 500 with a logged cause - one item missing qty → validation error naming the item id - 10k items during a cache refresh → a consistent snapshot or a retry Confirm the diagnosis and I will propose a patch.
Line-by-line read of a function, every assumption it makes, what breaks when each fails — before any fix.