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:
- Search for the package manually on pypi.org or npmjs.com.
- Verify the exact name: Agents frequently invent plausible names that do not exist.
- Check the age: Distrust packages created in the last 30 days.
- Check the downloads: Packages with fewer than 100 weekly downloads deserve extra scrutiny.
- 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
-
Create
.env.examplewith placeholders (not real values):DATABASE_URL=postgresql://user:password@host:5432/dbname SECRET_KEY=generate-with-secrets-module -
Create
.envwith real values (never commit). -
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
Recommended workflow
For individual developers
- Pre-commit hook:
vigil scan --changed-only --fail-on critical --quiet - Before PR:
vigil scan src/ --fail-on high - Manual review: Read each finding and fix it.
For teams
- Shared config: Commit
.vigil.yamlwith the team configuration. - Mandatory CI: vigil in the PR pipeline with
--fail-on high. - Strict production:
strictconfig for releases. - 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
| Week | fail-on | Active rules | Goal |
|---|---|---|---|
| 1 | critical | DEP-001, DEP-007, SEC-001-003 | Only the most severe |
| 2 | high | + AUTH-001, AUTH-002, AUTH-005 | Add auth |
| 3 | high | + TEST-001, SEC-004-006 | Add tests and secrets |
| 4 | medium | All | Full 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 scanand 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
.envis in.gitignore. - Confirm that no values are copied from
.env.example.