
- 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.
12 results

An eight-point check for the mistakes that get small apps breached in month one, with PASS/FAIL per item.

Use it to score a pricing page or in-app paywall on eleven criteria and get three ranked fixes with rewritten copy and one experiment to run.
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.

Audit a live page that isn't converting: a 7-point scorecard, quick wins, test ideas and before/after copy.

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.

Strips AI tells from a draft without inventing a new voice: quoted flags, a clean rewrite, a second-pass check.
Short answer: ChatGPT can't recommend Ledgerly, because nothing it can retrieve says Ledgerly exists. Even after launch, a single landing page is not enough. A "best X for Y" shortlist comes from web-wide consensus (reviews, roundups, forums, press), not from your own site.
Visibility ladder (retrieved → cited → mentioned → recommended)
| Rung | Ledgerly today | What governs it |
|---|---|---|
| 1. Retrieved | No: not live, not in Bing's index (ChatGPT search draws from a Bing-based index) | Being indexed and crawlable |
| 2. Cited | No | Extractable, factual, fresh pages |
| 3. Mentioned | No | Entity recognition; the web talking about you |
| 4. Recommended | No | Aggregate third-party consensus, largely independent of your own content |
Source pool (one web search for the exact query, 2026-11-07; not a ChatGPT answer): only third-party roundups and affiliate lists (NerdWallet, business.org, Zapier, Fueler, Techjockey, Aspire). The same five apps recur: Wave, Zoho Books, FreshBooks, QuickBooks, Xero. I did not run ChatGPT, so I'm not reporting what ChatGPT says; run the query list yourself.
Extractability check on site/index.html: clear definition in the first paragraph, partial (the H1 is a slogan); self-contained answer blocks, fail; statistics with sources, fail; comparison table for "vs" queries, fail; FAQ, partial (good answers hidden in collapsed <details>, none answers "Is Ledgerly good for freelancers?"); schema markup, fail; author or founder, fail; freshness date, fail; query-shaped headings, fail.
Top fixes, by impact against effort
1. Launch, then get indexed in Bing and Google. robots.txt with a wildcard allow plus explicit allows for OAI-SearchBot, PerplexityBot, Claude-SearchBot and Bingbot; decide training crawlers (GPTBot, ClaudeBot) separately.
2. Seed third-party consensus: G2 and Capterra listings with the first 20–30 real reviews; pitch the roundups that already rank for the query with a specific angle ("$12/month, no per-transaction fees, automatic invoice chasing"). Test: if a model ignored your own domain, would the web still put you on the list? Today, no.
3. Rewrite the hero into an extractable definition (below).
4. Add /vs/freshbooks, then QuickBooks and Wave, in the "fair source" pattern. Do not publish a self-ranked "best bookkeeping apps" listicle: in the skill's data 69% of such citations recommended competitors.
5. Publish /pricing.md and /llms.txt; fix the Studio button ("Talk to us" on a plan with a public price reads as a sales wall to an agent).
6. JSON-LD: Organization, SoftwareApplication with Offer per plan, FAQPage.
7. Buyer-intent FAQs rendered open in the HTML. 8. Named founder, "Last updated", one dated sourced number. 9. Measure monthly: 8 queries, 3–5 runs per engine, log rung and framing.
Hero answer block (57 words, every fact from the existing page):
> Ledgerly is a bookkeeping app for freelancers and two-person studios. It connects to your bank through Plaid or TrueLayer (US, UK, EU), auto-categorises each transaction by category and client, chases unpaid invoices on day 3, 7 and 14, and exports tax-ready P&L, VAT and quarterly estimates. Free plan available; Pro is $12/month with no per-transaction fees. […]Get cited in AI Overviews, ChatGPT and Perplexity: visibility audit, crawler check, ranked fixes, answer-block rewrites.

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.