| title | Quick Start: Security for AI Assisted Coding | ||||
|---|---|---|---|---|---|
| slug | quick-start-ai-coding | ||||
| category | ai | ||||
| depth | 1 | ||||
| audit_level |
|
||||
| last_reviewed | 2026-04-21 | ||||
| sources |
|
||||
| triggers_strong |
|
||||
| triggers_weak |
|
||||
| related |
|
Last reviewed: 2026-04-03 | Next review: 2026-10-03 | Priority: Essential | Automation: Partial (Gitleaks + Semgrep cover most patterns; logic review manual)
You are using Claude Code, Codex, Gemini CLI, Cursor, Copilot, ChatGPT, or another AI assistant / builder to ship fast. This guide tells you what to check before you ship, in order of importance.
Code generated by AI is optimized for working, not for secure. Always review before pushing.
AI assistants produce vulnerable code regularly:
- Hardcoded API keys in examples
eval()orexec()with user input- No authorization check on endpoints
- Passwords hashed with SHA-256
verify=Falseon HTTPS calls "to get it working"- SQL built by string concatenation
pickle.loads()oryaml.load()without safe mode
These patterns appear because they work in isolation. They are not safe in production.
# Scan right now
git diff --cached | grep -iE "^\+.*(api.?key|secret|password|token|sk-|ghp_|AKIA|sk_live)"If you find one: see references/ops/secret-leak-prevention.md.
git status | grep "\.env"
# Should return nothingIf it appears: echo ".env" >> .gitignore && git rm --cached .env
Check for these patterns in code you generated or pasted:
| Pattern | Language | Risk |
|---|---|---|
eval( |
JS/Python/PHP | Code injection |
exec( |
Python | Code injection |
shell=True |
Python subprocess | Command injection |
os.system( |
Python | Command injection |
innerHTML = |
JavaScript | XSS |
pickle.loads( |
Python | Remote code execution |
yaml.load( without Loader=yaml.SafeLoader |
Python | Remote code execution |
verify=False |
Python requests | MITM attack |
| SQL string + user input | Any | SQL injection |
For each new route the AI generated, ask: "Can any logged-in user call this, or only the owner?"
# AI often generates this - no ownership check
@app.get("/invoices/{id}")
def get_invoice(id: int, user=Depends(auth)):
return db.query(Invoice).filter(Invoice.id == id).first()
# any user can read any invoice by changing the ID
# This is what you need
def get_invoice(id: int, user=Depends(auth)):
invoice = db.query(Invoice).filter(
Invoice.id == id,
Invoice.owner_id == user.id # ownership check
).first()
if not invoice:
raise HTTPException(404)
return invoiceAI often generates MD5, SHA-256, or SHA-512. None of these are acceptable for passwords.
# What AI gives you (wrong)
import hashlib
hashed = hashlib.sha256(password.encode()).hexdigest()
# What you need
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(password)If the AI generated a frontend file that calls OpenAI, Anthropic, Stripe, or any service directly from the browser using a secret key, that key will be visible to every user who opens devtools.
Never acceptable in frontend: OpenAI key, Anthropic key, Stripe secret key, Supabase service_role, any OAuth client secret.
Move the call to a backend route. The frontend calls your backend; your backend calls the external API.
# What AI often gives you
return {"error": str(e), "traceback": traceback.format_exc()}
# What you need
logger.error(f"Error: {e}", exc_info=True)
return {"error": "Something went wrong.", "id": error_id}AI often generates frontend validation but skips server-side validation. Attackers call the API directly.
# Minimum: validate types and length on every endpoint
from pydantic import BaseModel, Field
class CreatePost(BaseModel):
title: str = Field(min_length=1, max_length=200)
content: str = Field(min_length=1, max_length=10000)
# status not in the model - user cannot set it# AI sometimes adds this to "fix" SSL errors in dev
requests.get(url, verify=False) # remove this before prod
# Fix the certificate properly, or pass the CA bundle:
requests.get(url, verify="/path/to/ca-bundle.crt")npm audit --audit-level=high
pip-auditIf you are using an AI IDE assistant, a browser coding tool, or a no-code / low-code platform, add these checks:
- Preview links should not expose internal tools, admin panels, or customer data.
- Demo mode is not a security control.
- Use a service account, not a founder or employee mailbox.
- Record token owner, scope, and rotation date.
- Use synthetic or masked data.
- Never upload HR, finance, support exports, or legal files blindly.
- Verify signatures and timestamps.
- Review trigger conditions so untrusted email/form data cannot launch sensitive actions.
- If there is no versioned export or Git sync, recovery and peer review are weak.
Full guide: references/ai/ai-ide-no-code-security.md
| What you might think | Reality |
|---|---|
| "The repo is private" | Private repos can be made public, leaked, or accessed by collaborators. A secret in Git history is compromised. |
| "I deleted the commit" | Deletion does not remove from history. Force-push + filter-repo is needed. And GitHub may have cached it. |
| "It's just a test key" | Test keys often have the same access as production keys. They also train you to accept bad habits. |
| "CORS protects my API" | CORS only affects browsers. curl, Postman, and backend scripts bypass it entirely. |
| "The frontend is minified" | Minified JavaScript is trivially readable with browser devtools. Any secret inside is visible. |
| "It's behind a VPN" | VPNs control network access, not application authorization. IDOR and injection still work inside a VPN. |
| "The endpoint is not documented" | Endpoints are discovered by scanners, JS bundle analysis, and Certificate Transparency logs. |
| "We use HTTPS" | HTTPS encrypts transit. It does not protect against XSS, CSRF, IDOR, injection, or weak passwords. |
- Revoke the key immediately (do not wait to clean history first).
- Check usage logs for unauthorized calls.
- Clean history with
git filter-repo. - Force push.
- Full guide:
references/ops/secret-leak-prevention.md
- Add a generic error handler. See
references/appsec/production-error-handling.md.
- Check
references/appsec/language-patterns.mdfor your language. - Replace with parameterized queries or safe equivalents.
- Check
references/iam/authorization-rbac.mdfor IDOR patterns. - Test manually: log in as user A, try to access user B's resources by changing IDs.
- Migrate hashes: on next login, re-hash with Argon2id and store the new hash.
- Never re-hash without the plaintext (you need the user to log in again).
# 1. Create .gitignore with security template
curl -o .gitignore https://raw.githubusercontent.com/stealthsrc/security-hardening/main/.gitignore-security-template
# 2. Create .env.example (edit with placeholder values, commit this)
# Create .env (real values, never commit)
cp .env.example .env
# edit .env with real values
# 3. Install Gitleaks pre-commit hook
pip install pre-commit
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks
EOF
pre-commit install
# 4. Copy security CI workflow
mkdir -p .github/workflows
# Copy .github/workflows/security.yml from this repo
# 5. Verify no secrets are already staged
gitleaks detect --source . --verbosePrint or bookmark this.
BEFORE EVERY PUSH:
[ ] No API key / token / password in code or .env committed
[ ] No eval/exec/shell=True/pickle.loads on user input
[ ] Every endpoint checks auth AND ownership
[ ] Passwords use Argon2id or bcrypt
[ ] No secret in frontend code
[ ] Error responses are generic (no stack trace)
[ ] Input validated server-side
[ ] verify=False removed
[ ] npm audit / pip-audit clean
[ ] .env not in git status