Skip to content

Instantly share code, notes, and snippets.

@badri
Created April 30, 2026 10:57
Show Gist options
  • Select an option

  • Save badri/616a44f33353fae3dc0a0b6c62b05116 to your computer and use it in GitHub Desktop.

Select an option

Save badri/616a44f33353fae3dc0a0b6c62b05116 to your computer and use it in GitHub Desktop.
Ship-Safe Code — 5 Silent Failures (audit commands). Companion to the Zero to SaaS 7-day series. Full checklist + Claude Code/Codex skill: gumroad.com/l/ship-safe-code-checklist

Ship-Safe Code — 5 Silent Failures (Audit Commands)

The runnable companion to the Zero to SaaS 7-day Ship-Safe Code series.

Five files, one per silent failure. Run each from your project root.

# File What it checks
1 failure-1-secrets.sh Committed secrets in git history + tracked code
2 failure-2-auth.sh Auth bug patterns + manual test snippets
3 failure-3-input.sh Unsafe input handling (XSS, SQL injection, path traversal)
4 failure-4-backups.md Backup-and-restore questions (mostly manual)
5 failure-5-errors.sh Silent error patterns + tracker presence

Want the full version?

The full Ship-Safe Code Checklist — with floor fixes, ceiling improvements, the 20-item pre-deploy pass, the stack-specific inspection map (Next.js / Supabase / Rails / Django / FastAPI), and the Ship-Safe Audit Skill for Claude Code and Codex (a code-review agent that runs this audit on your repo and emits a PR-ready report) — is at:

https://badridilbert.gumroad.com/l/ship-safe-code-checklist/SHIPSAFE — $19 with launch coupon (regular $39).

— Lakshmi

#!/usr/bin/env bash
# Silent Failure #1 — The Committed Secret
# Run from your repo root.
# 1. Scan git history for anything that looks like a key
git log -p | grep -iE "(api[-_]?key|secret|token|password|bearer)" | head -40
# 2. Scan tracked code
grep -rE "(sk-|api[_-]?key|secret|password|bearer)" \
--exclude-dir=node_modules --exclude-dir=.venv --exclude=*.lock | head -20
# 3. What's about to be committed
git status
git diff --staged
# 4. Are sensitive files tracked?
git ls-files | grep -iE "(\.env$|\.env\.|credentials|service-account)" | head -20
# If any of these show real-looking secrets — assume rotated, not safe.
#
# Floor fix:
# 1. Rotate every credential that appears in your repo or git history. Today.
# 2. Force every secret out of code into env vars.
# 3. .gitignore covers: .env, .env.*, *.pem, *.key, credentials.json, service-account*.json
# 4. Install git-secrets or trufflehog as a pre-commit hook.
#!/usr/bin/env bash
# Silent Failure #2 — The Open Door
# Run from your repo root.
# 1. Where is JWT used?
grep -rE "JWT|jwt\.decode|jsonwebtoken" \
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
--include="*.py" --include="*.rb" --include="*.go" | head -20
# 2. Where are session/auth checks happening?
grep -rE "useAuth|useSession|getServerSession|current_user|currentUser" \
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
--include="*.py" --include="*.rb" | head -20
# 3. Client-supplied user IDs (potential IDOR)
grep -rE "user_id|userId" \
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
--include="*.py" --include="*.rb" | grep -iE "(req\.|request\.|params\.|body\.)" | head -10
# Then in an incognito browser, run the 5 tests:
#
# 1. Hit a "protected" page directly. Does protected content flash before redirect?
# 2. DevTools → Network. Hit a protected API endpoint with no Authorization header. Returns data?
# 3. Log in. Copy your token. Log out. Hit a protected endpoint with the old token. Still works?
# 4. Decode your JWT at jwt.io. Is there an exp claim? Next hour, or next decade?
# 5. Edit URL: /users/me → /users/2 (or any other ID). Does the API return their data?
#
# Any "yes" = open door.
#
# Floor fix:
# - Move every authorization check to the server. Client-side `if (user)` is UX, not security.
# - JWTs: exp ≤ 1 hour, refresh tokens stored httpOnly + secure.
# - Logout = server-side token revocation, not client-only.
# - Object-level authorization on every endpoint.
#!/usr/bin/env bash
# Silent Failure #3 — The Trust Fall
# Run from your repo root.
# 1. Dangerous HTML rendering
grep -rE "dangerouslySetInnerHTML|v-html|innerHTML\s*=" \
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
--include="*.vue" --include="*.svelte" | head -10
# 2. Raw SQL string concat (Python f-strings)
grep -rE 'f".*SELECT|f".*INSERT|f".*UPDATE' --include="*.py" | head -10
# 3. Server-side template injection patterns
grep -rE "render_template_string|eval\(|new Function\(" \
--include="*.py" --include="*.js" --include="*.ts" | head -10
# 4. Path traversal
grep -rE "path\.join\([^,]+,\s*req\." --include="*.js" --include="*.ts" | head -10
# Then try these payloads in your forms + against your JSON API:
# '; DROP TABLE users; --
# <script>alert(document.cookie)</script>
# {{7*7}}
# ../../etc/passwd
#
# Bypass the form with curl too:
# curl -X POST https://yourapp.com/api/comments \
# -H "Content-Type: application/json" \
# -d '{"text": "<img src=x onerror=alert(1)>"}'
# Refresh where that comment renders. Alert? You have stored XSS.
#
# Floor fix:
# - Server-side schema validation on every endpoint (Pydantic / Zod / Yup / valibot)
# - Parameterized queries always (no string concat into SQL)
# - Escape on output, not input. Audit `dangerouslySetInnerHTML` usages.
# - Uploads: validate MIME by magic bytes, max size, store outside web root.

Silent Failure #4 — The Restore You've Never Run

This one is mostly manual. Answer these without checking:

  1. Where exactly are your last 7 days of database backups stored?
  2. How do you trigger a restore?
  3. When did you last actually do one (against a non-production target)?
  4. If your prod database vanished right now, what's the longest the data could be stale by, when it comes back?
  5. Where are your migration files? Are they checked into git, in order, with their down migrations?

If any of those is "I'd have to look that up" — that's a no.

Floor fix

  • Daily automated logical backup (pg_dump cron) to off-platform storage (R2, B2 — not the same provider hosting your DB)
  • One restore drill, this week. Spin up a fresh DB. Restore yesterday's backup. Run a query that proves data is queryable. Calendar this monthly.
  • Migrations live in git: Alembic / Prisma / Drizzle / Goose / dbmate. Stop running ad-hoc SQL.
  • User uploads / object storage backed up off-platform too. rclone daily.
#!/usr/bin/env bash
# Silent Failure #5 — The Silent 500
# Run from your repo root.
# 1. Empty Python except: pass
grep -rE "except.*:\s*pass|except\s+\w+\s*:\s*pass" --include="*.py" | head -20
# 2. Empty JS/TS catch blocks
grep -rE "catch\s*\(\s*[^)]*\s*\)\s*\{\s*\}|\.catch\(\(\)\s*=>\s*\{\s*\}\)" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" | head -20
# 3. Empty Ruby rescues
grep -rE "rescue\s*=>\s*\w+\s*$|rescue\s*$" --include="*.rb" | head -10
# 4. Is an error tracker even installed?
grep -rEi "(sentry|highlight|honeybadger|bugsnag|rollbar|glitchtip)" \
package.json requirements.txt Gemfile go.mod 2>/dev/null | head -10
# Then manually: force a 5xx in your app (dev or staging — not prod).
#
# 1. What does the user see? Stack trace, or generic copy + error ID?
# 2. Did the error arrive in your tracker with user context?
# 3. Did you get a notification on a channel you'd actually see in 5 minutes?
#
# Floor fix:
# - Production error pages return generic copy + an error ID. Never stack traces.
# - One error tracker wired up properly (Sentry / Highlight / GlitchTip self-hosted).
# - One alert channel you check (Discord webhook, email, PagerDuty).
# - Delete the silent catches. Every `except: pass` and empty `catch` is a bug ticket.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment