Introducción
Grafana es una de las plataformas de observabilidad de código abierto más populares, con más de 70k estrellas en GitHub y miles de colaboradores mejorándola cada día. Con más de 3k incidencias abiertas y cientos de 'pull requests' constantemente en movimiento, mantener la base de código limpia y consistente es un verdadero desafío.
Estudiando su base de código, podemos descubrir algunas de las reglas no escritas y mejores prácticas que ayudan al equipo a mantener una alta calidad mientras se avanza rápidamente. Muchas de estas reglas se centran en la seguridad, la mantenibilidad y la fiabilidad. Algunas de ellas abordan problemas que las herramientas de análisis estático tradicionales (SAST) no pueden detectar, como el uso incorrecto de async, las fugas de recursos o los patrones inconsistentes en el código. Estos son los tipos de problemas que los revisores humanos o las herramientas impulsadas por IA pueden detectar durante una revisión de código.
Los desafíos
Proyectos grandes como este enfrentan varios desafíos: volumen masivo de código, muchos módulos (API, UI, plugins) e innumerables integraciones externas (Prometheus, Loki, etc.). Cientos de colaboradores pueden seguir diferentes estilos de codificación o suposiciones. Las nuevas funcionalidades y las correcciones rápidas pueden introducir errores ocultos, fallos de seguridad o rutas de código confusas. Los revisores voluntarios pueden no conocer cada parte de la base de código, lo que lleva a pasar por alto patrones de diseño o mejores prácticas. En resumen, la escala y la diversidad de las contribuciones dificultan la aplicación de la coherencia y la fiabilidad.
¿Por qué importan estas reglas?
Un conjunto claro de reglas de revisión beneficia directamente la salud de Grafana. En primer lugar, mejora la mantenibilidad: los patrones consistentes (estructura de carpetas, nomenclatura, manejo de errores) facilitan la lectura, prueba y extensión del código. Los revisores dedican menos tiempo a adivinar la intención cuando todos siguen las convenciones comunes. En segundo lugar, se mejora la seguridad: reglas como “siempre validar la entrada del usuario” o “evitar redirecciones abiertas” previenen vulnerabilidades (CVE-2025-6023/4123, etc.) que se han encontrado en Grafana. Finalmente, la incorporación de nuevos colaboradores es más rápida: cuando los ejemplos y las revisiones utilizan consistentemente las mismas prácticas, los recién llegados aprenden el “modo Grafana” de forma rápida y segura.
Conectando el contexto con estas reglas
Estas reglas provienen de problemas reales en el código y la comunidad de Grafana. Los avisos de seguridad y los informes de errores han descubierto patrones (por ejemplo, path traversal que lleva a XSS) que convertimos en reglas preventivas. Cada regla a continuación destaca un error concreto, explica por qué es importante (rendimiento, claridad, seguridad, etc.) y muestra un fragmento claro ❌ no conforme frente a ✅ conforme en los lenguajes de Grafana (Go o TypeScript/JS).
Ahora, exploremos las 10 reglas que contribuyen a mantener la base de código de Grafana robusta, segura y comprensible.
10 reglas prácticas para la calidad del código inspiradas en Grafana
1. Utilice variables de entorno para la configuración (evite valores codificados).
Evita codificar directamente puertos, credenciales, URLs u otros valores específicos del entorno. Léelos de variables de entorno o archivos de configuración para mantener el código flexible y los secretos fuera del código fuente.
❌ No conforme:
// servidor.js
const appPort = 3000;
app.listen(appPort, () => consola.log("Escuchando en el puerto " + appPort));✅ Conforme:
// server.ts
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));Por qué es importante: El uso de variables de entorno mantiene los datos sensibles fuera del código fuente, hace que las implementaciones sean flexibles en diferentes entornos y evita fugas accidentales de secretos. También asegura que los cambios de configuración no requieran modificaciones de código, mejorando la mantenibilidad y reduciendo errores.
2. Sanitizar la entrada del usuario antes de usarla.
Toda entrada de usuarios o fuentes externas debe ser validada o saneada antes de su uso para prevenir ataques de inyección y comportamientos inesperados.
❌ No conforme:
// frontend/src/components/UserForm.tsx
const handleSubmit = (username: string) => {
setUsers([...users, { name: username }]);
};✅ Conforme:
// frontend/src/utils/sanitize.ts
export function sanitizeInput(input: string): string {
return input.replace(/<[^>]*>/g, ''); // removes HTML tags
}
// frontend/src/components/UserForm.tsx
import { sanitizeInput } from '../utils/sanitize';
const handleSubmit = (username: string) => {
const cleanName = sanitizeInput(username);
setUsers([...users, { name: cleanName }]);
};Por qué es importante: La sanitización adecuada de entradas previene XSS, ataques de inyección y comportamientos inesperados causados por entradas malformadas. Protege tanto a los usuarios como al sistema, y asegura que los procesos posteriores, el registro (logging) y el almacenamiento manejen los datos de forma segura.
3. Prevenir redirecciones abiertas y recorrido de rutas.
Asegúrate de que cualquier URL o ruta de archivo utilizada en tu código esté correctamente validada y saneada. No permitas que la entrada del usuario determine directamente las redirecciones o las rutas del sistema de archivos.
❌ No conforme:
// Express route in Grafana plugin
app.get("/goto", (req, res) => {
const dest = req.query.next; // attacker can supply any URL
res.redirect(dest);
});✅ Conforme:
// Express route with safe redirect
app.get("/goto", (req, res) => {
const dest = req.query.next;
// Only allow relative paths starting with '/'
if (dest && dest.startsWith("/")) {
res.redirect(dest);
} else {
res.status(400).send("Invalid redirect URL");
}
});Por qué es importante: Prevenir las redirecciones abiertas y el path traversal protege a los usuarios del phishing, las fugas de datos y el acceso no autorizado a archivos. Reduce la superficie de ataque, refuerza los límites de seguridad y evita la exposición accidental de recursos sensibles del servidor.
4. Habilitar una política de seguridad de contenido (CSP) estricta.
Aplica una Política de Seguridad de Contenido en las cabeceras de la aplicación que solo permita scripts, estilos, imágenes y otros recursos de fuentes de confianza. Prohíbe las fuentes unsafe-inline, eval y wildcard.
❌ No conforme: (Sin CSP o demasiado permisivo)
# grafana.ini (no conforme)
content_security_policy = false✅ Conforme: (CSP robusto en la configuración)
# grafana.ini
content_security_policy = true
content_security_policy_template = """
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;
object-src 'none';
font-src 'self';
style-src 'self' 'unsafe-inline' blob:;
img-src * data:;
base-uri 'self';
connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;
manifest-src 'self';
media-src 'none';
form-action 'self';
"""Por qué esto es importante: Una CSP estricta bloquea muchas clases de ataques del lado del cliente, incluyendo XSS. Impone un comportamiento predecible para los recursos, reduce la posibilidad de ejecución de código malicioso y proporciona un límite de seguridad claro en el contexto del navegador.
5. Manejar errores y comprobaciones de nulos (evitar pánicos).
Compruebe siempre si hay errores y valores nulos en las llamadas a funciones, las respuestas de la API y las estructuras de datos. Reemplace los fallos críticos con un manejo de errores adecuado y devuelva mensajes o códigos de error significativos.
❌ No conforme:
rows, _ := db.Query("SELECT * FROM users WHERE id=?", id) // ignored error
user := &User{}
rows.Next()
rows.Scan(&user.Name) // rows might be empty => user is nil => panic✅ Conforme:
rows, err := db.Query("SELECT * FROM users WHERE id=?", id)
if err != nil {
return nil, err
}
defer rows.Close()
if !rows.Next() {
return nil, errors.New("user not found")
}
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
user := &User{Name: name}Por qué es importante: El manejo adecuado de errores previene fallos y asegura que el sistema se mantenga fiable incluso cuando ocurren entradas o condiciones inesperadas. Mejora la mantenibilidad, reduce el tiempo de inactividad y facilita la depuración al proporcionar información de error significativa.
6. Aplazar la limpieza de recursos (prevenir fugas).
Asegúrate de que todos los recursos abiertos, como archivos, conexiones de red o manejadores de bases de datos, se cierren correctamente utilizando 'defer' inmediatamente después de la asignación. No dependas de la limpieza manual más adelante en el código.
❌ No conforme:
resp, err := http.Get(url)
// ... usa resp.Body ...
// olvidó: resp.Body.Close()✅ Conforme:
resp, err := http.Get(url)
if err != nil {
// handle error
}
defer resp.Body.Close()
// ... use resp.Body ...Por qué es importante: Una limpieza adecuada previene fugas de memoria, el agotamiento de descriptores de archivo y la saturación del pool de conexiones. Esto mantiene la estabilidad del sistema, evita la degradación del rendimiento con el tiempo y reduce los problemas operativos en producción.
7. Utilizar consultas parametrizadas (evitar la inyección SQL).
Utilice siempre consultas parametrizadas o sentencias preparadas al interactuar con la base de datos en lugar de la concatenación de cadenas para los comandos SQL.
❌ No conforme:
// Peligroso: userID podría contener una cita SQL o una inyección
query := "DELETE FROM sessions WHERE user_id = '" + userID + "';"
db.Exec(query)✅ Conforme:
// Seguro: userID se pasa como parámetro
db.Exec("DELETE FROM sessions WHERE user_id = ?", userID)Por qué es importante: Las consultas parametrizadas previenen los ataques de inyección SQL, una de las vulnerabilidades de seguridad más comunes. Protegen los datos sensibles, reducen el riesgo de corrupción de la base de datos y hacen que las consultas sean más mantenibles y fáciles de auditar. Esto garantiza tanto la seguridad como la fiabilidad de su aplicación.
8. Utilizar async/await correctamente en TypeScript (gestionar promesas).
Siempre espera las promesas y gestiona los errores utilizando `try/catch` en lugar de ignorar los rechazos o mezclar la gestión de estilo `callback`.
❌ No conforme:
async function fetchData() {
// Missing await: fetch returns a Promise, not the actual data
const res = fetch('/api/values');
console.log(res.data); // undefined
}✅ Conforme:
async function fetchData() {
try {
const res = await fetch('/api/values');
const data = await res.json();
console.log(data);
} catch (err) {
console.error("Fetch failed:", err);
}
}Por qué es importante: El manejo adecuado de la asincronía asegura que los errores en el código asíncrono no pasen desapercibidos, previene los rechazos de promesas no gestionados y mantiene un flujo de programa predecible. Hace que el código sea más legible, fácil de depurar y previene errores sutiles que pueden conducir a la corrupción de datos, estados inconsistentes o fallos inesperados en tiempo de ejecución.
9. Favorecer los tipos estrictos en TypeScript (evitar 'any').
Utilice tipos de TypeScript precisos en lugar de 'any' para definir variables, parámetros de función y tipos de retorno.
❌ No conforme:
// No types specified
function updateUser(data) {
// ...
}
let config: any = loadConfig();✅ Conforme:
interface User { id: number; name: string; }
function updateUser(data: User): Promise<User> {
// ...
}
interface AppConfig { endpoint: string; timeoutMs: number; }
const config: AppConfig = loadConfig();Por qué es importante: El tipado estricto detecta errores relacionados con tipos en tiempo de compilación, reduciendo los errores en tiempo de ejecución y mejorando la fiabilidad del código. Hace que el código se autodocumente, sea más fácil de refactorizar y asegura que todas las partes del sistema interactúen de una manera predecible y segura en tipos, lo cual es crucial en bases de código grandes y complejas como las de Grafana.
10. Aplicar un estilo de código y una nomenclatura consistentes.
Aplicar un formato uniforme, convenciones de nomenclatura y estructuras de archivos en toda la base de código.
❌ No conforme: (estilos mixtos)
const ApiData = await getdata(); // PascalCase for variable? function name not camelCase.
function Fetch_User() { ... } // Unusual naming.✅ Conforme:
const apiData = await fetchData();
function fetchUser() { ... }Por qué esto es importante: Un estilo y una nomenclatura consistentes mejoran la legibilidad y facilitan que múltiples colaboradores entiendan y mantengan el código. Reduce la carga cognitiva al navegar por el proyecto, previene errores sutiles causados por malentendidos y asegura que las herramientas automatizadas (linters, formateadores, revisores de código) puedan aplicar de forma fiable los estándares de calidad en un entorno de equipo grande.
Conclusión
Cada regla anterior aborda un desafío recurrente en la base de código de Grafana. Aplicarlas de forma consistente durante las revisiones de código ayuda al equipo a mantener un código limpio y predecible, mejorar la seguridad al prevenir vulnerabilidades comunes y facilitar la incorporación de nuevos colaboradores al proporcionar patrones claros. A medida que el proyecto escala, estas prácticas mantienen la base de código fiable, mantenible y más fácil de navegar para todos los implicados. Seguir estas reglas puede ayudar a cualquier equipo de ingeniería a construir y mantener software de alta calidad a escala.
.avif)
