shippingszn 0.9.6 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +19 -18
- package/dist/index.js +257 -77
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 shippingszn llc
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# shippingszn
|
|
2
2
|
|
|
3
|
-
Primary local
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Primary local launch inspector for AI-built apps. Run it inside the app you are
|
|
4
|
+
about to ship to catch the launch debt AI builders commonly miss: leaked API
|
|
5
|
+
keys, missing crawl assets, weak browser defenses, dangerous code patterns, and
|
|
6
|
+
last-mile polish gaps.
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
npx shippingszn@latest
|
|
@@ -18,18 +18,18 @@ version, and safe stack tags. It never uploads source code, repo URLs, project
|
|
|
18
18
|
names, secrets, handles, or emails. Finding-level detail stays out of public
|
|
19
19
|
CLI/Wall output and opens inside the paid Launch Fix Kit.
|
|
20
20
|
|
|
21
|
-
Human output starts with a
|
|
22
|
-
plain-English launch-risk explanation, completed
|
|
23
|
-
handoff. Run with `--json` to get the same
|
|
24
|
-
machine-readable shape:
|
|
21
|
+
Human output starts with a verdict, a higher-is-better Readiness Score,
|
|
22
|
+
severity counts, a short plain-English launch-risk explanation, completed
|
|
23
|
+
checks, and the locked Fix Kit handoff. Run with `--json` to get the same
|
|
24
|
+
score-and-counts summary in a machine-readable shape:
|
|
25
25
|
|
|
26
26
|
```json
|
|
27
27
|
{
|
|
28
|
-
"score":
|
|
29
|
-
"band": "
|
|
30
|
-
"counts": { "critical":
|
|
28
|
+
"score": 60,
|
|
29
|
+
"band": "fix_first",
|
|
30
|
+
"counts": { "critical": 0, "high": 11, "medium": 1, "lower": 1 },
|
|
31
31
|
"filesScanned": 128,
|
|
32
|
-
"coverage": { "checksCompleted": 19, "checklistAreas":
|
|
32
|
+
"coverage": { "checksCompleted": 19, "checklistAreas": 51 },
|
|
33
33
|
"detailsLocked": true,
|
|
34
34
|
"unlockUrl": "https://shippingszn.com/fix-kit?scanResultId=00000000-0000-4000-8000-000000000123",
|
|
35
35
|
"scanHandoff": {
|
|
@@ -40,9 +40,9 @@ machine-readable shape:
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Finding titles, files, evidence,
|
|
44
|
-
The free CLI explains why the score matters, but the actual fixes
|
|
45
|
-
the Launch Fix Kit.
|
|
43
|
+
Finding titles, files, evidence, AI-builder punch list, and verification steps
|
|
44
|
+
are paid. The free CLI explains why the score matters, but the actual fixes
|
|
45
|
+
unlock inside the Launch Fix Kit.
|
|
46
46
|
|
|
47
47
|
## Aggregate summary
|
|
48
48
|
|
|
@@ -81,7 +81,8 @@ maps back to one of the items on the checklist.
|
|
|
81
81
|
|
|
82
82
|
Internally each finding is tagged Critical, High, Medium, or Lower and maps to
|
|
83
83
|
the relevant checklist item. The free CLI output keeps that finding-level detail
|
|
84
|
-
locked
|
|
84
|
+
locked; the Fix Kit turns it into the human launch decision and AI-builder punch
|
|
85
|
+
list.
|
|
85
86
|
|
|
86
87
|
## Suppressing false positives
|
|
87
88
|
|
|
@@ -122,8 +123,8 @@ These are deliberately out of scope for v1:
|
|
|
122
123
|
quotas, etc.).
|
|
123
124
|
|
|
124
125
|
A clean report is **not** a launch certificate — it just means none of the
|
|
125
|
-
obvious things tripped a tripwire.
|
|
126
|
-
ship.
|
|
126
|
+
obvious things tripped a tripwire. Use the Fix Kit, owner-verification items, and
|
|
127
|
+
rerun loop before you ship.
|
|
127
128
|
|
|
128
129
|
## Usage
|
|
129
130
|
|
package/dist/index.js
CHANGED
|
@@ -396,12 +396,14 @@ var ITEMS_CRITICAL_A = [
|
|
|
396
396
|
"Click Run and let it finish. It usually takes a few minutes; longer for big projects.",
|
|
397
397
|
"Sort by severity and look at everything marked Critical or High first. Ignore Low for now.",
|
|
398
398
|
`For each finding, click into it. The scanner explains what's wrong in plain language and often suggests a fix. If you don't understand a finding, paste it into your AI builder and ask "what does this mean and how do I fix it?"`,
|
|
399
|
+
"Run your builder's own scanner too \u2014 Cursor, Claude Code, Lovable, Replit, Bolt, Snyk, Semgrep, or whatever your stack exposes. Treat it as the final gate after the manual checks, not a substitute for them.",
|
|
399
400
|
"Re-run the scanner after each batch of fixes. You want zero Critical and as few High as possible before launch."
|
|
400
401
|
],
|
|
401
402
|
redFlags: [
|
|
402
403
|
"Any Critical finding you can't explain in your own words",
|
|
403
404
|
`High-severity issues you're planning to "deal with later"`,
|
|
404
405
|
'A long list of "hidden" or "ignored" issues with no notes on why',
|
|
406
|
+
"Your AI builder has a security/audit panel and you have never opened it",
|
|
405
407
|
"Findings that show your app sending user data to companies you didn't intend",
|
|
406
408
|
"Files flagged as malicious or unsafe that you don't recognize"
|
|
407
409
|
],
|
|
@@ -415,12 +417,13 @@ var ITEMS_CRITICAL_A = [
|
|
|
415
417
|
category: "Security",
|
|
416
418
|
priority: "critical",
|
|
417
419
|
timeEstimate: "1 hr",
|
|
418
|
-
prompt: "Act as a security engineer. Scan my entire codebase (including config, scripts, tests, frontend, build output, and any committed .env files) for hardcoded secrets \u2014 API keys, tokens, database passwords, OAuth client secrets, webhook signing keys, JWT signing keys, anything that should be private. For each finding, tell me: file, line, what kind of secret it appears to be, and which service it's for. Then refactor every finding to read from environment variables with clear names (OPENAI_API_KEY, STRIPE_SECRET_KEY, DATABASE_URL, etc.), and update any .env.example or docs to list the required variables. Also audit runtime leakage paths: any console.log / logger call that prints req.body, req.headers, full user objects, OAuth callback params, or error objects containing tokens; any Sentry (or equivalent) breadcrumbs that capture request bodies or auth headers without scrubbing; any TODO/debug comments that quote a real key. List every secret that needs to be ROTATED at the source service because it was previously committed, shared, or
|
|
420
|
+
prompt: "Act as a security engineer. Scan my entire codebase (including config, scripts, tests, frontend, build output, and any committed .env files) for hardcoded secrets \u2014 API keys, tokens, database passwords, OAuth client secrets, webhook signing keys, JWT signing keys, anything that should be private. Classify every key as public-safe or secret-only: publishable Stripe keys, Supabase anon keys, and other documented browser keys may live in the frontend; service-role keys, Stripe secret keys, OpenAI/Anthropic keys, database URLs, webhook secrets, OAuth client secrets, and signing keys must stay server-side only. For each finding, tell me: file, line, what kind of secret it appears to be, and which service it's for. Then refactor every secret-only finding to read from environment variables with clear names (OPENAI_API_KEY, STRIPE_SECRET_KEY, DATABASE_URL, etc.), and update any .env.example or docs to list the required variables without values. Also audit runtime leakage paths: any console.log / logger call that prints req.body, req.headers, full user objects, OAuth callback params, or error objects containing tokens; any Sentry (or equivalent) breadcrumbs that capture request bodies or auth headers without scrubbing; any TODO/debug comments that quote a real key. List every secret that needs to be ROTATED at the source service because it was previously committed, shared, logged, screenshotted, or pasted into chat. Never print actual secret values back to me \u2014 only references and filenames.",
|
|
419
421
|
what: "Every app you build talks to other services \u2014 Stripe for payments, OpenAI for AI, your database for data. Each of those gives you a long secret string (an API key) that proves it's you. If those strings are sitting inside your code, anyone who looks at your code can use them. That includes anyone you accidentally share a screenshot with, anyone you push to GitHub, and anyone who gets into your builder account.",
|
|
420
|
-
why: `This is the #1 way
|
|
422
|
+
why: `This is the #1 way AI-built apps get destroyed. One leaked OpenAI key can rack up thousands of dollars over a weekend. One leaked Stripe key can let someone refund every charge you've ever made. Every modern builder gives you a place to store these safely \u2014 usually called "Secrets," "Environment Variables," or ".env" \u2014 you just have to use it.`,
|
|
421
423
|
steps: [
|
|
422
424
|
"Find the Secrets / Environment Variables panel in your builder (usually a lock or key icon in the sidebar). Replit calls it Secrets, Vercel calls them Environment Variables, Bolt calls it .env, etc.",
|
|
423
425
|
'Search your code for anything that looks like a long random string \u2014 things starting with "sk-", "AIza", or any variable named "API_KEY", "SECRET", "TOKEN", or "PASSWORD".',
|
|
426
|
+
"Separate public keys from secret keys. Publishable Stripe keys and Supabase anon keys can be browser-visible when provider docs say so; service-role keys, secret keys, AI keys, database URLs, and signing secrets cannot.",
|
|
424
427
|
"Run a git-history scan, not just a search through current files. Tools like gitleaks (free, one command: `gitleaks detect`) check every commit you've ever made \u2014 including ones where you 'removed' the key by editing the file. A removed key still lives in history forever, and bots scrape public GitHub for these patterns within minutes of a push.",
|
|
425
428
|
'For each one, add it to Secrets / env vars with a clear name (like OPENAI_API_KEY), then ask your AI builder: "replace the hardcoded OPENAI_API_KEY in my code with the environment variable."',
|
|
426
429
|
"Test that the app still works after the swap.",
|
|
@@ -429,6 +432,8 @@ var ITEMS_CRITICAL_A = [
|
|
|
429
432
|
redFlags: [
|
|
430
433
|
"You can search your project files and find an actual API key sitting in plain text",
|
|
431
434
|
"A database connection URL with the password right in it, in code",
|
|
435
|
+
"A VITE_ / NEXT_PUBLIC_ / EXPO_PUBLIC_ variable whose name includes SECRET, TOKEN, PASSWORD, PRIVATE, SERVICE_ROLE, or ADMIN",
|
|
436
|
+
"A Supabase service-role key, Stripe secret key, OpenAI key, Anthropic key, webhook secret, OAuth client secret, or database URL visible in frontend code",
|
|
432
437
|
"You've pasted real keys into chats, screenshots, or AI prompts and never rotated them",
|
|
433
438
|
"Your .env or secrets file is sitting in your GitHub history",
|
|
434
439
|
"You've never run gitleaks (or any history scanner) \u2014 only searched current files"
|
|
@@ -437,8 +442,8 @@ var ITEMS_CRITICAL_A = [
|
|
|
437
442
|
cliPrompt: {
|
|
438
443
|
whatFailed: "Your codebase contains what looks like a hardcoded API key or password. The scanner found a string that matches a common provider's secret format (OpenAI, Anthropic, Stripe, AWS, GitHub, etc.) sitting directly in source code or a committed config file.",
|
|
439
444
|
whyItBlocksLaunch: "A leaked production key is the single fastest way to lose money or expose user data. Public-repo secrets get scraped within minutes of being committed, and the bots that find them spin up paid API calls or exfiltrate database content before you notice. Even private repos leak \u2014 screenshots, accidental publishes, and shared chat threads all count.",
|
|
440
|
-
fixInstructions: "Move every detected secret out of source into environment variables (OPENAI_API_KEY, STRIPE_SECRET_KEY, DATABASE_URL, etc.) and read them at runtime via process.env. Add
|
|
441
|
-
aiBuilderPrompt: "Find every hardcoded
|
|
445
|
+
fixInstructions: "Move every detected secret-only value out of source into environment variables (OPENAI_API_KEY, STRIPE_SECRET_KEY, DATABASE_URL, etc.) and read them at runtime via process.env. Browser-visible public keys are allowed only when provider docs explicitly mark them publishable or anonymous; everything else moves behind a backend endpoint or serverless function. Add variable names to .env.example without values. Then ROTATE the original secret at the provider \u2014 the leaked value must be considered compromised. Update .gitignore to exclude .env, and run gitleaks across history to catch earlier commits.",
|
|
446
|
+
aiBuilderPrompt: "Find every hardcoded credential in this project (API keys, tokens, OAuth client secrets, JWT signing keys, webhook secrets, database passwords). For each: tell me the file, the line, what kind of credential it is, and whether provider docs say it is public-safe or secret-only. Refactor every secret-only credential to read from process.env with a clear name (OPENAI_API_KEY, STRIPE_SECRET_KEY, etc.) or move the action behind a backend endpoint. Add every required variable name to .env.example without values. Make sure .env is in .gitignore. List every secret value that needs to be ROTATED at the source service. Never print actual secret values back to me \u2014 only references.",
|
|
442
447
|
verificationStep: "Run `npx shippingszn` again and confirm zero findings under `secrets`. Run `gitleaks detect --source . --verbose` to scan git history. Rotate any value that ever appeared in a commit, screenshot, or chat \u2014 even removing it from current code doesn't unrotate it."
|
|
443
448
|
}
|
|
444
449
|
},
|
|
@@ -449,11 +454,12 @@ var ITEMS_CRITICAL_A = [
|
|
|
449
454
|
category: "Operations",
|
|
450
455
|
priority: "critical",
|
|
451
456
|
timeEstimate: "2 hr+",
|
|
452
|
-
prompt: "Act as a senior backend engineer. Audit my app for every paid third-party API I call (OpenAI, Anthropic, Replicate, ElevenLabs, Twilio, image generation, transcription, anything per-request). For each one: (1) tell me where in the provider's dashboard to set a HARD spending cap (not just an alert) and what number to start with \u2014 recommend a sane default for an early-stage app; (2) add an in-app per-user daily and per-IP daily quota for that endpoint, returning a polite 429 when exceeded; (
|
|
457
|
+
prompt: "Act as a senior backend engineer. Audit my app for every paid third-party API I call (OpenAI, Anthropic, Replicate, ElevenLabs, Twilio, Resend, SendGrid, scraping, search, image generation, transcription, anything per-request). For each one: (1) tell me where in the provider's dashboard to set a HARD spending cap (not just an alert) and what number to start with \u2014 recommend a sane default for an early-stage app; (2) set dashboard alerts at 50% and 80% of that cap where the provider supports alerts; (3) add an in-app per-user daily and per-IP daily quota for that endpoint, returning a polite 429 when exceeded; (4) add a global kill-switch I can toggle via env var to instantly stop calls if costs spike; (5) log every paid call with user ID, route, token count or unit cost so I can see who is consuming what; (6) add a simple cost dashboard or weekly email summary. After you're done, tell me the absolute worst-case daily spend if my app got pounded by a bot.",
|
|
453
458
|
what: "Every AI API and paid third-party service charges per request. If your app calls OpenAI, Anthropic, Replicate, ElevenLabs, Twilio, or anything similar, an attacker (or a bug) can run those calls in a loop and turn your free trial into a four-figure bill overnight. Spend caps and per-user quotas are the seatbelts.",
|
|
454
|
-
why: "This is the most underrated risk for
|
|
459
|
+
why: "This is the most underrated risk for AI-built apps in 2026. AI builders happily wire up an OpenAI key for you with no quotas. One infinite loop, one abusive script, or one curious user can rack up $1K\u2013$10K in a weekend. The provider will not refund it. Caps cost nothing and take 10 minutes to set.",
|
|
455
460
|
steps: [
|
|
456
461
|
"Log into every paid API dashboard (OpenAI, Anthropic, Replicate, etc.) and set a HARD monthly spending cap, not just a warning email. Start low \u2014 you can raise it.",
|
|
462
|
+
"Set dashboard alerts before the cap, at minimum 50% and 80%, so you hear about abuse before the provider shuts off the service or the bill lands.",
|
|
457
463
|
"In your app, add per-user quotas: 'this user can make at most N AI requests per day.' Even logged-in users need this.",
|
|
458
464
|
"Add per-IP rate limits on AI endpoints, separate from your normal API rate limits \u2014 much stricter.",
|
|
459
465
|
"Add a global kill-switch (an env variable like AI_ENABLED=false) you can flip in 10 seconds if costs spike.",
|
|
@@ -461,6 +467,8 @@ var ITEMS_CRITICAL_A = [
|
|
|
461
467
|
],
|
|
462
468
|
redFlags: [
|
|
463
469
|
"No spending cap set in your AI provider's dashboard",
|
|
470
|
+
"Only a warning email exists \u2014 no hard cap, quota, or kill switch",
|
|
471
|
+
"No alert before the cap is hit",
|
|
464
472
|
"No per-user limit on your AI features \u2014 anyone signed up can call them unlimited times",
|
|
465
473
|
"AI endpoints exposed to logged-out users without strict per-IP limits",
|
|
466
474
|
"You can't quickly answer 'what's the worst case my AI bill could be tomorrow?'",
|
|
@@ -475,7 +483,7 @@ var ITEMS_CRITICAL_A = [
|
|
|
475
483
|
category: "Security",
|
|
476
484
|
priority: "critical",
|
|
477
485
|
timeEstimate: "2 hr+",
|
|
478
|
-
prompt: "Act as a senior application security engineer. I want to replace any custom or partial authentication in this app with a battle-tested provider. First, recommend the best fit for my stack from: Clerk, Auth0, Supabase Auth, Stytch, or my platform's built-in auth \u2014 and explain why in one paragraph. Then implement it end-to-end: signup, login, logout, password reset, email verification, session management, and protected routes (both pages and API). Use the provider's recommended secure defaults. Add server-side checks on every protected route \u2014 never trust the frontend. Add login and OTP rate limiting (max 5 attempts per 15 minutes per IP+identifier). If email or SMS one-time codes are used, normalize phone numbers to E.164, make resend/cooldown behavior explicit, use anti-enumeration responses, write clear mobile copy, verify real code delivery, and add a recovery path if paid access depends on OTP. Set session expiry to 24h of inactivity. Migrate any existing user data safely. After you're done, give me a checklist of what I need to verify in the provider dashboard before launch.",
|
|
486
|
+
prompt: "Act as a senior application security engineer. I want to replace any custom or partial authentication in this app with a battle-tested provider. First, recommend the best fit for my stack from: Clerk, Auth0, Supabase Auth, Stytch, or my platform's built-in auth \u2014 and explain why in one paragraph. Then implement it end-to-end: signup, login, logout, password reset, email verification, session management, and protected routes (both pages and API). Use the provider's recommended secure defaults. Add server-side checks on every protected route \u2014 never trust the frontend. Add login and OTP rate limiting (max 5 attempts per 15 minutes per IP+identifier). If email or SMS one-time codes are used, normalize phone numbers to E.164, make resend/cooldown behavior explicit, use anti-enumeration responses, write clear mobile copy, verify real code delivery, handle double-clicked/expired verification links gracefully, and add a recovery path if paid access depends on OTP. Set session expiry to 24h of inactivity. Migrate any existing user data safely. After you're done, give me a checklist of what I need to verify in the provider dashboard before launch.",
|
|
479
487
|
what: "How users sign in, receive verification codes, recover paid access, and stay signed in. Done well, only the real user can get into their account. Done badly, an attacker can guess codes, enumerate customers, abuse SMS/email delivery, steal sessions, or read passwords straight out of your database.",
|
|
480
488
|
why: "If your login or OTP flow is weak, every other security thing you did doesn't matter \u2014 the attacker just walks in the front door or your paid users get locked out. The good news: you almost never need to build login from scratch. Use a proven provider and let shippingszn scan for the launch risks AI builders usually miss.",
|
|
481
489
|
steps: [
|
|
@@ -483,6 +491,7 @@ var ITEMS_CRITICAL_A = [
|
|
|
483
491
|
"Never store passwords directly. Real auth providers store a one-way scrambled version (a hash) so even they can't read it.",
|
|
484
492
|
"Turn on rate limiting on login (max 5 wrong attempts in 15 minutes) so attackers can't sit there guessing forever.",
|
|
485
493
|
"If you use email or SMS OTP, normalize phone numbers, add resend cooldown copy, use generic success-shaped start responses, and smoke a real delivered code before launch.",
|
|
494
|
+
"Make expired, reused, and double-clicked verification links land in a safe state instead of throwing a scary error or leaking account state.",
|
|
486
495
|
"If paid report or purchase access depends on OTP, add a recovery path: alternate contact, receipt/support handoff, or purchase history.",
|
|
487
496
|
"Set sessions to expire (12\u201324 hours is normal) so a stolen laptop doesn't mean a permanent account takeover.",
|
|
488
497
|
"Offer two-factor authentication (2FA) if your auth provider supports it \u2014 most do, with one toggle."
|
|
@@ -496,6 +505,7 @@ var ITEMS_CRITICAL_A = [
|
|
|
496
505
|
"SMS OTP compares raw phone strings instead of normalized E.164 numbers",
|
|
497
506
|
"The OTP start screen reveals whether an email, phone, purchase, or report exists",
|
|
498
507
|
"Paid users have no fallback when the email or SMS code does not arrive",
|
|
508
|
+
"Verification links break when clicked twice, opened on mobile, or opened after expiry",
|
|
499
509
|
"Session cookies missing HttpOnly, Secure, or SameSite (check Application \u2192 Cookies in dev tools)"
|
|
500
510
|
],
|
|
501
511
|
cliCoverage: "automated",
|
|
@@ -514,7 +524,7 @@ var ITEMS_CRITICAL_A = [
|
|
|
514
524
|
category: "Security",
|
|
515
525
|
priority: "critical",
|
|
516
526
|
timeEstimate: "2 hr+",
|
|
517
|
-
prompt: "Act as a security auditor. Audit my full app against the OWASP Top 10, focusing on: (1) SQL injection \u2014 find any place I'm building queries with string concatenation or user input that bypasses my ORM's parameterization; (2) NoSQL injection (Mongo, Firestore) \u2014 any place user input becomes a filter object without validation; (3) command injection \u2014 any place user input reaches a shell-invocation API in my language (Node shelling out, Python shell wrappers, Ruby backticks, etc.); (4) LDAP injection if I talk to a directory service; (5) XSS \u2014 find any place user input is rendered as HTML without escaping (innerHTML, raw HTML props, v-html, raw template interpolation, legacy doc-write APIs); (6) CSRF \u2014 find any state-changing endpoint without CSRF protection or SameSite cookies; (7) SSRF \u2014 find any place user input becomes a URL my server fetches; (8) insecure deserialization; (9) open redirects; (10) file upload handling \u2014 verify every upload path enforces a MIME allowlist, max size, path-traversal-safe filenames, and stores outside the web root (or on object storage with no public list); (11) verbose error responses \u2014 verify NODE_ENV (or equivalent) is 'production' in production and no stack trace, ORM dump, or absolute file path ever leaks to an end-user response body. For each finding: file, line, severity, plain-English explanation, exact fix. Apply the fixes after listing them. Then add a Content Security Policy header tuned to my actual asset sources \u2014 no wildcards, minimal unsafe-inline, justify any exception.",
|
|
527
|
+
prompt: "Act as a security auditor. Audit my full app against the OWASP Top 10, focusing on: (1) SQL injection \u2014 find any place I'm building queries with string concatenation or user input that bypasses my ORM's parameterization; (2) NoSQL injection (Mongo, Firestore) \u2014 any place user input becomes a filter object without validation; (3) command injection \u2014 any place user input reaches a shell-invocation API in my language (Node shelling out, Python shell wrappers, Ruby backticks, etc.); (4) LDAP injection if I talk to a directory service; (5) XSS \u2014 find any place user input is rendered as HTML without escaping (innerHTML, raw HTML props, v-html, raw template interpolation, legacy doc-write APIs); (6) CSRF \u2014 find any state-changing endpoint without CSRF protection or SameSite cookies; (7) SSRF \u2014 find any place user input becomes a URL my server fetches; (8) insecure deserialization; (9) open redirects; (10) file upload handling \u2014 verify every upload path enforces a MIME allowlist, max size, path-traversal-safe filenames, and stores outside the web root (or on object storage with no public list); (11) verbose error responses \u2014 verify NODE_ENV (or equivalent) is 'production' in production and no stack trace, ORM dump, SQL query, table name, column name, internal service URL, or absolute file path ever leaks to an end-user response body. For each finding: file, line, severity, plain-English explanation, exact fix. Apply the fixes after listing them. Then add a Content Security Policy header tuned to my actual asset sources \u2014 no wildcards, minimal unsafe-inline, justify any exception.",
|
|
518
528
|
what: "There are a handful of attacks that bots constantly run against every site on the internet. They have ugly names \u2014 XSS, SQL injection, CSRF \u2014 but the idea is simple: they trick your app into running code or queries it shouldn't. Modern frameworks have built-in defenses; you just have to use them correctly.",
|
|
519
529
|
why: "These bots don't care who you are. They scan the entire internet looking for sites that forgot to defend. If yours is one of those, your data ends up dumped on a forum, your users get hijacked, and you find out by reading about yourself online.",
|
|
520
530
|
steps: [
|
|
@@ -522,19 +532,21 @@ var ITEMS_CRITICAL_A = [
|
|
|
522
532
|
"Never paste user input directly into HTML you display to other users. Frameworks like React handle this safely by default \u2014 don't disable that behavior.",
|
|
523
533
|
"Add a Content Security Policy header (your AI builder can set this up in one prompt) that tells browsers what code they're allowed to run.",
|
|
524
534
|
"Use anti-CSRF tokens on forms that change data, if your auth provider doesn't already handle them.",
|
|
535
|
+
"Show generic errors to users and log detailed errors server-side. Users should never see SQL, table names, stack traces, service URLs, or raw exception text.",
|
|
525
536
|
'Ask your AI builder: "audit my app for XSS, SQL injection, and CSRF vulnerabilities and fix any you find."'
|
|
526
537
|
],
|
|
527
538
|
redFlags: [
|
|
528
539
|
"Anywhere you're building SQL queries by gluing strings together",
|
|
529
540
|
"Anywhere user input gets shown back to other users without going through the framework's safe rendering",
|
|
530
541
|
"Your framework is showing security warnings in the console you've been ignoring",
|
|
531
|
-
"Forms that change data (delete, update, transfer money) work fine when called from random other websites"
|
|
542
|
+
"Forms that change data (delete, update, transfer money) work fine when called from random other websites",
|
|
543
|
+
"Production errors show stack traces, table names, SQL snippets, ORM errors, absolute file paths, or internal service URLs"
|
|
532
544
|
],
|
|
533
545
|
cliCoverage: "automated",
|
|
534
546
|
cliPrompt: {
|
|
535
547
|
whatFailed: "The scanner caught a code pattern that maps to a well-known automated attack: unsafe HTML rendering (XSS), wildcard CORS (any origin can call your API), runtime code-execution calls, or a SQL/NoSQL query built from string concatenation instead of parameterization.",
|
|
536
548
|
whyItBlocksLaunch: "These are not theoretical. Bots scan the entire internet looking for sites that forgot to use the framework's safe defaults. Your app is one bot away from a stolen session, a hijacked admin route, or a customer-data dump on a forum. The fix is almost always a one-line change to use the framework's safe primitive instead of the raw one.",
|
|
537
|
-
fixInstructions: "Replace unsafe HTML rendering APIs (innerHTML, raw HTML props, v-html, legacy doc-write APIs) with the framework's safe interpolation (React's default {}, Vue's {{ }}, etc.). Replace wildcard CORS (`*`) with an explicit allowlist of trusted origins. Eliminate runtime code-execution calls \u2014 there is almost always a parser or DSL that does the job safely. Use parameterized queries everywhere \u2014 never glue strings into SQL/NoSQL filters. Add a Content Security Policy header tuned to your actual asset origins.",
|
|
549
|
+
fixInstructions: "Replace unsafe HTML rendering APIs (innerHTML, raw HTML props, v-html, legacy doc-write APIs) with the framework's safe interpolation (React's default {}, Vue's {{ }}, etc.). Replace wildcard CORS (`*`) with an explicit allowlist of trusted origins. Eliminate runtime code-execution calls \u2014 there is almost always a parser or DSL that does the job safely. Use parameterized queries everywhere \u2014 never glue strings into SQL/NoSQL filters. Return generic user-facing errors and log detailed exceptions only server-side. Add a Content Security Policy header tuned to your actual asset origins.",
|
|
538
550
|
aiBuilderPrompt: "Audit every finding in this scan tagged `common-attacks`. For each: explain in plain English what the attack is, show me the exact line, and apply the smallest safe fix \u2014 escape HTML rather than disabling sanitization, parameterize queries rather than string-glue, narrow CORS to the actual production origin(s), replace runtime code-execution calls with a typed parser. Also add a Content Security Policy header that allowlists ONLY my actual asset origins (no wildcards, minimal unsafe-inline, justify every exception). Show me the diff before applying.",
|
|
539
551
|
verificationStep: "Re-run the scanner and confirm zero findings tagged `common-attacks`. In the deployed app, open dev-tools Network and confirm responses include a tight CSP header. Try posting a `<script>alert(1)</script>` payload anywhere user content is rendered \u2014 confirm it shows as text, not as an executed alert."
|
|
540
552
|
}
|
|
@@ -554,7 +566,7 @@ var ITEMS_CRITICAL_B = [
|
|
|
554
566
|
what: "HTTPS is the little padlock in the browser bar. It encrypts everything between your user and your server, so people sharing the same WiFi can't read passwords as they're being typed. Security headers are extra instructions you send to the browser saying \u201Cnever trust content claiming to be from me unless it really is.\u201D",
|
|
555
567
|
why: "Without HTTPS, anyone on the same coffee-shop WiFi can read your users' passwords as plaintext. Without security headers, attackers can wrap your site inside theirs (clickjacking) or trick browsers into running malicious scripts. The fixes are basically free.",
|
|
556
568
|
steps: [
|
|
557
|
-
"Most modern hosting
|
|
569
|
+
"Most modern hosting gives you HTTPS automatically when you publish \u2014 confirm the lock icon shows up in the browser bar.",
|
|
558
570
|
"Set up an automatic redirect from http:// to https:// so no one accidentally lands on the unencrypted version.",
|
|
559
571
|
"Add a Content-Security-Policy header that locks down where your scripts, images, and fonts are allowed to come from.",
|
|
560
572
|
"Add X-Content-Type-Options: nosniff and X-Frame-Options: DENY (or SAMEORIGIN if you embed your own pages).",
|
|
@@ -608,7 +620,7 @@ var ITEMS_CRITICAL_B = [
|
|
|
608
620
|
priority: "critical",
|
|
609
621
|
timeEstimate: "1 hr",
|
|
610
622
|
prompt: "Act as a senior infrastructure engineer. Tell me, for my specific database setup, exactly: (1) is automatic point-in-time backup turned on, and how far back can I restore? (2) where the backups physically live and who can access them; (3) the EXACT click-by-click procedure to restore my database to a point 24 hours ago in a non-destructive way (clone first, swap if good, never restore in place blind). Then walk me through ACTUALLY DOING a test restore right now to a clone \u2014 not in theory, in practice \u2014 so I know it works before I need it. Also recommend an additional manual backup strategy (weekly export to object storage I control) so I'm not 100% dependent on my host. Output a one-page DISASTER_RECOVERY.md I can keep with my project.",
|
|
611
|
-
what: "An automatic, recent copy of your entire database stored somewhere safe \u2014 and the ability to restore from it without
|
|
623
|
+
what: "An automatic, recent copy of your entire database stored somewhere safe \u2014 and the ability to restore from it without guessing under pressure. Most managed databases (Neon, Supabase, Replit DB, RDS, PlanetScale) include some form of backup, but defaults vary, and 'a backup exists' is not the same thing as 'a backup that works.'",
|
|
612
624
|
why: "Sooner or later you, your AI builder, or a script will run the wrong query against the production database. Without a tested backup, your only options are 'rebuild from memory' and 'apologize to users in public.' With one, it's a 10-minute fix. The whole point of doing this BEFORE launch is that nobody is depending on the data yet \u2014 so testing the restore is free.",
|
|
613
625
|
steps: [
|
|
614
626
|
"Open your database's dashboard (Neon, Supabase, Replit, etc.) and find the Backups or Point-in-time Recovery section. Confirm automatic backups are on, and note how many days you can restore back.",
|
|
@@ -636,13 +648,14 @@ var ITEMS_CRITICAL_B = [
|
|
|
636
648
|
category: "Security",
|
|
637
649
|
priority: "critical",
|
|
638
650
|
timeEstimate: "2 hr+",
|
|
639
|
-
prompt: "Act as a backend security engineer. Go through every API endpoint in my app and check
|
|
651
|
+
prompt: "Act as a backend security engineer. Go through every API endpoint in my app and check four things for each: (1) authentication \u2014 is the user logged in? (2) authorization \u2014 is THIS user allowed to access THIS specific resource? (the IDOR check); (3) server-side validation \u2014 does the backend validate types, ranges, lengths, formats, and required fields before writing or calling paid APIs? (4) safe errors \u2014 does the route return generic user-facing errors while logging detailed errors server-side only? Pay especially close attention to endpoints that use IDs from the URL like /users/:id or /orders/:id \u2014 these are the most commonly broken. Output a table per endpoint: route, method, requires login? (Y/N), checks resource ownership? (Y/N), validates input server-side? (Y/N), leaks internal error detail? (Y/N), risk level. Then fix every endpoint that's missing checks. Add input validation (Zod, Yup, Valibot, Joi, or my framework's equivalent) to every endpoint \u2014 validate types, ranges, lengths, formats, and payload shape on the server, not only in React. Lock down CORS to my own production domain plus localhost/preview as needed \u2014 no wildcards in production. After fixes, give me 3 curl commands I can run to verify a logged-out user, a regular logged-in user, and another user's account all get blocked appropriately.",
|
|
640
652
|
what: "Your app has a frontend (what users see) and a backend (the URLs the frontend calls to load and save data \u2014 these are called API endpoints). If those backend URLs aren't checking who's asking and what they're allowed to do, anyone with browser developer tools can call them directly and do whatever they want.",
|
|
641
|
-
why: 'This is one of the most common silent disasters in
|
|
653
|
+
why: 'This is one of the most common silent disasters in AI-built apps: the frontend hides the "delete account" button from non-admins, but the backend lets anyone call /api/delete-account if they know the URL. The button is decoration; the backend check is the actual lock.',
|
|
642
654
|
steps: [
|
|
643
655
|
'Every endpoint that touches user data should check: "is this person logged in?"',
|
|
644
656
|
`Every endpoint that touches a specific user's data should also check: "is this person allowed to access THIS user's data?" This is the most commonly missed step.`,
|
|
645
|
-
"Validate every input on the backend \u2014 don't trust the frontend to send
|
|
657
|
+
"Validate every input on the backend \u2014 don't trust the frontend to send clean data. Client-side Zod is UX; server-side validation is security.",
|
|
658
|
+
"Return generic errors to users and log detailed errors server-side. Never hand an attacker table names, SQL, stack traces, or schema details.",
|
|
646
659
|
`Ask your AI builder: "go through every API endpoint in my project and tell me which ones don't check authentication or authorization, and fix them."`,
|
|
647
660
|
"Set up CORS so only your own frontend can call your backend."
|
|
648
661
|
],
|
|
@@ -650,6 +663,8 @@ var ITEMS_CRITICAL_B = [
|
|
|
650
663
|
"Admin functions you can call from a logged-out browser",
|
|
651
664
|
"You can change the user ID in a URL (/api/users/123 \u2192 /api/users/124) and read someone else's data",
|
|
652
665
|
"Your backend trusts whatever the frontend sends without re-checking it",
|
|
666
|
+
"Forms only validate in the browser, with no matching server-side schema",
|
|
667
|
+
"API responses expose SQL, table names, stack traces, ORM errors, or internal service URLs",
|
|
653
668
|
"CORS is wide open (Access-Control-Allow-Origin: *) on a non-public API"
|
|
654
669
|
],
|
|
655
670
|
cliCoverage: "automated"
|
|
@@ -687,12 +702,12 @@ var ITEMS_CRITICAL_B = [
|
|
|
687
702
|
category: "Product & Launch",
|
|
688
703
|
priority: "critical",
|
|
689
704
|
timeEstimate: "1 hr",
|
|
690
|
-
prompt: 'Act as a product engineer. Add /terms and /privacy pages to my app. IMPORTANT: do not invent legal language. Instead, generate a structured outline of every section that needs to exist in each page, customized to what my app actually does. Detect what to include by inspecting my dependencies and code \u2014 list every third-party service I integrate (Stripe, OpenAI, Google Analytics, Resend, Sentry, etc.) and note which ones need to be disclosed in the privacy policy. Output the outline as headings with a 1-sentence description of what each section should cover. Add a clear banner at the top of each page: "This is a starting outline. Get the actual legal language from a lawyer or a service like Termly, iubenda, or Termageddon." Then add the page routes, link them from the footer / signup / cookie banner, and include an "Effective date" field and a real contact email.',
|
|
705
|
+
prompt: 'Act as a product engineer. Add /terms and /privacy pages to my app. IMPORTANT: do not invent legal language. Instead, generate a structured outline of every section that needs to exist in each page, customized to what my app actually does. Detect what to include by inspecting my dependencies and code \u2014 list every third-party service I integrate (Stripe, OpenAI, Google Analytics, Resend, Sentry, Supabase, Firebase, Neon, Vercel, Cloudflare, etc.) and note which ones need to be disclosed in the privacy policy. Include a data-location section that points to the separate data-location inventory: where user data lives, what processors touch it, and which deletion/export path covers each one. Output the outline as headings with a 1-sentence description of what each section should cover. Add a clear banner at the top of each page: "This is a starting outline. Get the actual legal language from a lawyer or a service like Termly, iubenda, or Termageddon." Then add the page routes, link them from the footer / signup / cookie banner, and include an "Effective date" field and a real contact email.',
|
|
691
706
|
what: "Two pages most apps need: Terms of Service (the rules of using your app) and a Privacy Policy (what data you collect and what you do with it). They are legal documents \u2014 the words matter, and they have to actually describe what your app does.",
|
|
692
707
|
why: "These protect you from getting sued and protect your users from being misled. Most platforms (App Store, Google, Stripe, even Google sign-in) require them. Generated or copy-pasted policies that don't match your actual product are worse than nothing \u2014 they're evidence in a lawsuit.",
|
|
693
708
|
steps: [
|
|
694
709
|
"Do not have an AI write your final legal pages. Use a reputable template service (Termly, iubenda, Termageddon) or pay an actual attorney for a few hours.",
|
|
695
|
-
"Tailor whatever template you use to match what your app actually does \u2014 every third-party service you use (analytics, AI, payments, email) probably needs to be mentioned.",
|
|
710
|
+
"Tailor whatever template you use to match what your app actually does \u2014 every third-party service you use (analytics, AI, payments, database, auth, hosting, email) probably needs to be mentioned.",
|
|
696
711
|
"Create /terms and /privacy pages and link them from the footer, signup, and any place you collect data.",
|
|
697
712
|
"Include an effective date and a real way to contact you.",
|
|
698
713
|
"If you collect cookies or run analytics, add a cookie banner where required (especially in the EU and UK)."
|
|
@@ -702,7 +717,8 @@ var ITEMS_CRITICAL_B = [
|
|
|
702
717
|
"AI-generated policies that talk about features your app doesn't have",
|
|
703
718
|
"Pages copied from another company (with their company name still in there)",
|
|
704
719
|
"No effective date, no contact info",
|
|
705
|
-
"Your privacy policy doesn't mention services you actually use (Stripe, OpenAI, Google Analytics, etc.)"
|
|
720
|
+
"Your privacy policy doesn't mention services you actually use (Stripe, OpenAI, Google Analytics, etc.)",
|
|
721
|
+
"No one can say which provider or region stores user data"
|
|
706
722
|
],
|
|
707
723
|
cliCoverage: "automated"
|
|
708
724
|
},
|
|
@@ -714,8 +730,8 @@ var ITEMS_CRITICAL_B = [
|
|
|
714
730
|
priority: "critical",
|
|
715
731
|
timeEstimate: "2 hr+",
|
|
716
732
|
prompt: "Act as a privacy-aware product engineer. Add two flows to my app: (1) Account deletion \u2014 a button in account settings that, when confirmed (with a second-step modal), permanently deletes the user, all their personal data, and all their content, with a 7-day grace period during which the account can be recovered. After 7 days, the deletion is hard. Email confirmation when initiated and when finalized. (2) Data export \u2014 a button that emails the user a downloadable JSON or CSV of all their personal data and content within 24 hours. Audit my schema and tell me which tables/columns count as 'personal data' and need to be included. Make sure the deletion respects foreign-key constraints and removes data from any third party I forward to (Stripe customer, email provider, analytics). Don't break referential integrity for OTHER users (e.g., comments by deleted user become 'deleted user' instead of cascading). Output a short DATA_RIGHTS.md describing what's deleted and what's retained for legal reasons.",
|
|
717
|
-
what: "Two buttons in account settings: 'Download my data' and 'Delete my account.' One emails the user a copy of everything you have on them; the other actually removes them. Both are required almost everywhere personal data is regulated, and both are missing from almost every
|
|
718
|
-
why: "GDPR (EU), CCPA (California), and an expanding list of US state laws make these legally required if you have any users in those places \u2014 which you will, because the internet is global. Beyond legal: it's the right thing, it builds trust, and it costs basically nothing to add now versus a
|
|
733
|
+
what: "Two buttons in account settings: 'Download my data' and 'Delete my account.' One emails the user a copy of everything you have on them; the other actually removes them. Both are required almost everywhere personal data is regulated, and both are missing from almost every AI-built app.",
|
|
734
|
+
why: "GDPR (EU), CCPA (California), and an expanding list of US state laws make these legally required if you have any users in those places \u2014 which you will, because the internet is global. Beyond legal: it's the right thing, it builds trust, and it costs basically nothing to add now versus a rushed weekend later when someone files a complaint.",
|
|
719
735
|
steps: [
|
|
720
736
|
"Add a 'Delete my account' button in account settings. Require a second confirmation step ('type DELETE to confirm') so it's not accidental.",
|
|
721
737
|
"Implement a 7-day grace period: the account is disabled immediately, deleted permanently after 7 days. Email the user when each happens.",
|
|
@@ -769,7 +785,7 @@ var ITEMS_CRITICAL_B = [
|
|
|
769
785
|
timeEstimate: "2 hr+",
|
|
770
786
|
prompt: "Act as a payments engineer. Audit my app's entire paid flow before launch. First, identify whether I use Stripe, Lemon Squeezy, Paddle, RevenueCat, app-store payments, or something else. Then verify and fix: (1) checkout can be created only by the server, never with a client-supplied price or product; (2) the success page does NOT unlock paid access just because the URL says success; it checks the backend for a paid order; (3) webhooks verify the provider signature and persist paid status, receipt ID, amount, currency, and customer email; (4) every paid product, subscription, upgrade, cancellation, refund, and failed-payment state has a real user-facing path; (5) test-mode checkout, webhook delivery, receipt email, refund, and cancellation all work end-to-end. Output exact test-card steps, webhook setup steps, env vars, and the one thing I should click in the provider dashboard to prove money would actually move.",
|
|
771
787
|
what: "If your app charges money, the payment flow is not 'done' when the checkout button opens. It is done when payment succeeds, your backend hears the signed webhook, paid access unlocks, the receipt lands, and cancellation/refund paths do not strand the user.",
|
|
772
|
-
why: "This is where
|
|
788
|
+
why: "This is where AI-built apps embarrass themselves fast. A team launches, someone pays, the success page lies, the webhook never arrives, and now the first customer is both confused and charged. Or worse: a user edits a client-side price and buys the expensive thing for $0. Payments need a real dry run before launch day.",
|
|
773
789
|
steps: [
|
|
774
790
|
"Run a full test-mode checkout from a logged-out or brand-new account. Pay with the provider's test card and confirm the success page shows the right purchased thing.",
|
|
775
791
|
"Verify paid access comes from your backend's recorded payment state, not from a success URL, localStorage flag, or client-side boolean.",
|
|
@@ -838,6 +854,114 @@ var ITEMS_CRITICAL_B = [
|
|
|
838
854
|
],
|
|
839
855
|
cliCoverage: "manual_only",
|
|
840
856
|
whyManual: "AI guardrails need scenario testing against real prompts, tool permissions, data boundaries, and destructive-action confirmations. Static code cannot prove the model behaves safely under adversarial input."
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
id: "database-access-rules",
|
|
860
|
+
number: 19,
|
|
861
|
+
title: "Prove every user can only read their own rows",
|
|
862
|
+
category: "Security",
|
|
863
|
+
priority: "critical",
|
|
864
|
+
timeEstimate: "2 hr+",
|
|
865
|
+
prompt: "Act as a database security engineer. Audit every table, collection, bucket, and document store that contains user data. First identify the backend: Supabase/Postgres, Firebase/Firestore, MongoDB, Prisma/Drizzle/SQL, object storage, or something else. Then verify the real access boundary: (1) Supabase/Postgres: Row Level Security is enabled on every user-scoped table and policies exist for select/insert/update/delete; service-role keys never reach the browser; policies are tested with two different users. (2) Firebase/Firestore: security rules deny by default and explicitly check auth.uid/resource ownership; rules are tested in the emulator. (3) SQL/ORM: every query that reads user data filters by the authenticated user or permission model server-side. (4) Storage: private files live in private buckets with signed URLs and ownership checks. Output a table: data surface, user-scoped? rule/policy present? cross-user test passed? risk. Then write the exact SQL/rules/tests needed to close gaps.",
|
|
866
|
+
what: "Database access rules are the lock underneath your UI. Your frontend can hide another user's records, but the database or API still has to refuse the request when someone changes an ID, opens DevTools, or calls the endpoint directly.",
|
|
867
|
+
why: "This is the classic AI-built app data breach: the app looks private, but Supabase RLS is off, Firebase rules allow broad reads, or the server query forgets `where user_id = currentUser.id`. That is not a hack; it is an unlocked door.",
|
|
868
|
+
steps: [
|
|
869
|
+
"List every user-scoped table, collection, storage bucket, and API route that returns saved user data.",
|
|
870
|
+
"For Supabase, enable RLS on every user-scoped table and write policies for select, insert, update, and delete. Do not ship with zero policies.",
|
|
871
|
+
"For Firebase/Firestore, make rules deny by default and allow reads/writes only when auth.uid owns the document or has an explicit role.",
|
|
872
|
+
"Test with two real test users: create data as User A, log in as User B, and try to read, edit, or delete User A's records through the app, the API, and any SDK/browser console path.",
|
|
873
|
+
"Keep privileged service-role/admin keys server-side only. If a browser bundle can use the key to bypass rules, the rules do not matter."
|
|
874
|
+
],
|
|
875
|
+
redFlags: [
|
|
876
|
+
"Supabase table shows RLS disabled or zero policies",
|
|
877
|
+
"Firestore rules include broad allow read/write statements",
|
|
878
|
+
"The frontend hides records but backend/API queries do not check ownership",
|
|
879
|
+
"Service-role/admin/database credentials are present in browser code",
|
|
880
|
+
"No cross-user test has ever been run with two separate accounts"
|
|
881
|
+
],
|
|
882
|
+
cliCoverage: "manual_only",
|
|
883
|
+
whyManual: "Database access rules must be proven against the real provider and two real users. Static code can spot hints, but it cannot prove Supabase RLS, Firebase rules, storage policies, and cross-user ownership tests all work in the deployed environment."
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
id: "auth-failure-cases",
|
|
887
|
+
number: 20,
|
|
888
|
+
title: "Test the auth failures attackers try first",
|
|
889
|
+
category: "Security",
|
|
890
|
+
priority: "critical",
|
|
891
|
+
timeEstimate: "30 min",
|
|
892
|
+
prompt: "Act as a QA/security engineer. Run the auth failure-case drill before launch, using production-similar auth settings and throwaway test accounts. Test: (1) wrong password or wrong OTP 5-6 times in a row \u2014 confirm rate limiting/lockout and generic copy; (2) password reset or magic-link start for an email that does not exist \u2014 confirm it looks the same as a real email path and does not reveal account existence; (3) verification or magic link clicked twice, opened after expiry, and opened on a different device \u2014 confirm it lands in a safe state with a recovery path; (4) signup with an already registered email or phone \u2014 confirm it does not leak account state beyond the provider's safe default; (5) login from mobile and desktop \u2014 confirm session/cookie behavior is correct. Output pass/fail evidence, screenshots or curl commands where useful, and the exact copy users see.",
|
|
893
|
+
what: "Happy-path auth testing proves almost nothing. The dangerous bugs are in the weird edges: wrong codes, fake emails, repeated attempts, duplicate signup, expired links, and users opening links on a different device.",
|
|
894
|
+
why: "Attackers probe the failure paths first because they reveal whether an account exists, whether a code can be brute-forced, and whether a user can get locked out. Paid users hit the same paths accidentally on launch day.",
|
|
895
|
+
steps: [
|
|
896
|
+
"Try the wrong password, magic code, or OTP 5-6 times. Confirm a real limit kicks in and the message stays generic.",
|
|
897
|
+
"Start password reset or magic-link login for an email that does not exist. The response should look like success, not 'account not found.'",
|
|
898
|
+
"Click the same verification/magic link twice and after expiry. It should recover gracefully, not show a stack trace or reveal internals.",
|
|
899
|
+
"Sign up with an email or phone that already exists. Confirm the provider's safe default copy and rate limits are used.",
|
|
900
|
+
"Record the exact pass/fail evidence in the launch report so nobody hand-waves auth as 'probably fine.'"
|
|
901
|
+
],
|
|
902
|
+
redFlags: [
|
|
903
|
+
"Wrong password or OTP can be tried indefinitely",
|
|
904
|
+
"Password reset says whether an email exists",
|
|
905
|
+
"Verification links crash or leak internals when reused",
|
|
906
|
+
"Signup copy reveals registered users in a way attackers can enumerate",
|
|
907
|
+
"No one has tested auth failure states outside the happy path"
|
|
908
|
+
],
|
|
909
|
+
cliCoverage: "manual_only",
|
|
910
|
+
whyManual: "Auth failure behavior requires a live auth provider, delivered emails/SMS, real browser sessions, and throwaway accounts. The scanner can flag code signals, but it cannot honestly click links, trigger provider lockouts, or verify copy in every failure state."
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
id: "data-location-inventory",
|
|
914
|
+
number: 21,
|
|
915
|
+
title: "Know where every piece of user data lives",
|
|
916
|
+
category: "Product & Launch",
|
|
917
|
+
priority: "critical",
|
|
918
|
+
timeEstimate: "1 hr",
|
|
919
|
+
prompt: "Act as a privacy and infrastructure operator. Build a data-location inventory for this app before launch. Inspect the code, dependencies, env var names, docs, provider dashboards, and signup/payment/email/auth flows. Output a table with: data type collected, source form/route, primary store, provider, region/location if available, third parties that receive it, retention period, deletion/export path, and policy disclosure needed. Include email providers, auth providers, analytics, error monitoring, payment processors, AI providers, file storage, databases, logs, and support tools. If a provider dashboard is needed to confirm region, say exactly which dashboard page must be checked. Do not invent regions \u2014 mark unknown until verified.",
|
|
920
|
+
what: "The moment you collect an email, payment, file, prompt, or user profile, you need to know where it goes. 'It is somewhere in Supabase' or 'Vercel handles it' is not enough when a user asks for deletion or a platform asks for your privacy details.",
|
|
921
|
+
why: "Legal pages only protect you if they match reality. If your app sends user data to Stripe, OpenAI, Resend, Sentry, PostHog, Supabase, Firebase, Neon, or a support inbox, those processors and regions have to be known before launch.",
|
|
922
|
+
steps: [
|
|
923
|
+
"List every data collection point: signup, checkout, waitlist, contact form, uploaded file, AI prompt, analytics event, error log, support request.",
|
|
924
|
+
"For each one, write where it is stored first, which third parties receive it, and how long it is kept.",
|
|
925
|
+
"Open provider dashboards and record region/location where available: database, hosting, auth, email, analytics, AI, payments, storage, logs.",
|
|
926
|
+
"Tie every data store to a deletion/export path so account deletion is not just deleting one database row.",
|
|
927
|
+
"Update the privacy policy outline so it names the real categories of data, processors, retention, and deletion/export contact."
|
|
928
|
+
],
|
|
929
|
+
redFlags: [
|
|
930
|
+
'You cannot answer "where does a new user email go?"',
|
|
931
|
+
"Privacy policy names fewer processors than the code actually uses",
|
|
932
|
+
"No one knows the database/auth/storage region",
|
|
933
|
+
"Error monitoring or analytics receives personal data without disclosure",
|
|
934
|
+
"Account deletion only touches the app database and ignores Stripe, email, analytics, logs, or support tools"
|
|
935
|
+
],
|
|
936
|
+
cliCoverage: "manual_only",
|
|
937
|
+
whyManual: "Data location depends on live provider dashboard settings, regions, processors, and retention choices. Static repo scanning can infer likely services, but it cannot prove where provider-held data physically lives or whether privacy disclosures match reality."
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
id: "public-form-abuse",
|
|
941
|
+
number: 22,
|
|
942
|
+
title: "Block bots from public forms before launch",
|
|
943
|
+
category: "Security",
|
|
944
|
+
priority: "critical",
|
|
945
|
+
timeEstimate: "1 hr",
|
|
946
|
+
prompt: "Act as an abuse-prevention engineer. Find every public form or unauthenticated write surface: signup, login/OTP send, password reset, waitlist, contact, feedback, newsletter, invite request, file upload, AI demo, support, and checkout-start. For each one, decide whether it needs CAPTCHA/Turnstile, rate limiting, email/phone verification, honeypot fields, server-side validation, or all of the above. Prefer Cloudflare Turnstile for public marketing/contact/waitlist forms because it is free and privacy-friendly. Add the provider secret server-side only, verify tokens on the backend before accepting the submission, keep localhost/dev bypasses development-only, and add abuse logging. Then test with a missing token, invalid token, repeated submissions, and a real browser pass.",
|
|
947
|
+
what: "Public forms are doors bots can walk through without an account. If they can submit freely, they will fill your waitlist with spam, burn SMS/email credits, scrape paid APIs, or bury real leads under junk.",
|
|
948
|
+
why: "A polished launch can still get wrecked by a public form with no bot guard. The app looks fine until the first traffic spike turns into 500 spam contacts, SMS pumping, or an AI endpoint bill.",
|
|
949
|
+
steps: [
|
|
950
|
+
"Inventory every unauthenticated form or POST route, not just the contact form.",
|
|
951
|
+
"Put CAPTCHA or Cloudflare Turnstile on public marketing/contact/waitlist/feedback forms that accept arbitrary submissions.",
|
|
952
|
+
"Keep rate limits on every public write endpoint even when CAPTCHA is present. CAPTCHA slows bots; rate limits cap damage.",
|
|
953
|
+
"Verify bot tokens server-side before writing to the database, sending email/SMS, or calling a paid API.",
|
|
954
|
+
"Test missing-token, invalid-token, repeated-submit, and real-browser success paths before launch."
|
|
955
|
+
],
|
|
956
|
+
redFlags: [
|
|
957
|
+
"Contact, waitlist, feedback, signup, or OTP forms accept unlimited public submissions",
|
|
958
|
+
"CAPTCHA token is checked only in the frontend",
|
|
959
|
+
"CAPTCHA is present but the endpoint still writes data when the token is missing",
|
|
960
|
+
"No rate limit backs up the CAPTCHA",
|
|
961
|
+
"Development/test bypass can run in production"
|
|
962
|
+
],
|
|
963
|
+
cliCoverage: "manual_only",
|
|
964
|
+
whyManual: "Bot protection has to be verified against live public forms and provider token validation. The scanner can look for rate-limit or CAPTCHA libraries, but it cannot prove every unauthenticated form rejects missing tokens and survives repeated submissions."
|
|
841
965
|
}
|
|
842
966
|
];
|
|
843
967
|
|
|
@@ -851,7 +975,7 @@ var ITEMS_CRITICAL = [
|
|
|
851
975
|
var ITEMS_HIGH = [
|
|
852
976
|
{
|
|
853
977
|
id: "error-monitoring",
|
|
854
|
-
number:
|
|
978
|
+
number: 23,
|
|
855
979
|
title: "Get notified the moment something breaks",
|
|
856
980
|
category: "Operations",
|
|
857
981
|
priority: "high",
|
|
@@ -876,7 +1000,7 @@ var ITEMS_HIGH = [
|
|
|
876
1000
|
},
|
|
877
1001
|
{
|
|
878
1002
|
id: "uptime-monitoring",
|
|
879
|
-
number:
|
|
1003
|
+
number: 24,
|
|
880
1004
|
title: "Get pinged the moment your app goes completely down",
|
|
881
1005
|
category: "Operations",
|
|
882
1006
|
priority: "high",
|
|
@@ -903,17 +1027,18 @@ var ITEMS_HIGH = [
|
|
|
903
1027
|
},
|
|
904
1028
|
{
|
|
905
1029
|
id: "rate-limiting",
|
|
906
|
-
number:
|
|
1030
|
+
number: 25,
|
|
907
1031
|
title: "Cap how often someone can hit your app",
|
|
908
1032
|
category: "Security",
|
|
909
1033
|
priority: "high",
|
|
910
1034
|
timeEstimate: "1 hr",
|
|
911
|
-
prompt: "Act as a backend security engineer. Add rate limiting to every public-facing endpoint. Use a sensible default (60 req/min/IP) for
|
|
1035
|
+
prompt: "Act as a backend security engineer. Add rate limiting to every public-facing endpoint. Use a sensible default (60 req/min/IP) for normal public reads, around 100 req/min/IP for low-risk public utility endpoints, and tighter limits for sensitive ones: login (5 per 15 min per IP+email), signup (3 per hour per IP), password reset (3 per hour per email), OTP send (3-5 per hour per identifier), public forms (strict enough to stop spam), and AI/expensive endpoints (whatever fits a daily budget \u2014 ask me what I'm willing to spend per day). Return a clean 429 response with a Retry-After header and a friendly JSON message. Log every rate-limit hit with IP and route so I can see attacks. Use my framework's recommended middleware (express-rate-limit, hono-rate-limit, @upstash/ratelimit, Arcjet, etc.). Recommend in-memory vs Redis/Upstash backing based on whether I'm running multiple instances. After you're done, give me a curl one-liner I can run to verify it actually blocks me after N attempts.",
|
|
912
1036
|
what: "Rate limiting puts a maximum on how many requests one person (or one IP address) can make in a given window \u2014 say, 60 requests a minute. Without it, a single bot can hammer your backend until it falls over, run up your AI bill, or brute-force passwords until something works.",
|
|
913
1037
|
why: "Without rate limits you are one bad actor away from a $10,000 OpenAI bill, a crashed server, or a leaked password. Rate limits are cheap to add and save you from a long list of nightmares.",
|
|
914
1038
|
steps: [
|
|
915
1039
|
"Add rate limiting to every public-facing endpoint.",
|
|
916
1040
|
"Use stricter limits on the sensitive ones: login (5 per 15 minutes), signup (3 per hour), password reset (3 per hour), AI calls (whatever fits your budget).",
|
|
1041
|
+
"Use a shared backing store such as Redis/Upstash when the app runs more than one instance. In-memory limits reset per instance.",
|
|
917
1042
|
`Return a clear "you're going too fast, try again in X seconds" message \u2014 don't just silently fail.`,
|
|
918
1043
|
"Watch for repeated rate-limit hits \u2014 they're usually attacks. Have alerts set up for spikes.",
|
|
919
1044
|
'Ask your AI builder: "add per-IP rate limiting to all my endpoints with stricter limits on login, signup, and password reset."'
|
|
@@ -922,6 +1047,7 @@ var ITEMS_HIGH = [
|
|
|
922
1047
|
"No rate limits at all",
|
|
923
1048
|
"Same limits everywhere (login should be way stricter than browsing)",
|
|
924
1049
|
"Auth endpoints (login, signup, password reset) not strictly capped \u2014 brute-force and credential-stuffing bots will find you",
|
|
1050
|
+
"Rate limits are in-memory even though production runs multiple instances",
|
|
925
1051
|
"No alerting when someone repeatedly hits the limit",
|
|
926
1052
|
"You can't see how often this is happening"
|
|
927
1053
|
],
|
|
@@ -929,12 +1055,12 @@ var ITEMS_HIGH = [
|
|
|
929
1055
|
},
|
|
930
1056
|
{
|
|
931
1057
|
id: "dependency-audit",
|
|
932
|
-
number:
|
|
1058
|
+
number: 26,
|
|
933
1059
|
title: "Patch your dependencies for known vulnerabilities",
|
|
934
1060
|
category: "Security",
|
|
935
1061
|
priority: "high",
|
|
936
1062
|
timeEstimate: "30 min",
|
|
937
|
-
prompt: "Act as a senior security engineer. Audit my project's installed dependencies for known vulnerabilities AND for supply-chain hygiene, and apply fixes. Tasks: (1) run the right auditor for my package manager \u2014 `npm audit` or `pnpm audit` for Node, `pip-audit` for Python, `bundle audit` for Ruby, `cargo audit` for Rust \u2014 and report the severity counts (critical / high / moderate / low). (2) for every Critical or High finding, try the auto-fix first (`npm audit fix`); if that doesn't clear it, upgrade the offending package manually and test the app after each change. Don't blindly `--force`. (3) if clearing a CVE needs a major-version bump that would break my app, document the CVE + the blocked upgrade + my exposure, then add a temporary mitigation if one's possible. (4) supply-chain hygiene \u2014 for every direct dependency, look up weekly download count and most-recent release date on the registry. Flag any dep with <1k weekly downloads (could be a typosquat \u2014 check the name against the obvious legitimate package) and any dep with no release in 12+ months (unmaintained \u2014 plan a replacement). (
|
|
1063
|
+
prompt: "Act as a senior security engineer. Audit my project's installed dependencies for known vulnerabilities AND for supply-chain hygiene, and apply fixes. Tasks: (1) run the right auditor for my package manager \u2014 `npm audit` or `pnpm audit` for Node, `pip-audit` for Python, `bundle audit` for Ruby, `cargo audit` for Rust \u2014 and report the severity counts (critical / high / moderate / low). (2) for every Critical or High finding, try the auto-fix first (`npm audit fix`); if that doesn't clear it, upgrade the offending package manually and test the app after each change. Don't blindly `--force`. (3) if clearing a CVE needs a major-version bump that would break my app, document the CVE + the blocked upgrade + my exposure, then add a temporary mitigation if one's possible. (4) run or configure the builder/platform's built-in dependency/security scanner too, then compare its findings against the package-manager audit so nothing is silently ignored. (5) supply-chain hygiene \u2014 for every direct dependency, look up weekly download count and most-recent release date on the registry. Flag any dep with <1k weekly downloads (could be a typosquat \u2014 check the name against the obvious legitimate package) and any dep with no release in 12+ months (unmaintained \u2014 plan a replacement). (6) turn on automated dependency updates so this doesn't rot: Dependabot (free on GitHub) or Renovate \u2014 PR weekly for minor/patch, prompt on major. (7) add an audit step to my CI pipeline that fails the build on any new Critical CVE. When you're done, tell me plainly: any CVEs that made it to prod, any I deferred (with reasoning), any suspicious or abandoned deps I should swap, and the auto-update cadence you configured.",
|
|
938
1064
|
what: "Your app pulls in hundreds of third-party packages via `npm install` (or pip, bundle, cargo). Some of those packages have known security bugs with public write-ups and working exploits. Auditing means running one command to list every known bug in your dependencies, then upgrading to the fixed versions.",
|
|
939
1065
|
why: "Most successful attacks on small apps aren't clever \u2014 they're automated scanners finding sites that ship an old version of a popular library with a published CVE. The fix is usually a one-command upgrade. Skipping this is handing attackers the easiest version of your app.",
|
|
940
1066
|
steps: [
|
|
@@ -942,12 +1068,14 @@ var ITEMS_HIGH = [
|
|
|
942
1068
|
"For every Critical or High finding, try the auto-fix first (e.g. `npm audit fix`). Test that the app still works after each fix.",
|
|
943
1069
|
"For fixes that require a major-version bump, read the package's upgrade notes before updating \u2014 breaking changes are real.",
|
|
944
1070
|
"If a CVE has no fix yet, at least know you have it. Document it and subscribe to the package's security advisories.",
|
|
1071
|
+
"Check your builder or hosting scanner output too. If Cursor, Claude Code, Lovable, Replit, GitHub, Snyk, or Semgrep flags dependency risk, reconcile it with the package audit.",
|
|
945
1072
|
"Turn on Dependabot or Renovate in your GitHub repo so it opens PRs as new versions ship \u2014 you're not manually checking anymore.",
|
|
946
1073
|
"Add the audit command to your CI pipeline so shipping a new Critical CVE breaks the build."
|
|
947
1074
|
],
|
|
948
1075
|
redFlags: [
|
|
949
1076
|
"You have never run `npm audit` (or your language's equivalent) on this project",
|
|
950
1077
|
"The audit shows Critical severity \u2014 and you shipped anyway",
|
|
1078
|
+
"Your builder or GitHub security tab shows warnings you have not read",
|
|
951
1079
|
"Dependabot / Renovate is not turned on",
|
|
952
1080
|
"Your lockfile hasn't been touched in more than 6 months",
|
|
953
1081
|
"CI never fails on a new CVE \u2014 you'll only find out from a bug report or a breach"
|
|
@@ -957,7 +1085,7 @@ var ITEMS_HIGH = [
|
|
|
957
1085
|
},
|
|
958
1086
|
{
|
|
959
1087
|
id: "logging",
|
|
960
|
-
number:
|
|
1088
|
+
number: 27,
|
|
961
1089
|
title: "Keep a paper trail of what your app is doing",
|
|
962
1090
|
category: "Operations",
|
|
963
1091
|
priority: "high",
|
|
@@ -983,7 +1111,7 @@ var ITEMS_HIGH = [
|
|
|
983
1111
|
},
|
|
984
1112
|
{
|
|
985
1113
|
id: "session-management",
|
|
986
|
-
number:
|
|
1114
|
+
number: 28,
|
|
987
1115
|
title: "Make sessions feel safe AND convenient",
|
|
988
1116
|
category: "Security",
|
|
989
1117
|
priority: "high",
|
|
@@ -1008,7 +1136,7 @@ var ITEMS_HIGH = [
|
|
|
1008
1136
|
},
|
|
1009
1137
|
{
|
|
1010
1138
|
id: "github",
|
|
1011
|
-
number:
|
|
1139
|
+
number: 29,
|
|
1012
1140
|
title: "Connect to GitHub for backups and history",
|
|
1013
1141
|
category: "Infrastructure",
|
|
1014
1142
|
priority: "high",
|
|
@@ -1033,19 +1161,19 @@ var ITEMS_HIGH = [
|
|
|
1033
1161
|
},
|
|
1034
1162
|
{
|
|
1035
1163
|
id: "rollback",
|
|
1036
|
-
number:
|
|
1164
|
+
number: 30,
|
|
1037
1165
|
title: "Know how to roll back a bad deploy in under a minute",
|
|
1038
1166
|
category: "Operations",
|
|
1039
1167
|
priority: "high",
|
|
1040
1168
|
timeEstimate: "15 min",
|
|
1041
1169
|
prompt: "Act as a deployment engineer. For my specific hosting platform, walk me through the EXACT click-by-click procedure to roll back to the previous deploy. Then have me actually do it once \u2014 to a previous commit, then forward again \u2014 so I know the muscle memory before I need it. Also: (1) tell me what's preserved during a rollback (env variables, secrets, data) and what isn't (any DB migration that ran on the bad deploy is NOT undone \u2014 call this out); (2) recommend whether I should enable preview deployments on every PR/branch so I can test changes before they hit production; (3) give me a 5-line emergency runbook (ROLLBACK.md) I can paste into my repo: 'If production is broken, do these 3 things in this order.'",
|
|
1042
|
-
what: "The ability to undo a deployment in 30 seconds and get back to the last known good version. Almost every modern host
|
|
1043
|
-
why: "You will ship something broken to production. Your AI builder will help you 'fix' something at midnight and the fix will be worse. The difference between a 30-second outage and a 3-hour
|
|
1170
|
+
what: "The ability to undo a deployment in 30 seconds and get back to the last known good version. Almost every modern host supports one-click rollback to any prior version \u2014 but you need to know where the button is BEFORE the bad deploy.",
|
|
1171
|
+
why: "You will ship something broken to production. Your AI builder will help you 'fix' something at midnight and the fix will be worse. The difference between a 30-second outage and a 3-hour incident is whether you've practiced rolling back once when nothing was wrong.",
|
|
1044
1172
|
steps: [
|
|
1045
1173
|
"Find the Deployments or Releases panel in your hosting platform (most have 'Promote to production' or 'Rollback' next to each version).",
|
|
1046
1174
|
"Do a practice rollback NOW, while everything is fine. Roll back one version, confirm the site still works, then roll forward again. You want this in muscle memory.",
|
|
1047
1175
|
"Understand what rollback does NOT undo: any database migration that ran on the bad version is still applied \u2014 your code is rolled back but your schema isn't. Plan accordingly (migrations should be backwards compatible).",
|
|
1048
|
-
"Turn on preview deployments
|
|
1176
|
+
"Turn on preview deployments so every change gets a temporary URL you can test before it touches production.",
|
|
1049
1177
|
"Write a 5-line ROLLBACK.md in your repo: '1. Open hosting dashboard. 2. Find latest known-good version. 3. Click Rollback. 4. Verify site works. 5. Tell users in status page / Twitter what happened.'"
|
|
1050
1178
|
],
|
|
1051
1179
|
redFlags: [
|
|
@@ -1053,14 +1181,14 @@ var ITEMS_HIGH = [
|
|
|
1053
1181
|
"You've never actually performed a rollback even once",
|
|
1054
1182
|
"All your changes go straight to production without a preview deployment",
|
|
1055
1183
|
"Database migrations run automatically and are not reversible",
|
|
1056
|
-
"No written runbook \u2014 at 2am you'll be improvising
|
|
1184
|
+
"No written runbook \u2014 at 2am you'll be improvising under pressure"
|
|
1057
1185
|
],
|
|
1058
1186
|
cliCoverage: "manual_only",
|
|
1059
1187
|
whyManual: "Rollback readiness is muscle memory in the hosting dashboard plus a real recovery path for data migrations. The scanner cannot prove you practiced rollback and forward again."
|
|
1060
1188
|
},
|
|
1061
1189
|
{
|
|
1062
1190
|
id: "soft-launch",
|
|
1063
|
-
number:
|
|
1191
|
+
number: 31,
|
|
1064
1192
|
title: "Soft-launch to 5 friends before you launch publicly",
|
|
1065
1193
|
category: "Product & Launch",
|
|
1066
1194
|
priority: "high",
|
|
@@ -1087,7 +1215,7 @@ var ITEMS_HIGH = [
|
|
|
1087
1215
|
},
|
|
1088
1216
|
{
|
|
1089
1217
|
id: "interviews",
|
|
1090
|
-
number:
|
|
1218
|
+
number: 32,
|
|
1091
1219
|
title: "Talk to your first 10 users on a real call",
|
|
1092
1220
|
category: "Growth",
|
|
1093
1221
|
priority: "high",
|
|
@@ -1114,7 +1242,7 @@ var ITEMS_HIGH = [
|
|
|
1114
1242
|
},
|
|
1115
1243
|
{
|
|
1116
1244
|
id: "onboarding",
|
|
1117
|
-
number:
|
|
1245
|
+
number: 33,
|
|
1118
1246
|
title: "Make the first 5 minutes obvious",
|
|
1119
1247
|
category: "Product & Launch",
|
|
1120
1248
|
priority: "high",
|
|
@@ -1145,7 +1273,7 @@ var ITEMS_HIGH = [
|
|
|
1145
1273
|
var ITEMS_MEDIUM = [
|
|
1146
1274
|
{
|
|
1147
1275
|
id: "analytics",
|
|
1148
|
-
number:
|
|
1276
|
+
number: 34,
|
|
1149
1277
|
title: "Set up basic usage tracking",
|
|
1150
1278
|
category: "Operations",
|
|
1151
1279
|
priority: "medium",
|
|
@@ -1171,7 +1299,7 @@ var ITEMS_MEDIUM = [
|
|
|
1171
1299
|
},
|
|
1172
1300
|
{
|
|
1173
1301
|
id: "db-performance",
|
|
1174
|
-
number:
|
|
1302
|
+
number: 35,
|
|
1175
1303
|
title: "Make your database queries fast",
|
|
1176
1304
|
category: "Infrastructure",
|
|
1177
1305
|
priority: "medium",
|
|
@@ -1197,7 +1325,7 @@ var ITEMS_MEDIUM = [
|
|
|
1197
1325
|
},
|
|
1198
1326
|
{
|
|
1199
1327
|
id: "custom-domain",
|
|
1200
|
-
number:
|
|
1328
|
+
number: 36,
|
|
1201
1329
|
title: "Get your own domain (yourname.com)",
|
|
1202
1330
|
category: "Infrastructure",
|
|
1203
1331
|
priority: "medium",
|
|
@@ -1229,7 +1357,7 @@ var ITEMS_MEDIUM = [
|
|
|
1229
1357
|
},
|
|
1230
1358
|
{
|
|
1231
1359
|
id: "email",
|
|
1232
|
-
number:
|
|
1360
|
+
number: 37,
|
|
1233
1361
|
title: "Make app emails actually arrive",
|
|
1234
1362
|
category: "Infrastructure",
|
|
1235
1363
|
priority: "medium",
|
|
@@ -1257,7 +1385,7 @@ var ITEMS_MEDIUM = [
|
|
|
1257
1385
|
},
|
|
1258
1386
|
{
|
|
1259
1387
|
id: "mobile",
|
|
1260
|
-
number:
|
|
1388
|
+
number: 38,
|
|
1261
1389
|
title: "Make it not suck on phones",
|
|
1262
1390
|
category: "Product & Launch",
|
|
1263
1391
|
priority: "medium",
|
|
@@ -1284,7 +1412,7 @@ var ITEMS_MEDIUM = [
|
|
|
1284
1412
|
},
|
|
1285
1413
|
{
|
|
1286
1414
|
id: "performance",
|
|
1287
|
-
number:
|
|
1415
|
+
number: 39,
|
|
1288
1416
|
title: "Make it fast",
|
|
1289
1417
|
category: "Product & Launch",
|
|
1290
1418
|
priority: "medium",
|
|
@@ -1310,7 +1438,7 @@ var ITEMS_MEDIUM = [
|
|
|
1310
1438
|
},
|
|
1311
1439
|
{
|
|
1312
1440
|
id: "accessibility",
|
|
1313
|
-
number:
|
|
1441
|
+
number: 40,
|
|
1314
1442
|
title: "Make it usable for everyone, including people with disabilities",
|
|
1315
1443
|
category: "Product & Launch",
|
|
1316
1444
|
priority: "medium",
|
|
@@ -1337,12 +1465,12 @@ var ITEMS_MEDIUM = [
|
|
|
1337
1465
|
},
|
|
1338
1466
|
{
|
|
1339
1467
|
id: "seo",
|
|
1340
|
-
number:
|
|
1468
|
+
number: 41,
|
|
1341
1469
|
title: "Set up the basics so search and social shares work",
|
|
1342
1470
|
category: "Growth",
|
|
1343
1471
|
priority: "medium",
|
|
1344
1472
|
timeEstimate: "1 hr",
|
|
1345
|
-
prompt: "Act as a technical SEO engineer. Add complete meta tags to every page. For each route, generate a unique <title> (\u226460 chars, includes brand) and meta description (\u2264160 chars, action-oriented and not stuffed with keywords). Add full Open Graph tags (og:title, og:description, og:image with absolute URL, og:url, og:type, og:site_name) and Twitter Card tags (summary_large_image variant with the same image). Add a canonical link per page. If my framework supports it (Next, Remix, Nuxt, SvelteKit, etc.), use the framework's metadata API; otherwise inject into <head>. Generate /sitemap.xml and /robots.txt using the real production domain (ask me what it is). When done, give me 3 URLs from my site I can paste into opengraph.xyz to verify previews look right.",
|
|
1473
|
+
prompt: "Act as a technical SEO engineer. Add complete meta tags to every page. For each route, generate a unique <title> (\u226460 chars, includes brand) and meta description (\u2264160 chars, action-oriented and not stuffed with keywords). Add full Open Graph tags (og:title, og:description, og:image with absolute URL, og:url, og:type, og:site_name) and Twitter Card tags (summary_large_image variant with the same image). Add a canonical link per page. If my framework supports it (Next, Remix, Nuxt, SvelteKit, etc.), use the framework's metadata API; otherwise inject into <head>. Generate /sitemap.xml and /robots.txt using the real production domain (ask me what it is). Do not claim Google has indexed the site from this work alone; Search Console submission and URL Inspection are a separate launch closeout step. When done, give me 3 URLs from my site I can paste into opengraph.xyz to verify previews look right.",
|
|
1346
1474
|
what: "Tiny bits of metadata in your HTML that tell Google what each page is about, and tell Twitter, LinkedIn, Slack, and iMessage how to display a preview when someone shares your link.",
|
|
1347
1475
|
why: "These are your free billboards. A page with no title gets no clicks in Google. A link in iMessage with no preview image looks broken or sketchy. You only have to do this once per page.",
|
|
1348
1476
|
steps: [
|
|
@@ -1350,6 +1478,7 @@ var ITEMS_MEDIUM = [
|
|
|
1350
1478
|
"Add a unique meta description (under 160 characters) \u2014 this is what shows under your link in Google.",
|
|
1351
1479
|
"Add Open Graph tags (og:title, og:description, og:image, og:url) \u2014 these power link previews in iMessage, Slack, LinkedIn.",
|
|
1352
1480
|
"Add Twitter Card tags (twitter:card = summary_large_image plus title/description/image) for Twitter/X previews.",
|
|
1481
|
+
"Make sure robots.txt points at the real production sitemap and important pages are not accidentally noindexed.",
|
|
1353
1482
|
"Test how your link looks BEFORE you announce it: opengraph.xyz, metatags.io, or just paste it into a Slack channel and see."
|
|
1354
1483
|
],
|
|
1355
1484
|
redFlags: [
|
|
@@ -1357,13 +1486,43 @@ var ITEMS_MEDIUM = [
|
|
|
1357
1486
|
"No meta descriptions (Google generates random snippets for you)",
|
|
1358
1487
|
"Sharing your link shows no preview image",
|
|
1359
1488
|
"Preview image is broken or shows a default builder-supplied placeholder",
|
|
1360
|
-
"No canonical URL set (causes duplicate-content issues in search)"
|
|
1489
|
+
"No canonical URL set (causes duplicate-content issues in search)",
|
|
1490
|
+
"Saying the site is indexed when you have only generated sitemap and robots files"
|
|
1361
1491
|
],
|
|
1362
1492
|
cliCoverage: "automated"
|
|
1363
1493
|
},
|
|
1494
|
+
{
|
|
1495
|
+
id: "search-discovery",
|
|
1496
|
+
number: 42,
|
|
1497
|
+
title: "Submit and inspect your site before launch",
|
|
1498
|
+
category: "Growth",
|
|
1499
|
+
priority: "medium",
|
|
1500
|
+
timeEstimate: "30 min",
|
|
1501
|
+
prompt: "Act as a search discoverability engineer doing a production launch closeout. First confirm the real production domain and verify the deployed version is the one I intend to launch. Then verify /robots.txt returns 200, does not block public pages, and points to the production sitemap. Verify /sitemap.xml returns 200, uses absolute production URLs, and includes the homepage plus every important public page. Check important public pages for self-referential canonical URLs, unique titles and meta descriptions, internal links, and no accidental noindex tags or X-Robots-Tag headers. In Google Search Console, use the domain property if available, submit the production sitemap, use URL Inspection for the homepage and high-value changed URLs, and report indexed status, crawl allowed status, Google-selected canonical, user-declared canonical, and last crawl time. Request indexing for high-value new or changed pages when the UI allows it. Start validation for fixed Discovered - currently not indexed or Crawled - currently not indexed buckets when appropriate. For Bing/Microsoft, configure or use IndexNow in production only and submit the sitemap URLs. If Search Console or Bing access is missing, do not pretend this is complete: tell me exactly which property, sitemap URL, and page URLs to verify or submit.",
|
|
1502
|
+
what: "This is the handoff from 'my site has SEO files' to 'search engines have actually been told about the launch.' A sitemap sitting in your repo is not the same thing as Google knowing about the pages, crawling them, and deciding whether to index them.",
|
|
1503
|
+
why: "Prelaunch founders often announce a product and then discover that Google has not crawled the homepage, the new guide pages are only 'discovered,' or the sitemap was never submitted. That turns launch momentum into dead air. This check makes discovery status visible before you start sending people to the site.",
|
|
1504
|
+
steps: [
|
|
1505
|
+
"Use the real production domain, not localhost, preview, or a platform fallback URL.",
|
|
1506
|
+
"Verify robots.txt, sitemap.xml, canonical URLs, internal links, and noindex/X-Robots-Tag on the live production pages.",
|
|
1507
|
+
"Submit the production sitemap in Google Search Console for the domain property, preferably sc-domain:yourdomain.com.",
|
|
1508
|
+
"Inspect the homepage and high-value launch pages in Search Console. Record whether each page is live, crawlable, submitted, discovered, crawled, and indexed.",
|
|
1509
|
+
"Request indexing for high-value new or changed pages when Search Console allows it, and start validation for fixed indexing buckets when appropriate.",
|
|
1510
|
+
"Submit the production sitemap URLs to Bing/Microsoft with IndexNow. Do not submit preview or development URLs."
|
|
1511
|
+
],
|
|
1512
|
+
redFlags: [
|
|
1513
|
+
"Assuming a page is indexed because it exists on production",
|
|
1514
|
+
"A sitemap exists, but Search Console has never downloaded it",
|
|
1515
|
+
"No verified Search Console property for the real production domain",
|
|
1516
|
+
"Important pages are live but orphaned from nav, footer, homepage, or related pages",
|
|
1517
|
+
"Preview or development domains are submitted to search engines",
|
|
1518
|
+
'Final report says "indexed" when the page was only submitted or requested'
|
|
1519
|
+
],
|
|
1520
|
+
cliCoverage: "manual_only",
|
|
1521
|
+
whyManual: "Search discovery requires authenticated Search Console/Bing access, production-domain checks, and third-party crawl/index state. The local scanner can verify files and page HTML, but it cannot honestly prove sitemap submission, URL Inspection status, manual indexing requests, or whether Google has indexed a page."
|
|
1522
|
+
},
|
|
1364
1523
|
{
|
|
1365
1524
|
id: "feedback",
|
|
1366
|
-
number:
|
|
1525
|
+
number: 43,
|
|
1367
1526
|
title: "Give users an easy way to tell you what's broken",
|
|
1368
1527
|
category: "Product & Launch",
|
|
1369
1528
|
priority: "medium",
|
|
@@ -1390,7 +1549,7 @@ var ITEMS_MEDIUM = [
|
|
|
1390
1549
|
},
|
|
1391
1550
|
{
|
|
1392
1551
|
id: "responsive-actions",
|
|
1393
|
-
number:
|
|
1552
|
+
number: 44,
|
|
1394
1553
|
title: "Make every action tell the user what's happening",
|
|
1395
1554
|
category: "Product & Launch",
|
|
1396
1555
|
priority: "medium",
|
|
@@ -1417,7 +1576,7 @@ var ITEMS_MEDIUM = [
|
|
|
1417
1576
|
},
|
|
1418
1577
|
{
|
|
1419
1578
|
id: "launch-polish",
|
|
1420
|
-
number:
|
|
1579
|
+
number: 45,
|
|
1421
1580
|
title: "Walk through the whole app one last time",
|
|
1422
1581
|
category: "Product & Launch",
|
|
1423
1582
|
priority: "medium",
|
|
@@ -1445,7 +1604,7 @@ var ITEMS_MEDIUM = [
|
|
|
1445
1604
|
},
|
|
1446
1605
|
{
|
|
1447
1606
|
id: "launch-list",
|
|
1448
|
-
number:
|
|
1607
|
+
number: 46,
|
|
1449
1608
|
title: "Map exactly where your first 100 users will come from",
|
|
1450
1609
|
category: "Growth",
|
|
1451
1610
|
priority: "medium",
|
|
@@ -1472,14 +1631,14 @@ var ITEMS_MEDIUM = [
|
|
|
1472
1631
|
},
|
|
1473
1632
|
{
|
|
1474
1633
|
id: "support",
|
|
1475
|
-
number:
|
|
1634
|
+
number: 47,
|
|
1476
1635
|
title: "Give users a real way to get help",
|
|
1477
1636
|
category: "Product & Launch",
|
|
1478
1637
|
priority: "medium",
|
|
1479
1638
|
timeEstimate: "1 hr",
|
|
1480
1639
|
prompt: "Act as a customer support systems designer for a tiny early-stage app. Add a simple, real support path that a paying or stuck user can find in under 10 seconds. Requirements: (1) a support email or help link visible from footer, account/settings, checkout/success, receipts, and error states; (2) route support messages somewhere I actually monitor daily; (3) include enough context automatically when possible: user ID, email, current URL, order/report ID, browser, and last error; (4) create canned first replies for payment issue, login/code not arriving, data deletion/export, bug report, and refund request; (5) add a simple SUPPORT.md with where messages land, expected response time during launch week, and refund/escalation rules. Do not add a giant help center. Make the smallest support loop that prevents paying users from feeling abandoned.",
|
|
1481
|
-
what: "Feedback is how users tell you what is broken. Support is how a real person gets help when they are stuck, locked out, charged, confused, or angry. For
|
|
1482
|
-
why: "The worst early customer experience is not a bug. It is paying, getting stuck, and finding no human way out. A simple support path turns
|
|
1640
|
+
what: "Feedback is how users tell you what is broken. Support is how a real person gets help when they are stuck, locked out, charged, confused, or angry. For an AI-built launch, this can be one monitored email address \u2014 but it has to be visible and it has to work.",
|
|
1641
|
+
why: "The worst early customer experience is not a bug. It is paying, getting stuck, and finding no human way out. A simple support path turns a bad moment into a conversation and keeps small launch issues from becoming public complaints.",
|
|
1483
1642
|
steps: [
|
|
1484
1643
|
"Create one support destination you will actually watch during launch week: support@yourdomain.com, a shared inbox, Plain, Crisp, Intercom, HelpScout, or even a monitored Gmail alias.",
|
|
1485
1644
|
"Put the support link where pain happens: footer, account/settings, checkout success, receipt email, error pages, login/OTP screens, and paid-report pages.",
|
|
@@ -1503,7 +1662,7 @@ var ITEMS_MEDIUM = [
|
|
|
1503
1662
|
var ITEMS_LOWER = [
|
|
1504
1663
|
{
|
|
1505
1664
|
id: "aeo",
|
|
1506
|
-
number:
|
|
1665
|
+
number: 48,
|
|
1507
1666
|
title: "Make AI assistants able to recommend you",
|
|
1508
1667
|
category: "Growth",
|
|
1509
1668
|
priority: "lower",
|
|
@@ -1533,7 +1692,7 @@ var ITEMS_LOWER = [
|
|
|
1533
1692
|
},
|
|
1534
1693
|
{
|
|
1535
1694
|
id: "community",
|
|
1536
|
-
number:
|
|
1695
|
+
number: 49,
|
|
1537
1696
|
title: "Start a small community space",
|
|
1538
1697
|
category: "Growth",
|
|
1539
1698
|
priority: "lower",
|
|
@@ -1560,7 +1719,7 @@ var ITEMS_LOWER = [
|
|
|
1560
1719
|
},
|
|
1561
1720
|
{
|
|
1562
1721
|
id: "iteration",
|
|
1563
|
-
number:
|
|
1722
|
+
number: 50,
|
|
1564
1723
|
title: "Plan what you'll improve in week 1",
|
|
1565
1724
|
category: "Growth",
|
|
1566
1725
|
priority: "lower",
|
|
@@ -1587,14 +1746,14 @@ var ITEMS_LOWER = [
|
|
|
1587
1746
|
},
|
|
1588
1747
|
{
|
|
1589
1748
|
id: "installable-app",
|
|
1590
|
-
number:
|
|
1749
|
+
number: 51,
|
|
1591
1750
|
title: "Make your app installable on phones",
|
|
1592
1751
|
category: "Product & Launch",
|
|
1593
1752
|
priority: "lower",
|
|
1594
1753
|
timeEstimate: "30 min",
|
|
1595
1754
|
prompt: "Act as a frontend engineer. Make my web app a proper Progressive Web App (PWA) so phone users can install it to their home screen. Tasks: (1) create a /manifest.json with name, short_name (\u226412 chars), start_url: '/', display: 'standalone', background_color, theme_color matching my brand, and an icons array with at least 192\xD7192 and 512\xD7512 PNGs (generate them from my logo if I don't have them yet \u2014 lossless, transparent background). (2) add <link rel='manifest' href='/manifest.json'> and <meta name='theme-color' content='#yourbrand'> to <head>. (3) generate a proper favicon bundle: favicon.ico (multi-size), favicon.svg, apple-touch-icon.png (180\xD7180), and wire them into <head> with <link rel='icon'> and <link rel='apple-touch-icon'>. (4) verify in Chrome DevTools \u2192 Application \u2192 Manifest that every field validates and the install prompt appears. (5) test on a real phone: open in Safari or Chrome, use Share \u2192 'Add to Home Screen', confirm the app launches full-screen without the browser chrome. Report any warnings from Lighthouse's PWA audit.",
|
|
1596
1755
|
what: "A tiny /manifest.json plus proper-sized icons lets people add your site to their phone's home screen with one tap. When they launch it, it opens full-screen \u2014 no browser URL bar in the way \u2014 like a real app. Zero app-store submission needed.",
|
|
1597
|
-
why: "Most
|
|
1756
|
+
why: "Most AI-built sites stop at the default favicon and never get installed. Users who add your app to their home screen return two to three times more often than bookmark-only users \u2014 and it's a 30-minute setup to enable it forever. Also closes the subtle credibility gap of a generic browser favicon on your production URL.",
|
|
1598
1757
|
steps: [
|
|
1599
1758
|
"Generate your icon set: at minimum a 512\xD7512 PNG, a 192\xD7192 PNG, a 180\xD7180 apple-touch-icon.png, and a favicon.ico or favicon.svg. Transparent backgrounds.",
|
|
1600
1759
|
"Create a /manifest.json with your app name, short name (\u226412 chars), start URL, standalone display mode, theme color, and the icons.",
|
|
@@ -3883,6 +4042,24 @@ var SCORE_BANDS = [
|
|
|
3883
4042
|
goNoGoLabel: "Go"
|
|
3884
4043
|
}
|
|
3885
4044
|
];
|
|
4045
|
+
function scoreWithinBand(rawScore, band) {
|
|
4046
|
+
return Math.max(band.minScore, Math.min(band.maxScore, rawScore));
|
|
4047
|
+
}
|
|
4048
|
+
function severityBandedScore(rawScore, counts) {
|
|
4049
|
+
if (counts.critical > 0) {
|
|
4050
|
+
return Math.min(rawScore, SCORE_BANDS[0].maxScore);
|
|
4051
|
+
}
|
|
4052
|
+
if (counts.high > 0) {
|
|
4053
|
+
return scoreWithinBand(rawScore, SCORE_BANDS[1]);
|
|
4054
|
+
}
|
|
4055
|
+
if (counts.medium > 0) {
|
|
4056
|
+
return scoreWithinBand(rawScore, SCORE_BANDS[2]);
|
|
4057
|
+
}
|
|
4058
|
+
if (counts.lower > 0) {
|
|
4059
|
+
return scoreWithinBand(rawScore, SCORE_BANDS[3]);
|
|
4060
|
+
}
|
|
4061
|
+
return SCORE_BANDS[3].maxScore;
|
|
4062
|
+
}
|
|
3886
4063
|
var SOURCE_SCORE_CAP = {
|
|
3887
4064
|
url: 88,
|
|
3888
4065
|
cli: 100,
|
|
@@ -4219,13 +4396,14 @@ function prioritizeLaunchFindings(findings, source = "manual", promptBuilder) {
|
|
|
4219
4396
|
function scoreFromCounts(counts, source) {
|
|
4220
4397
|
const weightedPenalty = counts.critical * SEVERITY_WEIGHTS.critical + counts.high * SEVERITY_WEIGHTS.high + counts.medium * SEVERITY_WEIGHTS.medium + counts.lower * SEVERITY_WEIGHTS.lower;
|
|
4221
4398
|
const rawScore = clampScore(100 - weightedPenalty);
|
|
4399
|
+
const severityScore = severityBandedScore(rawScore, counts);
|
|
4222
4400
|
const scoreCap = SOURCE_SCORE_CAP[source];
|
|
4223
|
-
const score = Math.min(
|
|
4401
|
+
const score = Math.min(severityScore, scoreCap);
|
|
4224
4402
|
return {
|
|
4225
4403
|
score,
|
|
4226
4404
|
rawScore,
|
|
4227
4405
|
weightedPenalty,
|
|
4228
|
-
coveragePenalty: Math.max(0,
|
|
4406
|
+
coveragePenalty: Math.max(0, severityScore - score)
|
|
4229
4407
|
};
|
|
4230
4408
|
}
|
|
4231
4409
|
function scoreBandFor(score) {
|
|
@@ -4419,8 +4597,8 @@ function printHelp() {
|
|
|
4419
4597
|
process.stdout.write(
|
|
4420
4598
|
`shippingszn v${PKG_VERSION}
|
|
4421
4599
|
|
|
4422
|
-
Read-only
|
|
4423
|
-
|
|
4600
|
+
Read-only launch inspector that checks the current project against high-signal
|
|
4601
|
+
launch-readiness items from shippingszn.
|
|
4424
4602
|
|
|
4425
4603
|
Usage:
|
|
4426
4604
|
npx shippingszn@latest [path] [options]
|
|
@@ -4436,14 +4614,14 @@ Options:
|
|
|
4436
4614
|
-h, --help Show this help.
|
|
4437
4615
|
-v, --version Print version.
|
|
4438
4616
|
|
|
4439
|
-
This is the FREE
|
|
4617
|
+
This is the FREE inspection. It tells you launch risk exists. It does NOT tell
|
|
4440
4618
|
you what's broken or how to fix it \u2014 that's gated behind the $49 Launch Fix Kit
|
|
4441
|
-
(unlocks full findings +
|
|
4442
|
-
verification steps + red-flag checks). Read-only on
|
|
4443
|
-
locked scan handoff for checkout and send anonymous
|
|
4444
|
-
counts, file count, scanner version \u2014 no project
|
|
4445
|
-
finding-level detail); there is no Wall publish opt-in or
|
|
4446
|
-
code non-zero if any critical findings.
|
|
4619
|
+
(unlocks full findings + 51-item workbook + human ship decision + AI-builder
|
|
4620
|
+
punch list + verification steps + red-flag checks). Read-only on
|
|
4621
|
+
disk. Normal runs create a locked scan handoff for checkout and send anonymous
|
|
4622
|
+
Wall stats (score, severity counts, file count, scanner version \u2014 no project
|
|
4623
|
+
name, no paths, no finding-level detail); there is no Wall publish opt-in or
|
|
4624
|
+
opt-out flag. Exit code non-zero if any critical findings.
|
|
4447
4625
|
`
|
|
4448
4626
|
);
|
|
4449
4627
|
}
|
|
@@ -4699,11 +4877,13 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
|
|
|
4699
4877
|
process.stdout.write(c.dim(`Scanned ${files.length} files.
|
|
4700
4878
|
|
|
4701
4879
|
`));
|
|
4880
|
+
process.stdout.write(`${c.bold("Verdict:")} ${bandColor(c.bold(bandLabel))}
|
|
4881
|
+
`);
|
|
4702
4882
|
process.stdout.write(
|
|
4703
|
-
`${c.bold("
|
|
4704
|
-
|
|
4883
|
+
`${c.bold("Readiness Score:")} ${launchReadiness.score}/100
|
|
4705
4884
|
`
|
|
4706
4885
|
);
|
|
4886
|
+
process.stdout.write(c.dim("Higher is better.\n\n"));
|
|
4707
4887
|
process.stdout.write(`${c.bold("Findings detected:")}
|
|
4708
4888
|
`);
|
|
4709
4889
|
process.stdout.write(` ${c.red(`${totals.critical} critical`)}
|
|
@@ -4746,19 +4926,19 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
|
|
|
4746
4926
|
if (totals.high > 0) {
|
|
4747
4927
|
process.stdout.write(
|
|
4748
4928
|
c.dim(
|
|
4749
|
-
"High and medium launch
|
|
4929
|
+
"High and medium launch gaps can expose trust gaps, break conversion moments, or make the project feel unfinished.\n\n"
|
|
4750
4930
|
)
|
|
4751
4931
|
);
|
|
4752
4932
|
} else if (totals.medium > 0) {
|
|
4753
4933
|
process.stdout.write(
|
|
4754
4934
|
c.dim(
|
|
4755
|
-
"Medium launch
|
|
4935
|
+
"Medium launch gaps usually will not crash the app, but they can make users hesitate, miss conversion moments, or make the project feel unfinished.\n\n"
|
|
4756
4936
|
)
|
|
4757
4937
|
);
|
|
4758
4938
|
} else {
|
|
4759
4939
|
process.stdout.write(
|
|
4760
4940
|
c.dim(
|
|
4761
|
-
"Lower launch
|
|
4941
|
+
"Lower launch gaps usually will not break the app, but they can still make the project feel rough or unfinished.\n\n"
|
|
4762
4942
|
)
|
|
4763
4943
|
);
|
|
4764
4944
|
}
|
|
@@ -4778,7 +4958,7 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
|
|
|
4778
4958
|
`);
|
|
4779
4959
|
process.stdout.write(
|
|
4780
4960
|
c.dim(
|
|
4781
|
-
` Includes the ${totalFindings} specific ${pluralize(totalFindings, "finding")}, file evidence, and AI-builder
|
|
4961
|
+
` Includes the ${totalFindings} specific ${pluralize(totalFindings, "finding")}, file evidence, human launch decision, and AI-builder punch list.
|
|
4782
4962
|
|
|
4783
4963
|
`
|
|
4784
4964
|
)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shippingszn",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Read-only
|
|
3
|
+
"version": "0.9.7",
|
|
4
|
+
"description": "Read-only launch inspector for AI-built apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"keywords": [
|
|
16
16
|
"cli",
|
|
17
17
|
"launch",
|
|
18
|
+
"launch-inspector",
|
|
18
19
|
"checklist",
|
|
19
20
|
"preflight",
|
|
20
21
|
"security",
|