AI-assisted development

I Built an App With AI. What Should I Fix Before Putting It Into Production?

Before launch, test the failure paths around authentication, credentials, environments, payments, webhooks, email, storage, and recovery.

An AI-built application can reach a working demo quickly. Before launch, review what happens when the happy path fails: a user submits malformed data, a webhook arrives twice, a payment is uncertain, a job crashes, an email bounces, or a dependency stops responding.

Production readiness is not a final polish pass. It is a set of concrete checks that make failures bounded, visible, recoverable, and safe.

1. Turn off debug output and public errors

Failure mode: A production exception displays a stack trace, SQL query, filesystem path, environment value, or provider response to a visitor.

Action: Disable debug mode and public exception pages in the deployed environment. Send detailed errors to protected logs with a request ID, while returning a generic user-facing message. Check a deliberately failing route in production and confirm it does not expose configuration or credentials.

2. Separate development, staging, and production

Failure mode: A staging checkout charges a real card, a test email reaches a customer, or a local migration targets the production database.

Action: Use separate credentials, databases, provider modes, callback URLs, queues, and recipient policies. Make the selected environment visible in safe operational metadata. Test staging with a test Stripe account and captured email, not with a live Stripe key or production SES identity.

3. Keep production credentials and database permissions narrow

Failure mode: A leaked application secret can administer the cloud account or delete unrelated data.

Action: Use workload identity or narrowly scoped deployment secrets. Give the runtime database account only the tables and statements it needs; separate migration authority from request-time authority. Rotate credentials and confirm the old value no longer works.

For supported provider operations, Gateman can keep selected provider credentials outside the application. Its Stripe capability ID is stripe.use, scope stripe:use, route POST /stripe/request; the request remains provider-native. Its email capability uses email.send and POST /send/ses. These boundaries do not make the application identity harmless: a compromised application can still abuse every granted operation. Generic secrets.read retrieval is different because it returns a credential value to the application.

The official client guides are PHP, Node.js, Python, and Go.

4. Validate input and escape output

Failure mode: A crafted request changes business data, injects SQL, renders script in another user’s browser, or causes an unsafe redirect.

Action: Validate type, length, format, range, and allowed values on the server. Use prepared SQL statements and allowlists for dynamic structural values. Escape output for its context: HTML, an attribute, JavaScript, JSON, a URL, or a shell command. Treat client-side validation as convenience only.

Test invalid input directly, including missing fields, unexpected arrays, oversized strings, Unicode, duplicate parameters, and malformed identifiers.

5. Verify authentication, authorization, and sessions

Failure mode: A logged-in user can read another tenant’s object by changing an ID, or an old session remains valid after a privilege change.

Action: Enforce account ownership at the repository or service boundary for every read and mutation. Regenerate the session ID after login or privilege changes, expire sessions appropriately, protect cookies, and require reauthentication for sensitive changes. Test direct object URLs and API requests using two accounts.

Authentication answers who the caller is. Authorization answers whether that caller may perform this action on this object in this state. Do not treat hidden controls or random IDs as authorization.

6. Use CSRF protection where browser state changes

Failure mode: A malicious site causes a browser with an active session to submit a state-changing request.

Action: Use CSRF tokens and server-side verification for cookie-authenticated browser mutations. Set SameSite cookies deliberately, check the origin or referer where appropriate, and do not exempt password, billing, credential, or destructive actions merely because they are POST requests. Test both a valid token and a missing or mismatched token.

7. Add rate limits and abuse controls

Failure mode: Login attempts, password resets, email sends, search, uploads, or expensive provider calls are automated until the service or budget is exhausted.

Action: Rate-limit by the appropriate combination of account, user, IP, route, and provider operation. Add request-size limits, concurrency limits, quotas, lockouts or step-up checks where abuse warrants them. Return a controlled response and log enough safe context to investigate without logging secrets.

8. Verify webhooks before processing them

Failure mode: An attacker posts a forged payment event, or a genuine event is applied twice or out of order.

Action: Preserve the exact raw body, read the provider signature header, verify timestamp and signature with the provider-maintained library, and reject invalid signatures before business processing. Durably record the event ID before applying non-repeatable work. Make handlers idempotent and do not assume event arrival order.

For Stripe, keep test and live endpoint secrets separate, and reconcile current provider state when an event is ambiguous. A webhook acknowledgement means durable acceptance, not that every downstream action has completed.

9. Redact logs and diagnostic material

Failure mode: A credential, session cookie, reset link, payment token, webhook secret, or full customer message appears in logs that more people can access than the application.

Action: Establish a denylist for Authorization headers, cookies, secret fields, and provider credentials. Log request IDs, route, account, safe entity IDs, status, error code, and duration instead. Review exception reporters, query logging, request bodies, test output, generated fixtures, shell history, and debug pages.

Run a realistic failure and inspect the actual log entry. Redaction that exists only in a code comment is not a control.

10. Back up data and test restoration

Failure mode: Backups exist, but no one can restore them, the restore is incomplete, or the application’s schema and uploaded files do not match.

Action: Define backup frequency, retention, encryption, access, and ownership. Perform a restore into an isolated environment. Start the restored application, verify users and representative records, check file/object references, and record the time and failures. A restore test should be a repeatable operation, not a statement in a runbook.

11. Treat email acceptance as the beginning

Failure mode: The application records “sent” as “delivered,” keeps retrying an address that hard-bounces, or ignores complaint signals.

Action: Store the provider message ID and distinguish request acceptance, delivery, bounce, complaint, rejection, and delay. Receive provider events or notifications, deduplicate them, suppress permanent bounces, and review complaints. Keep consent, unsubscribe, sender identity, reputation, and recipient policy in the application’s ownership boundary.

12. Exercise cron jobs, workers, and queues

Failure mode: A scheduled job runs twice, a worker dies after claiming work, or a queue silently stops while the web request still reports success.

Action: Give jobs durable state, bounded retries, visibility into failures, and a safe recovery path. Make a job idempotent where possible, record attempts, and alert on queue age and dead letters. Confirm the deployed scheduler actually invokes the job and that the worker process restarts after failure.

13. Lock and review dependencies

Failure mode: A fresh deployment resolves a different dependency tree, or an abandoned package has an unpatched vulnerability.

Action: Commit lockfiles, build from the lockfile in CI, review update notices, and identify packages without active maintenance. Remove unused dependencies. Test updates in staging and keep a rollback artifact. A package that is popular is not automatically appropriate for handling credentials or payments.

14. Require HTTPS and secure browser defaults

Failure mode: Cookies or credentials travel over HTTP, framing enables clickjacking, or a permissive browser policy turns an injection into account compromise.

Action: Redirect HTTP to HTTPS, use Secure, HttpOnly, and appropriate SameSite cookies, and configure relevant headers such as Content-Security-Policy, frame restrictions, MIME sniffing protection, and a referrer policy. Check the deployed response, not only framework configuration. Ensure local exceptions do not reach production.

15. Monitor failures and alert on action

Failure mode: The system is technically running while payments fail, email queues grow, provider calls time out, or disk space disappears.

Action: Monitor availability, latency, error rate, queue age, database health, provider rejection/throttle responses, backup freshness, and certificate expiry. Alert on thresholds that have an owner and a response. Include correlation IDs so an alert can lead to a request or job without exposing sensitive payloads.

16. Test billing and subscription failures

Failure mode: A payment succeeds but the order remains pending, a renewal fails without customer communication, or a cancellation is applied to the wrong tenant.

Action: Test successful payment, decline, timeout, duplicate webhook, delayed webhook, refund, renewal failure, cancellation, plan change, and entitlement reconciliation. Make webhook processing and database updates recoverable. Never grant access solely because a browser returned from a checkout page.

17. Bound third-party API uncertainty

Failure mode: A timeout is treated as “provider did nothing,” and a blind retry creates a duplicate charge, email, or external object.

Action: Set connect and total timeouts, handle provider rate limits, preserve provider request IDs, and classify transport outcomes as certain or uncertain. Use provider-native idempotency where supported, record the application intent, and reconcile state after an ambiguous response. Official clients do not remove the need for this decision.

18. Plan migrations and rollback

Failure mode: Code deploys before its schema, a destructive migration cannot be reversed, or a rollback starts old code against a new incompatible database.

Action: Make migrations additive where possible, deploy compatibility code before removing columns, back up affected tables before structural changes, and rehearse rollback or forward repair in staging. Define which version of application code can run against each schema state.

19. Remove dead paths and test credentials

Failure mode: A forgotten admin route, temporary script, fixture credential, debug endpoint, or old webhook remains reachable after launch.

Action: Search routes, scheduled tasks, public files, scripts, test fixtures, environment examples, and DNS for temporary artifacts. Delete or disable unused paths, revoke test credentials that were copied into deployments, and verify that a production build does not include development tools or test data.

20. Run a deployed production smoke test

Failure mode: Local tests pass while the deployed application has a wrong environment variable, cookie domain, callback URL, queue worker, database permission, or cache rule.

Action: Run a controlled smoke test against the deployed route: log in with a test account, exercise one representative read and mutation, verify a safe email path, inspect the resulting job or provider acceptance, and confirm the user-facing response. Use production-safe test data and clean it up. Confirm logs, alerts, and rollback references before declaring launch complete.

What I would fix first if this were my app

Keep an evidence folder for the release: the deployed commit or build identifier, migration status, backup and restore result, smoke-test result, provider-mode checks, and the owner for each alert. This makes launch review concrete and gives the next person a starting point when something fails. A checklist that cannot point to evidence is only a list of intentions.

  1. Separate production from development: credentials, databases, provider modes, recipients, webhooks, queues, and deployment configuration.
  2. Turn off public errors and audit logs, exception reports, debug pages, and command output for secret or personal-data leakage.
  3. Verify tenant authorization, session handling, CSRF protection, input validation, output escaping, and rate limits on the highest-risk routes.
  4. Make payment and webhook processing durable, signature-verified, duplicate-safe, order-independent, and recoverable.
  5. Confirm backups can actually restore the database and files, then test the rollback path for the next deployment.
  6. Exercise workers, scheduled jobs, email feedback, provider timeouts, rate limits, and ambiguous outcomes.
  7. Run the deployed smoke test with safe data and write down who responds to each alert.

An AI coding tool can accelerate implementation, but it does not know which production assumptions are unsafe unless you make them explicit. The checklist is complete only when each item has an owner, evidence, and a recovery path.

← All articles