How to Security-Review Code You Did Not Write
The uncomfortable part of AI-assisted development is not that the code is bad. It is that there is more of it than anyone reads, and reviewing it line by line does not scale. So most teams skim, the app works, and the review becomes a vibe.
There is a better method: stop reading the diff and follow the data.
Step 1 — Map the trust boundaries
Write down every place untrusted input crosses into your system: HTTP routes, server functions, webhooks, file uploads, query parameters, and anything a browser can call directly — including your database's public API. This list is short even in a large app, and everything that matters happens on it.
An AI-generated codebase usually has one boundary you did not think about: the database itself. If the client holds a key that can query tables directly, every table is an endpoint.
Step 2 — Check authorisation at each boundary, not in the UI
For every entry point, ask two questions: who is allowed to call this, and where is that enforced? The answer must point at server-side code or a database policy. A hidden button, a conditional route render, or a client-side role check is not an answer.
Two frequent generated mistakes:
- An endpoint that reads a user or role identifier from the request body instead of from the verified session.
- A privileged database client used to answer "is this caller an admin?" — which means the check runs with privileges the caller does not have.
Step 3 — Trace the input
Take one request and follow the value. Is it validated against a schema at the edge? Is it interpolated into SQL, a shell command, a file path, or a URL that the server then fetches? Server-side fetches from user-supplied URLs deserve special attention — an allowlist, not a blocklist.
Then trace the response back out: does it return the whole database row when the UI needs three fields? Over-returning is how internal flags, other users' identifiers and password-reset state leak.
Step 4 — Audit secrets
Every secret read at module scope in a file that a component can import is a candidate for shipping to the browser. Check the built bundle, not the source tree, and check git history separately — a deleted key is still a published key until it is rotated.
Step 5 — Judge dependencies by reachability
A generated app adds packages generously. Resolve the lockfile against advisory data, then ask whether the vulnerable code path is reachable from your request handling. A critical advisory in a build-time tool and one in your auth path both say "critical"; only one of them is a launch blocker.
Step 6 — Read the config as code
Deployment configuration is where quiet mistakes live: debug flags, permissive origins, public source maps, preview environments wired to production data, webhook handlers that skip signature verification. None of it appears in the application diff.
Step 7 — Record evidence, then decide
For each finding, write down what was observed, where, and what the fix changes. That record is what turns a review into a decision you can defend later — and re-check after the next twenty AI edits, when the answer may have changed.
The goal is not a clean report. It is knowing which parts of your app you have actually verified, and being honest about the rest.
Sentrail automates the mechanical half of this — boundaries, access rules, secrets, dependencies, configuration — against your real repository and providers, and leaves the judgement calls to you with the evidence attached.
