← Back to Docs

Best Practices

Recommendations for teams that use AI agents to generate code and want to maintain security standards.

General rule

Never blindly trust the output of an AI agent. Treat generated code with the same scrutiny you would apply to a pull request from a junior developer who is unfamiliar with your stack.


Dependencies

Verify before installing

Before running pip install or npm install with dependencies suggested by an agent:

  1. Search for the package manually on pypi.org or npmjs.com.
  2. Verify the exact name: Agents frequently invent plausible names that do not exist.
  3. Check the age: Distrust packages created in the last 30 days.
  4. Check the downloads: Packages with fewer than 100 weekly downloads deserve extra scrutiny.
  5. Look for the source repository: Packages without a linked repository are suspicious.

Run vigil deps before installing

# Verificar dependencias ANTES de instalar
vigil deps

# Si hay findings criticos, corregir primero
vigil deps -f json | jq '.findings[] | select(.severity == "critical")'

Maintain a lockfile

Lockfiles (package-lock.json, poetry.lock, pip-tools with generated requirements.txt) pin exact versions and hashes. This prevents a package from being modified between runs.

Do not accept invented versions

Agents sometimes suggest versions that do not exist:

# Esto puede ser inventado por el agente
flask==99.0.0

vigil detects this with rule DEP-007, but it is good practice to verify versions manually.


Authentication and authorization

Always add auth middleware

When an agent generates endpoints, always add authentication explicitly. Agents tend to generate functional endpoints without protection.

Bad (generated by agent):

@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
    db.delete_user(user_id)
    return {"deleted": user_id}

Good (corrected):

@app.delete("/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: User = Depends(get_current_user),
):
    if not current_user.is_admin:
        raise HTTPException(403)
    db.delete_user(user_id)
    return {"deleted": user_id}

CORS: restrict origins

Never accept CORS with * in production:

# MAL
app.add_middleware(CORSMiddleware, allow_origins=["*"])

# BIEN
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://miapp.com", "https://admin.miapp.com"],
)

JWT: generate real secrets

# MAL
SECRET_KEY = "supersecret123"

# BIEN
import secrets
# Generar una vez y almacenar en variable de entorno
SECRET_KEY = os.environ["JWT_SECRET_KEY"]
# Para generar: python -c "import secrets; print(secrets.token_urlsafe(32))"

Cookies: enable all flags

response.set_cookie(
    "session",
    value=token,
    httponly=True,     # No accesible desde JavaScript
    secure=True,       # Solo enviar via HTTPS
    samesite="lax",    # Proteccion CSRF
    max_age=3600,      # Expiracion
)

Secrets and credentials

Never hardcode secrets

Even if the agent suggests “temporary” default values:

# MAL — el agente lo genera asi
DATABASE_URL = "postgresql://admin:password123@localhost:5432/mydb"
SECRET_KEY = os.getenv("SECRET_KEY", "fallback-secret-not-for-production")

# BIEN — sin defaults inseguros
DATABASE_URL = os.environ["DATABASE_URL"]  # Falla si no esta definida
SECRET_KEY = os.environ["SECRET_KEY"]

Use .env with .gitignore

  1. Create .env.example with placeholders (not real values):

    DATABASE_URL=postgresql://user:password@host:5432/dbname
    SECRET_KEY=generate-with-secrets-module
  2. Create .env with real values (never commit).

  3. Add to .gitignore:

    .env
    .env.local
    .env.production
    *.pem
    *.key
    credentials.json

Verify with vigil

# Detecta secrets placeholder, entropia baja, y valores copiados de .env.example
vigil scan src/ -C secrets

Tests

Require real assertions

A test without assertions is not a test:

# MAL — solo verifica que no crash
def test_login():
    response = client.post("/login", json={"user": "admin", "pass": "123"})

# BIEN — verifica comportamiento
def test_login():
    response = client.post("/login", json={"user": "admin", "pass": "123"})
    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data
    assert data["token_type"] == "bearer"

Verify status codes in API tests

# MAL — no verifica si la request fallo
def test_get_users():
    response = client.get("/users")
    users = response.json()
    assert len(users) > 0

# BIEN
def test_get_users():
    response = client.get("/users")
    assert response.status_code == 200
    users = response.json()
    assert len(users) > 0

Do not catch generic exceptions in tests

# MAL — oculta errores reales
def test_complex_operation():
    try:
        result = complex_operation()
    except Exception:
        pass  # El test nunca falla

# BIEN — dejar que la excepcion propague
def test_complex_operation():
    result = complex_operation()
    assert result.status == "success"

Avoid circular mocks

# MAL — el mock retorna exactamente lo que el test espera
def test_calculate():
    with patch("app.calculate") as mock:
        mock.return_value = 42
        result = app.calculate(6, 7)
        assert result == 42  # No se testo nada real

# BIEN — mockear la dependencia, no la funcion bajo test
def test_calculate():
    with patch("app.database.get_multiplier") as mock:
        mock.return_value = 2
        result = app.calculate(6, 7)
        assert result == 26  # 6 * 7 / 2 — logica real testeada

Run vigil tests regularly

# Detectar tests sin assertions, triviales, y mocks circulares
vigil tests

# Requerir al menos 2 assertions por test
vigil tests --min-assertions 2

For individual developers

  1. Pre-commit hook: vigil scan --changed-only --fail-on critical --quiet
  2. Before PR: vigil scan src/ --fail-on high
  3. Manual review: Read each finding and fix it.

For teams

  1. Shared config: Commit .vigil.yaml with the team configuration.
  2. Mandatory CI: vigil in the PR pipeline with --fail-on high.
  3. Strict production: strict config for releases.
  4. Document exceptions: If a rule is disabled, document why in the config.
# .vigil.yaml
rules:
  # Desactivado porque usamos tokens de larga duracion para webhooks
  # Aprobado por: @security-team en PR #123
  AUTH-003:
    enabled: false

Progressive scaling

Weekfail-onActive rulesGoal
1criticalDEP-001, DEP-007, SEC-001-003Only the most severe
2high+ AUTH-001, AUTH-002, AUTH-005Add auth
3high+ TEST-001, SEC-004-006Add tests and secrets
4mediumAllFull coverage

Improved prompts for agents

When using an AI agent, include security instructions in your prompt:

For dependencies

“Generate the code using only packages that exist on PyPI. Verify that the names are correct. Do not invent packages.”

For authentication

“All endpoints must have authentication. Use Depends(get_current_user) in FastAPI. DELETE and PUT endpoints require explicit authorization.”

For secrets

“Do not hardcode secrets or default values. All sensitive configurations must be read from environment variables without a default value.”

For tests

“Each test must have at least 2 meaningful assertions. Do not use assert is not None as the only assertion. Always verify the status code in API tests.”


AI-generated code review checklist

Before approving a PR with AI-generated code:

  • Run vigil scan and fix all findings.
  • Verify that all dependencies exist in the registry.
  • Confirm that there are no hardcoded secrets.
  • Verify that endpoints have auth middleware.
  • Confirm that CORS is restricted to specific origins.
  • Verify that tests have meaningful assertions.
  • Check that .env is in .gitignore.
  • Confirm that no values are copied from .env.example.