DEV Community

문세환
문세환

Posted on

How to add a security gate to your vibe-coding workflow (5 minutes)

You're vibe-coding. Claude or GPT writes your backend in 10 minutes. It works. You ship it.

Three weeks later: SQL injection in production. Save function that never saved. Async endpoint blocking the event loop.

Here's how to add a security gate in 5 minutes that catches these before they ship.


The problem with AI-generated code

AI models generate code that looks correct. It passes type checks. It runs. It returns the right status code.

But structurally, it has consistent failure modes:

# Looks fine. Breaks everything.
async def get_user(user_id: str):
    result = db.query(f"SELECT * FROM users WHERE id = '{user_id}'")
    return result

# save() that doesn't save
def save_preferences(user_id: str, prefs: dict):
    validated = validate(prefs)
    return {"status": "saved", "user": user_id}   # no INSERT anywhere

# async with no await
async def send_notification(msg: str):
    requests.post(WEBHOOK_URL, json={"text": msg})  # blocks event loop
Enter fullscreen mode Exit fullscreen mode

These aren't caught by mypy, pylint, or Bandit. They're structural patterns specific to AI-generated code.


Step 1: Scan locally (30 seconds)

curl -X POST https://pleasing-transformation-production-90c2.up.railway.app/v1/scan \
  -H "X-API-Key: vg_free_test" \
  -F "file=@app.py"
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "passed": false,
  "block_count": 2,
  "issues": [
    {
      "kind": "SQL_INJECTION_RISK",
      "severity": "BLOCK",
      "line": 3,
      "detail": "unsafe SQL formatting — use parameterized queries"
    },
    {
      "kind": "MISSING_WRITE",
      "severity": "BLOCK",
      "line": 8,
      "detail": "function 'save_preferences' has no DB write call"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

If passed: true — ship it. If not — fix the BLOCKs first.


Step 2: Add to GitHub Actions (2 minutes)

Create .github/workflows/vibeguard.yml:

name: VibeGuard Scan
on: [pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Scan changed files
        run: |
          git diff --name-only origin/main...HEAD \
            | grep -E '\.(py|js|ts|go|rb|java|php|kt)$' \
            | while read f; do
                echo "Scanning $f..."
                result=$(curl -s -X POST \
                  https://pleasing-transformation-production-90c2.up.railway.app/v1/scan \
                  -H "X-API-Key: ${{ secrets.VIBEGUARD_KEY }}" \
                  -F "file=@$f")
                echo "$result" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if not d.get('passed'):
    for i in d.get('issues',[]):
        if i['severity']=='BLOCK':
            print(f'BLOCK [{i[\"kind\"]}] line {i[\"line\"]}: {i[\"detail\"]}')
    sys.exit(1)
"
              done
Enter fullscreen mode Exit fullscreen mode

Add secret: Settings → Secrets → VIBEGUARD_KEY = vg_free_test

Every PR now gets scanned. BLOCK = PR fails. Green = safe to merge.


Step 3: Pre-commit hook (optional, 1 minute)

# .git/hooks/pre-commit
#!/bin/sh
for file in $(git diff --cached --name-only | grep -E '\.(py|js|ts|go)$'); do
    result=$(curl -s -X POST \
        https://pleasing-transformation-production-90c2.up.railway.app/v1/scan \
        -H "X-API-Key: vg_free_test" \
        -F "file=@$file")
    passed=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin)['passed'])")
    if [ "$passed" = "False" ]; then
        echo "BLOCK found in $file — run curl scan to see details"
        exit 1
    fi
done
Enter fullscreen mode Exit fullscreen mode
chmod +x .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

Now every commit is scanned before it's made.


What gets caught

Pattern Example Why AI generates it
SQL_INJECTION_RISK f"SELECT ... WHERE id='{x}'" f-strings are common in training data
MISSING_WRITE def save(): return {"ok": True} AI optimizes for "looking complete"
FAKE_ASYNC async def f(): return requests.get(url) copies async signature without understanding
CORS_WILDCARD allow_origins=["*"] + credentials copies boilerplate without understanding interaction
STUB_SKELETON def process(data): return {} placeholder that AI forgot to implement
HARDCODED_TABLE 40-key dict instead of DB query AI avoids DB setup complexity
SSRF_RISK httpx.get(user_url) unvalidated doesn't think about internal network access
PATH_TRAVERSAL open(user_path) unvalidated doesn't add boundary checks

9 languages supported: Python, JS, TS, Go, Ruby, Java, PHP, Kotlin, C/C++.


Free tier

  • vg_free_test key: full Pro features, 50 files/day
  • No signup required
  • Code not stored — processed in memory, discarded after scan

API: https://pleasing-transformation-production-90c2.up.railway.app
GitHub: https://github.com/Moonsehwan/aina-scan


The whole point: vibe-coding is fast. The gate should be faster. 30-second scan before you merge beats a 3-week postmortem.

Top comments (0)