How to Secure AI-Generated Code Before You Ship It
Published · Strata Security
On This Page
- Why AI Coding Assistants Changed the Economics of Shipping Code
- AI Code Isn't Inherently Insecure — Review Is the Real Variable
- Common Insecure Defaults in AI-Generated Code
- Mobile Applications Built With AI Assistance
- Repository and CI/CD Considerations
- Supply Chain Risk From AI-Suggested Dependencies
- Security Review Checklist Before Shipping AI-Generated Code
- Frequently Asked Questions
- Conclusion
1Why AI Coding Assistants Changed the Economics of Shipping Code
Tools like Claude Code, GitHub Copilot, and ChatGPT have changed how fast a working feature gets written. A task that once took a day of scaffolding — a new API route, an admin panel, a data export job — can now be described in a sentence and generated in minutes. For startup founders, indie developers, and small engineering teams, that speed is not a marginal improvement. It is the difference between shipping a feature this week or not shipping it at all.
Speed changes the economics of development, but it doesn't change the economics of security review. A human developer writing the same feature by hand would produce code at a fraction of the pace, which meant the volume of new code entering a codebase on any given day was naturally bounded by how fast a person could type and think. AI-assisted development removes that natural bound. The result, in practice, is security debt: code that works, ships, and accumulates faster than anyone is reviewing it for the mistakes that were always possible, just previously rarer because there was less code being written per hour.
This article covers what AI-generated code gets wrong by default, with concrete insecure and secure examples, a repeatable review checklist, and where mobile apps, repositories, and CI/CD pipelines fit into the picture — for engineers and teams who want to keep moving fast without shipping the same mistakes at a faster rate.
2AI Code Isn't Inherently Insecure — Review Is the Real Variable
It's tempting to treat "AI-generated" as a security category of its own, but that framing doesn't hold up. A large language model trained on public code produces code that reflects the patterns most common in its training data — which includes both good and bad security practice, in roughly the proportion they actually appear in the wild. An assistant asked to "connect to the database" is just as capable of producing a parameterized query as a raw string-concatenated one; which it produces depends heavily on the specificity of the prompt, the surrounding code it's using as context, and whether a security-relevant detail was ever mentioned at all.
The actual risk isn't the model — it's the removal of a review step. A prompt like "add an endpoint that returns a user's order history" is a complete, working instruction for the happy path. It says nothing about authorization, rate limiting, or what should happen if the requested order belongs to a different user — so an assistant optimizing for "does this satisfy the request" has no reason to add checks the prompt never mentioned. A human engineer under identical time pressure, writing the same endpoint from scratch, is prone to exactly the same omission. AI-generated code should be reviewed with the same rigor as code from a new, unfamiliar contributor — not because the tool is untrustworthy, but because unreviewed code from any source carries this risk, and AI assistants simply produce a lot more of it per hour than a person does.
3Common Insecure Defaults in AI-Generated Code
The categories below aren't unique to AI-generated code — they're the same weakness classes that show up in any fast-moving codebase. What's different is how often they appear when a prompt doesn't explicitly ask for the secure version, since "explicitly secure" and "shortest working example" are frequently different amounts of code.
Hardcoded Secrets and API Keys
Asked to "connect to Stripe" or "add a database connection," an assistant will frequently produce a complete, runnable example — which usually means a literal key or connection string sitting directly in the generated file, because that's the fastest way to demonstrate a working example without also scaffolding a configuration system.
# Generated to "get it working quickly"
STRIPE_SECRET_KEY = "sk_live_51H8x...redacted...tK3m"
DATABASE_URL = "postgresql://admin:SuperSecret123@prod-db.internal:5432/app"import os
STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]The fix costs nothing in functionality — it's the same code, reading the same value, from a source that isn't the file itself. See Hardcoded Secrets: Why They Still Reach Production and How Attackers Find Them for how these values get discovered once they're committed, and why deleting the file later usually isn't enough on its own.
Authentication Mistakes
Authentication code generated quickly tends to under-specify the exact parameters that matter most: signing algorithm, token expiry, and where the signing key itself comes from. A short prompt like "add JWT login" is enough to produce a token that works in a demo and fails every real security expectation.
const token = jwt.sign(
{ userId: user.id },
"secret123"
)const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SIGNING_KEY,
{ expiresIn: "15m", algorithm: "HS256" }
)A signing key this weak is brute-forceable in practice, and a token with no expiry remains valid indefinitely if it's ever intercepted — two separate problems the insecure version introduces in four lines of code.
Authorization Mistakes
This is the single most common gap in AI-generated endpoints. Authentication ("who is this user") is usually scaffolded correctly, because it's a well-known pattern the model has seen thousands of times. Authorization ("is this specific user allowed to access this specific resource") is business-specific and contextual — and a prompt that doesn't spell it out often gets an endpoint that works for the happy path and nothing else.
app.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await Order.findById(req.params.id)
res.json(order)
})app.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await Order.findById(req.params.id)
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Not found' })
}
res.json(order)
})The insecure version is a textbook CWE-639 (Authorization Bypass Through User-Controlled Key) — incrementing an ID in the URL is enough to read any other customer's order. It passes every functional test and every happy-path demo, which is exactly why it's easy to ship without noticing.
Dependency Risk
Asked to add a capability — parsing a file format, sending an email, resizing an image — an assistant will often reach for a plausible-sounding package rather than confirm the package is real, maintained, and not a typo-squatted name close to a more popular one. Package name confusion (reqeusts instead of requests,python3-dateutil instead of python-dateutil) is a documented real-world attack pattern, and a generated import or require statement is not, by itself, evidence the package is legitimate.
- Confirm the package actually exists on the official registry (npm, PyPI) before installing it.
- Check maintenance activity — last publish date, open issue count, and download volume relative to what it claims to do.
- Compare the exact name character-by-character against the well-known package it resembles.
Insecure Configuration Defaults
Configuration generated to "just make the request work" tends to disable the exact restriction that would have caught a real attack later. A wildcard CORS policy is the most common version of this — it silences a development-time error message at the cost of allowing any origin to make authenticated requests against the API in production.
app.use(cors({ origin: '*', credentials: true }))app.use(cors({
origin: ['https://app.example.com'],
credentials: true,
}))Logging Sensitive Information
Debug logging generated during a "why isn't login working" troubleshooting session routinely logs the exact values that shouldn't reach a log aggregator — passwords, tokens, full request bodies — because those are precisely the values a developer wants visible while debugging, and the generated code has no way to know a log line meant for today's debugging session will still be running in production next quarter.
logger.info(`Login attempt for ${email} with password ${password}`)logger.info(`Login attempt for user_id=${user.id}`)Application logs are frequently retained for months, shipped to third-party aggregators, and readable by a wider set of engineers than the production database itself — which makes a logged credential a quieter, longer-lived exposure than the same value briefly appearing in a stack trace.
4Mobile Applications Built With AI Assistance
Mobile development carries every risk above plus platform-specific ones, because a compiled mobile binary ships to every device that installs the app — a secret embedded in it is available to anyone who downloads the app and decompiles it, not just someone who breaches a server. AI-generated mobile code has been observed to default to cleartext network traffic (skipping ATS/Network Security Config enforcement), to embed API keys directly in source rather than a secure keystore, and to leave Android components exported without a permission requirement when a prompt asks for a "share" or "deep link" feature without mentioning who else should be able to invoke it.
These are exactly the patterns covered in depth in APK Static Analysis, Explained and IPA Security Testing, Explained — both worth reading directly if AI-assisted code is going into a shipping Android or iOS app, since static analysis of the compiled binary is what actually catches these before an app store release.
5Repository and CI/CD Considerations
A faster rate of commits changes the calculus for repository and pipeline controls that were previously "nice to have." Branch protection, required reviews, and status checks matter more, not less, when a larger share of incoming code was generated in minutes rather than written over hours — the volume of changes a reviewer needs to get through increases, while the time available to review each one doesn't.
- Require pull request review on every change touching authentication, authorization, payment handling, or dependency manifests — regardless of how the change was authored.
- Run secret and dependency scanning in CI on every push, not just periodically, since AI-assisted commits can introduce a hardcoded value or a new package in the same change.
- Don't let "AI-generated, looked fine" substitute for a required status check passing.
For the full set of repository controls this implies — secret detection, dependency review, access hygiene — see Repository Security Assessment Checklist, and for the pipeline-level controls (branch protection, artifact integrity, fork PR handling) see CI/CD Security Controls Every Engineering Team Should Have.
6Supply Chain Risk From AI-Suggested Dependencies
Every dependency an assistant suggests is a trust decision made on your behalf, often without your explicit awareness that a decision was even made. A generatedpackage.json or requirements.txt entry is a name and a version range — it carries no signal about whether that package is actively maintained, whether its maintainer's account has ever been compromised, or whether the name is a close imitation of a more popular package registered specifically to catch this exact mistake.
This isn't a hypothetical risk category — it's the same trust boundary CI/CD Security Controls Every Engineering Team Should Have covers for pipeline dependencies generally, just introduced earlier in the process, at the moment an assistant suggests npm install some-package rather than at build time. Treat a newly suggested dependency as a candidate to verify, not a decision already made — confirm the registry listing, the maintenance history, and the exact package name before it enters a lockfile.
7Security Review Checklist Before Shipping AI-Generated Code
Run this before merging any AI-assisted change that touches more than styling or copy:
Secrets
- No API key, password, token, or connection string appears as a literal value anywhere in the diff.
- Every credential is read from an environment variable or secret manager, not a config file committed to the repository.
Authentication
- Signing keys are sourced from the environment, never a literal string in code.
- Tokens have an explicit, reasonable expiry — not "never expires" by omission.
Authorization
- Every endpoint that reads or writes a specific resource confirms the requesting user actually owns or is permitted to access that resource — not just that they're logged in.
- Object IDs in URLs or request bodies can't be incremented or guessed to access another user's data.
Dependencies
- Every new package is confirmed to exist on the official registry, under the exact expected name.
- New dependencies are checked against known CVEs before merging, not after a scanner flags them post-release.
Configuration
- No wildcard CORS origin, no debug/verbose mode left enabled, no cleartext traffic permitted where the platform default is encrypted.
Logging
- No password, token, full request body, or other sensitive field is written to application logs.
Mobile (if applicable)
- No secret embedded in app source destined for a compiled binary; network traffic is encrypted by default.
Repository / CI/CD
- The change passes required status checks (secret scan, dependency scan, tests) — no bypass because "it's just a generated change."
8Frequently Asked Questions
9Conclusion
AI coding assistants make it faster to write working software — they don't make it faster to review that software for the mistakes it can quietly contain. Hardcoded secrets, missing authorization checks, permissive configuration, and unvetted dependencies are not new categories of risk; they're the same categories engineering teams have always had to review for, arriving at a volume that outpaces manual review unless a deliberate process is in place to catch them. Treating every AI-assisted change with the review rigor of an unfamiliar contributor's pull request — not more suspicion, just the same standard — is what keeps development speed from becoming security debt.
Some of this review is well-suited to automation. Strata Security is one example of a platform built to run secret detection, dependency CVE checks, and SAST pattern matching automatically on every repository push — catching a meaningful share of the issues in this article before a human reviewer ever opens the diff, and leaving their time for the judgment calls automation genuinely can't make. See Repository Security Scanner for the technical detail on how that scanning works.
Apply This With the Decision Center
This article supports For AppSec Teams — see the product page for how Strata applies these ideas directly.
Visit For AppSec Teams →More From the Knowledge Center
- How Engineering Managers Quantify Application Security Risk
- CI/CD Security Controls Every Engineering Team Should Have
- Repository Security Assessment Checklist
- APK Static Analysis, Explained
- IPA Security Testing, Explained
- Vulnerability Prioritization Beyond CVSS
- Hardcoded Secrets: Why They Still Reach Production and How Attackers Find Them