Vibe Coding and Security: How to Secure AI-Assisted Applications
AI-assisted development accelerates delivery, but generated code requires engineering review, testing, threat modelling, and security validation before production. Practical security guidance for developers using AI coding tools.
Vibe Coding and Security: How to Secure AI-Assisted Applications
1. Introduction: Vibe Coding Changed How We Build Software
A developer describes an API endpoint, authentication flow, database model, or frontend component to an AI assistant and receives working code within seconds. The question is no longer simply "Can AI write the code?" The more important question is "Do we understand and trust the code that we are shipping?"
"Vibe coding" — a term popularized in early 2025 by AI researcher Andrej Karpathy — describes the practice of describing a desired outcome to an AI assistant and iteratively refining the generated code until it works. What started as a casual way to prototype personal projects has grown into a mainstream development practice. AI coding assistants now handle everything from boilerplate and test generation to full feature implementation. According to GitHub's 2024 annual report, developers using Copilot complete tasks up to 55% faster on average. But speed alone doesn't guarantee security.
The real question is whether the acceleration introduces new risk, and if so, how developers can mitigate it without sacrificing the productivity gains.
2. What Exactly Is Vibe Coding?
"Vibe coding" can describe different levels of developer involvement. At one end, a developer might use an AI assistant to suggest a function name or refactor a method — a productivity nudge. At the other end, an iterative process where the developer provides natural-language prompts, the AI generates code, the developer tests it, feeds back feedback, and the AI regenerates. Agentic coding workflows go further: the AI executes shell commands, installs packages, runs tests, and can even push branches autonomously.
Key practices that vibe coding encompasses:
- AI-assisted programming — the AI suggests single functions, classes, or snippets within the developer's editor.
- Natural-language prompts — the developer describes a requirement in plain language, and the AI generates corresponding code.
- Iterative code generation — a back-and-forth loop where the developer reviews, provides feedback, and the AI regenerates.
- AI-generated debugging — the developer explains a bug, and the AI proposes a fix.
- AI-generated refactoring — the developer asks the AI to restructure code while preserving behavior.
- AI-generated tests — the developer asks the AI to write unit tests, integration tests, or property-based tests.
- Agentic coding workflows — the AI operates with elevated permissions, executing commands, installing packages, and managing branches with minimal human oversight.
The terminology is still evolving, and "vibe coding" can describe different levels of developer involvement. The security concern focuses on the latter: unreviewed, AI-generated software deployed without understanding its security assumptions.
3. Is AI-Generated Code Actually Less Secure?
The short answer: AI-assisted development does not inherently make code less secure, but it changes the risk profile. Security problems can arise because:
- Developers may accept code they do not fully understand.
- Generated code may contain insecure patterns.
- Developers may omit security requirements from prompts.
- AI may recommend unnecessary dependencies.
- Generated examples may rely on insecure defaults.
- Authentication and authorization logic may be incomplete.
- Developers may focus on functionality rather than threat models.
- AI-assisted development can increase the speed at which both good and bad code is produced.
These problems are not unique to AI. Traditional human-written software has always suffered from security vulnerabilities. The OWASP Top 10 has tracked the same categories for years. The difference is workflow and scale: AI can produce code at a rate and volume that makes manual review infeasible if the developer doesn't have a structured process.
The core argument remains: AI can accelerate software development, but it does not remove the developer's responsibility for the security of the software being shipped.
4. Common Security Problems in AI-Assisted Applications
Broken Access Control
Authentication answers "Who are you?" Authorization answers "What are you allowed to do?" A common failure is assuming that authentication automatically implies authorization. Consider this API endpoint:
GET /api/users/123If the backend simply retrieves the user ID from the request without verifying that the authenticated user owns that resource, an authenticated attacker can access any user's data — an Insecure Direct Object Reference (IDOR) or Broken Object Level Authorization (BOLA) vulnerability. This CWE-639 pattern appears frequently in AI-assisted implementations because the developer may ask the AI to "create an endpoint that returns user data" without specifying that the request must be authorized against the authenticated user's ID.
A safer implementation validates ownership server-side:
# Vulnerable: trusts the client-supplied ID
def get_user(request, user_id):
return User.query.get(user_id) # No auth check
# Safer: verifies the authenticated user owns the resource
def get_user(request, user_id):
user = User.query.get(user_id)
if user != request.auth.user:
raise PermissionDenied("You do not own this resource")
return userInjection
AI coding assistants may recommend code that interpolates user input directly into SQL, shell commands, or API queries — classic injection territory. Consider this Python example:
# Vulnerable: raw SQL with string interpolation
query = f"SELECT * FROM users WHERE name = '{user_input}'"
# Result: an attacker can inject ' OR '1'='1' to bypass authentication
# Safer: parameterized query
query = "SELECT * FROM users WHERE name = ?"
result = db.execute(query, (user_input,))The vulnerable version is dangerous because it allows an attacker to read arbitrary data, modify/delete data, or in some cases execute administrative operations. The parameterized version separates code from data, ensuring user input is always treated as a value, not executable code.
Injection is not unique to AI — human-written code has contained SQL injection since the 1990s — but AI assistants may suggest interpolated strings more frequently because they prioritize concise, readable examples that work in demonstrations.
Cross-Site Scripting
Unsafe rendering of user-supplied data into HTML is a persistent problem. If an AI-generated template directly interpolates a variable without escaping:
<!-- Vulnerable: direct interpolation -->
<h1>Hello, {{ username }}</h1>
<!-- Safer: framework-escaped rendering -->
<h1>Hello, {{ username | e }}</h1>The unescaped version allows an attacker who stores a malicious script in a user profile to execute it in every viewer's browser — a stored XSS vulnerability. Frameworks vary in their default escaping behavior; some require explicit opt-out of escaping, others require explicit opt-in. The developer must know which category their framework falls into.
Cross-Site Request Forgery
CSRF tricks a victim into submitting an unwanted request to a web application where they're authenticated. Modern frameworks mitigate this with synchronizer tokens, same-site cookie attributes, and anti-CSRF frameworks. However, API-only applications typically don't use cookies for authentication and are naturally resistant to traditional CSRF. The developer should still verify that their threat model includes the appropriate protections for their architecture.
Server-Side Request Forgery
An application that fetches user-supplied URLs is vulnerable to SSRF. Consider this Node.js example:
// Vulnerable: fetches user-supplied URL without validation
async function fetchData(req) {
const url = req.query.url;
const response = await fetch(url);
return response.json();
}An attacker can force the server to request internal services (e.g., http://127.0.0.1/metadata), access cloud metadata endpoints, or scan internal networks. A safer approach validates the URL against an allowlist:
// Safer: URL allowlist validation
const ALLOWED_PROTOCOLS = ['https://api.example.com'];
async function fetchData(req) {
const url = new URL(req.query.url);
if (!ALLOWED_PROTOCOLS.some(p => url.hostname.endsWith(p.split('/')[2]))) {
throw new Error("URL not allowed");
}
const response = await fetch(url);
return response.json();
}Insecure File Uploads
Applications that accept user-uploaded files must validate type, content, storage location, naming, and size. A vulnerable implementation might save any file to a web-accessible directory with the original filename:
# Vulnerable: saves any file with original name
uploaded_file.save(f"/var/www/uploads/{uploaded_file.filename}")An attacker could upload a PHP web shell, execute it, and gain control of the server. A safer approach:
# Safer: validate type, rename, store outside web root
import magic, os, uuid
def save_upload(uploaded_file):
# Validate MIME type
mime = magic.from_buffer(uploaded_file.read(2048), mime=True)
if not mime.startswith('image/'):
raise ValueError("Only images allowed")
# Read back and reset pointer
uploaded_file.seek(0)
# Generate safe filename
ext = mime.split('/')[-1]
safe_name = f"{uuid.uuid4()}.{ext}"
path = f"/var/data/uploads/{safe_name}"
uploaded_file.save(path)
# Set restrictive permissions
os.chmod(path, 0o640)
return safe_nameAuthentication Problems
Common authentication weaknesses in AI-assisted implementations include password hashing issues (plaintext or weak hashes), missing brute-force protection, inadequate session management, and MFA bypasses. The developer should ensure that:
- Passwords are hashed using a adaptive function (argon2, bcrypt, scrypt)
- Session tokens are regenerated after authentication
- Rate limiting protects against brute-force
- Password reset flows are secure and verified
- Tokens are transmitted over HTTPS with the
SecureandHttpOnlyattributes
Secrets Exposure
Hardcoding secrets into source code or pasting them into AI prompts is a well-documented anti-pattern. API keys, database credentials, JWT signing keys, and cloud credentials should never be committed to source control or included in prompts that the AI retains. The safest approach is:
- Use environment variables or secret management tools (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- Reference secrets at runtime rather than embedding them
- Rotate credentials regularly
- Audit for leaked credentials using tools like Gitleaks or TruffleHog
5. Never Trust the Frontend
The frontend runs on the user's device and is fully controlled by them. A dangerous assumption might look like this in a template:
{/* ❌ Bad: assumes frontend controls admin status */}
{isAdmin && <AdminPanel />}The backend must independently determine authorization. Even if the frontend hides the admin panel, an attacker can modify the frontend JavaScript or send a direct API request with isAdmin: true. Server-side authorization should always be the source of truth:
// Frontend only
{if (user.role === 'admin') showAdmin()}
// ✅ Backend + frontend
// Backend: always check authorization
// Frontend: shows UI only after server confirmsThis trust boundary — the point where control transitions from the server to the client — is where most application security boundaries should be drawn.
6. Authentication Is Not Authorization
Authentication answers "Who are you?" Authorization answers "What are you allowed to do?" A developer can correctly authenticate a user but still accidentally expose another user's data. Consider this Flask example:
# Authentication: verify the user's credentials
login_user(user)
# Authorization: accidentally skip the check
# ❌ Bad: just because user is logged in doesn't mean they can see any record
records = Record.query.all() # ALL records, not just the user's
# ✅ Better: filter by authenticated user
records = Record.query.filter_by(user_id=current_user.id).all()The principle extends beyond simple row-level access. Role-based access control (RBAC), attribute-based access control (ABAC), and object-level authorization should all be enforced server-side, independently of any frontend checks.
7. Be Careful What You Give Your AI Assistant
Developers should carefully consider whether they are exposing:
- API keys and secrets
- Production database credentials
- Private cryptographic keys
- Customer information or personally identifiable data
- Proprietary source code or internal architecture diagrams
- Production logs containing stack traces or credentials
- Authentication tokens and session identifiers
Review the provider's current privacy, retention, security, and enterprise policies. Some AI coding tools offer enterprise plans with data retention guarantees and opt-outs for training on customer code. Even with those guarantees, assume that anything you include in a prompt could potentially be seen by the AI provider's operators or exposed in certain failure modes. When in doubt, omit sensitive details and ask the AI to generate code without them.
8. AI-Generated Dependencies and Supply-Chain Risk
AI can recommend libraries and packages — sometimes ones the developer hasn't explicitly requested. Before accepting any dependency, ask:
- Do I actually need this dependency? Can I use a standard-library solution instead?
- Is the project actively maintained? When was the last release?
- Is it reputable? Does it have a significant user base and trusted maintainers?
- Does it have known vulnerabilities? Check with Software Composition Analysis, dependency scanners, or OSV databases.
- What dependencies does it introduce? A single package may pull in dozens of transitive dependencies.
- What permissions does it require? Some packages request capabilities far beyond their stated purpose.
- Is there a standard-library solution? Many common tasks (UUID generation, date parsing, encryption) have built-in or standard alternatives.
- Is the package name correct? Typosquatting — publishing malicious packages with names similar to popular ones — is a known supply-chain attack.
Developers should:
- Pin dependencies to specific versions in lockfiles (
package-lock.json,requirements.txt,Cargo.lock). - Enable automated dependency updates (Dependabot, Renovate, GitHub Dependabot).
- Run dependency scanning as part of the CI/CD pipeline.
- Generate a Software Bill of Materials (SBOM) for production deployments.
- Review transitive dependencies, not just direct ones.
9. Security Testing for Vibe-Coded Applications
Security testing should be integrated throughout the development lifecycle, not tacked on at the end:
- Unit tests — verify individual pieces of application logic. AI-generated code should have unit tests written for it, just as human-written code does.
- Integration tests — verify interactions between components. AI-generated code that connects to databases, calls external APIs, or modifies state should have integration tests.
- SAST — Static Application Security Testing scans source code for known vulnerability patterns without executing it. Tools include Semgrep, CodeQL, and GitHub Advanced Security.
- DAST — Dynamic Application Security Testing tests a running application for vulnerabilities. Tools include OWASP ZAP, Burp Suite, and automated CI pipelines.
- Dependency scanning — identifies vulnerable or outdated dependencies. GitHub Dependabot, Snyk, and Trivy are common choices.
- Secret scanning — detects credentials accidentally committed to source control. Gitleaks, TruffleHog, and GitHub secret scanning find these automatically.
- Container scanning — when applications use containers, scan images for vulnerabilities. Trivy, Anchore, and Grype are common.
- Manual security testing — automated tools do not replace human reasoning. A developer should review authentication flows, authorization checks, input validation, and data flow diagrams.
5. Security-by-Design Prompting
Developers can improve AI-assisted development by explicitly asking the AI to consider security. Better prompts communicate security requirements:
- "Implement this endpoint using server-side authorization and validate that the authenticated user owns the requested resource."
- "Generate this SQL using parameterized queries; do not interpolate user input."
- "Implement password hashing using Argon2id with a work factor of at least 15; do not store passwords in plaintext."
- "Create this API with rate limiting of 100 requests per minute per IP; include retry-after headers."
However, important caveat: adding "make this secure" to an AI prompt does not guarantee secure code. The AI may still produce flawed implementations. Prompt improvements reduce risk but do not eliminate the need for engineering review, testing, and security validation.
7. A Secure Vibe-Coding Workflow
A practical workflow developers can follow:
Step 1 — Define requirements, including security and privacy requirements.
Step 2 — Identify sensitive data and trust boundaries.
Step 3 — Define authentication and authorization requirements.
Step 3 — Ask the AI to propose an implementation, explicitly including security requirements in the prompt.
Step 5 — Review generated code. Can you explain every line? If not, ask the AI to clarify.
Step 6 — Review dependencies. Are they necessary? Are they maintained? Are there known vulnerabilities?
Step 7 — Write tests. Unit tests, integration tests, and property-based tests where appropriate.
Step 8 — Run security scanners. SAST, DAST, dependency scanning, and secret scanning as part of the CI pipeline.
Step 9 — Perform manual security testing. Review authentication flows, authorization checks, input validation, and data flow diagrams.
Step 10 — Review configuration and secrets. Ensure no credentials are hardcoded and that environment-appropriate settings are used.
Step 11 — Deploy with least privilege. The application should only have the permissions it absolutely needs.
Step 12 — Monitor the application. Log security-relevant events and set up alerts for anomalies.
Step 13 — Patch and review continuously. Schedule regular dependency updates, security reviews, and threat model revisions.
10. Production Security Checklist
A concise practical checklist for production deployment:
- HTTPS — enforce TLS everywhere; redirect HTTP to HTTPS.
- Secure cookies —
Secure,HttpOnly,SameSite=StrictorSameSite=Laxattributes; avoidSameSite=Noneunless absolutely required. - Security headers —
Content-Security-Policy,X-Content-Type-Options: nosniff,X-Frame-Options: DENYorSAMEORIGIN,Referrer-Policy. - Input validation — validate all input on the server side; reject malformed data early.
- Output encoding — encode all output based on context (HTML entity encoding, JavaScript escaping, URL encoding).
- Authentication — use established libraries and protocols (OAuth 2.0, OpenID Connect); never roll your own crypto.
- Authorization — enforce server-side, role-based or attribute-based access control; verify every request.
- Rate limiting — protect against abuse and automated attacks; implement exponential backoff.
- Logging — record security-relevant events (authentication failures, authorization violations, suspicious patterns); retain logs securely.
- Monitoring — set up alerts for anomalous activity, failed authentication spikes, and error rate increases.
- Backups — regular, tested backups of application data and configuration; verify restore procedures.
- Secret management — never hardcode secrets; use dedicated secret management; rotate regularly.
- Dependency updates — schedule regular dependency reviews and updates; use automated tools.
- Least privilege — databases, file systems, and APIs should operate with the minimum permissions necessary.
- Database permissions — application accounts should have only the specific tables, rows, and operations they need.
- Error handling — do not expose stack traces or internal details to end users; log them internally.
- CORS configuration — restrict to known origins; avoid
*where possible. - File upload security — validate type, content, size; store outside web root; scan for malware.
- API security — enforce authentication and authorization on all API endpoints; use rate limiting and throttling.
- Environment separation — development, staging, and production should have separate configurations and secrets.
11. What Developers Should Remember
If the browser can modify it, don't trust it.
If the application doesn't need the permission, don't grant it.
If a secret is in source code, assume it can eventually leak.
If you don't understand generated code, don't blindly deploy it.
If security wasn't considered during design, fixing it later is usually harder.
These principles are deliberately concise. They're meant to be remembered, not just read.
12. Conclusion
Vibe coding is a development technique, not a security strategy. AI can help developers move faster, but security still requires understanding, threat modelling, code review, testing, least privilege, secure configuration, monitoring, and continuous maintenance. The developers who thrive with AI-assisted development are those who treat AI as a powerful pair-programmer — not a replacement for engineering judgment.
The prompt that starts with "Create an API endpoint" and ends with git push main may work, but it's a workflow that defers security to later — if ever. The developers who thrive will be the ones who build security into every step, from the first prompt to ongoing production monitoring.
