Análisis estático y auditoría con ShellCheck
Integre ShellCheck en una barrera de seguridad e interprete sus resultados para reforzar cada script.
Análisis estático y auditoría con ShellCheck es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.
Qué es ShellCheck y por qué es importante
ShellCheck es una herramienta de análisis estático de código abierto para scripts de shell. Analiza el código fuente de Bash (y de POSIX sh, dash y ksh) sin ejecutarlo e informa de errores, construcciones inseguras, problemas de portabilidad y problemas de estilo; cada uno lleva una etiqueta con un código de regla único, como SC2086.
En un flujo reforzado de seguridad, ShellCheck actúa como una barrera obligatoria: no se publica ningún script hasta que la supera. Esto es importante porque:
- Muchas vulnerabilidades del shell (división de palabras, inyección y expansiones sin entrecomillar) son invisibles durante las pruebas del flujo correcto, pero se activan con entradas controladas por un atacante.
- ShellCheck detecta estas clases de errores antes del tiempo de ejecución y sin coste.
- Documenta por qué cada patrón es peligroso, lo que aumenta con el tiempo la concienciación del equipo.
Instálelo en cualquier sistema:
# Debian / Ubuntu
sudo apt-get install shellcheck
# macOS (Homebrew)
brew install shellcheck
# From source via Cabal (any platform)
cabal update && cabal install ShellCheck
# Verify
shellcheck --versionEjecutar ShellCheck por primera vez
La invocación más sencilla es shellcheck <script>. ShellCheck lee la línea shebang para determinar el dialecto del shell y, a continuación, muestra los resultados en stdout.
Cada resultado incluye:
- Archivo y número de línea: ubicación exacta
- Gravedad:
error,warning,infoostyle - Código SC: identificador estable de la regla que puede consultar o suprimir
- Explicación para humanos: indica qué está mal y, a menudo, cómo corregirlo
Ejecute el script siguiente y observe el resultado que produciría ShellCheck:
#!/usr/bin/env bash
# demo_bad.sh — intentionally flawed for ShellCheck demonstration
FILE=$1
if [ $FILE == '' ]; then
echo "No file given"
fi
cat $FILE | grep 'error' | wc -lInterpretar los resultados y códigos SC de ShellCheck
Para el script de la escena anterior, ShellCheck mostraría resultados como los siguientes:
SC2086(warning): Entrecomille con comillas dobles para evitar la expansión de comodines y la división de palabras, en$FILEdentro de[ $FILE == '' ]y encat $FILE.SC2039/SC3010(info): == en [ ] es un bashismo; use = para POSIX.SC2002(style): Cat innecesario. Considere cmd < file en lugar de cat file | cmd.
Cada código SC corresponde a una página wiki en https://www.shellcheck.net/wiki/SCxxxx, con la justificación y un ejemplo corregido.
La versión corregida de ese script:
#!/usr/bin/env bash
# demo_fixed.sh — ShellCheck-clean
FILE="$1"
if [ -z "$FILE" ]; then
echo "No file given" >&2
exit 1
fi
grep -c 'error' "$FILE"Niveles de gravedad y acciones recomendadas
ShellCheck clasifica cada resultado según su gravedad. En una barrera de seguridad, debe tratarlos de la siguiente manera:
- error: casi con toda seguridad es un error o un agujero de seguridad. Bloquee la compilación. Corríjalo inmediatamente. Ejemplos:
SC2148, que indica que falta el shebang, ySC2070, que indica un$?sin entrecomillar. - warning: patrón de alto riesgo que a menudo puede explotarse. Bloquee la compilación. Corríjalo o justifique explícitamente su supresión. Ejemplo: variable sin entrecomillar como
SC2086. - info: probablemente sea correcto hoy, pero es frágil o no portable. Corríjalo en la misma solicitud de cambios, salvo que se haya excluido explícitamente del alcance.
- style: preferencia estética o de POSIX. Se recomienda, pero es opcional en una base de código exclusivamente de Bash.
Use --severity=warning para obtener un código de salida distinto de cero únicamente con advertencias o resultados de mayor gravedad: es el umbral estándar de una barrera de seguridad:
#!/usr/bin/env bash
# gate.sh — fail CI on errors and warnings only
shellcheck --severity=warning scripts/*.sh
echo "ShellCheck exit code: $?"Integrar ShellCheck como barrera de seguridad de CI
Una barrera de seguridad solo es útil cuando es obligatoria y automatizada. El patrón siguiente integra ShellCheck en un paso de CI que:
- Encuentra todos los archivos
.shdel repositorio. - Ejecuta ShellCheck con
--severity=warningy resultados JSON legibles por máquinas. - Falla la canalización (
exit 1) si existe algún resultado. - Imprime un resumen para que los ingenieros puedan actuar sobre los resultados sin salir del registro de CI.
Coloque este archivo en su repositorio y llámelo desde su canalización de CI (GitHub Actions, Jenkins, GitLab CI, etc.):
#!/usr/bin/env bash
# ci/shellcheck_gate.sh
set -euo pipefail
SCRIPTS=$(find . -name '*.sh' -not -path './.git/*')
FAILED=0
for script in $SCRIPTS; do
echo "==> Checking: $script"
if ! shellcheck --severity=warning --format=tty "$script"; then
FAILED=1
fi
done
if [ "$FAILED" -eq 1 ]; then
echo "[GATE] ShellCheck found warnings or errors. Build blocked." >&2
exit 1
fi
echo "[GATE] All scripts passed ShellCheck."La familia SC2086: expansiones de variables sin entrecomillar
SC2086 es el resultado más común de ShellCheck y una de las vulnerabilidades del shell más explotadas: las expansiones de variables sin entrecomillar.
Cuando una variable no está entrecomillada con comillas dobles, el shell realiza la división de palabras (divide según IFS) y la expansión de comodines sobre su valor. Un atacante que controle la variable puede inyectar argumentos adicionales, provocar recorridos no deseados del sistema de archivos o hacer que los comandos reciban operandos inesperados.
Patrón clásico peligroso:
#!/usr/bin/env bash
# Attacker sets: FILENAME="important.txt /etc/passwd"
FILENAME="$1"
# UNSAFE — word splitting turns this into two args
rm $FILENAME
# SAFE — double quotes prevent splitting
rm "$FILENAME"
# Arrays are the right tool for lists
FILES=("$@")
rm -- "${FILES[@]}"Detectar riesgos de inyección de comandos con SC2046 y SC2035
Dos reglas menos conocidas, pero críticas, abordan la inyección de comandos mediante la salida de subcapas:
SC2046: Entrecomille esto para evitar la división de palabras y los comodines dentro de$(…). Si la salida de una subcapa se utiliza sin entrecomillar, cualquier espacio en blanco o carácter comodín de la salida se convierte en un token del shell.SC2035: Use./*.shen lugar de*.shpara evitar que los nombres de archivo que comienzan por-se interpreten como opciones (un vector clásico de inyección de argumentos).
Escenario concreto de explotación y solución:
#!/usr/bin/env bash
# SC2046 example — output of find fed unquoted to chmod
# If a filename contains spaces, extra arguments appear
# UNSAFE
chmod 600 $(find /secrets -name '*.key')
# SAFE — use a while-read loop or xargs with -0
find /secrets -name '*.key' -print0 \
| xargs -0 chmod 600
# SC2035 example
# UNSAFE — a file named '-rf' would be passed as an option
rm *.sh
# SAFE
rm -- ./*.shUsar el formato de salida JSON para la automatización
ShellCheck admite varios formatos de salida mediante --format:
tty(predeterminado): salida de terminal legible para humanosjson: legible por máquinas; ideal para paneles, bloqueos personalizados o cargas en plataformas SASTgcc: compatible con herramientas que analizan el formato de errores de GCC (IDE, Vim/Emacs)checkstyle: formato XML que consume el complemento Checkstyle de Jenkins
El formato JSON permite escribir políticas automatizadas, por ejemplo, bloquear únicamente determinados códigos SC o agregar resultados de una base de código grande en un informe de seguridad.
#!/usr/bin/env bash
# Emit JSON and filter for only error-severity findings using jq
shellcheck --format=json scripts/deploy.sh \
| jq '[.[] | select(.level == "error")]'
# Count distinct SC codes across all scripts
find . -name '*.sh' -print0 \
| xargs -0 shellcheck --format=json 2>/dev/null \
| jq '[.[] | .code] | group_by(.) | map({code: .[0], count: length}) | sort_by(-.count)'Suprimir correctamente los falsos positivos
Desactivar ShellCheck indiscriminadamente anula su propósito. El enfoque correcto es una supresión específica y documentada que afecte únicamente a la línea o el bloque exactos donde el resultado realmente no sea aplicable.
Hay tres mecanismos de supresión:
- Desactivación en línea:
# shellcheck disable=SC2086en la línea anterior al código señalado. Solo afecta a esa línea. - Desactivación/activación de bloque: rodee una sección con
# shellcheck disable=…y# shellcheck enable=…. - Directiva a nivel de archivo: coloque
# shellcheck disable=…al principio del archivo (rara vez está justificado; documente el motivo).
Toda supresión debe incluir un comentario que explique por qué el resultado es un falso positivo:
#!/usr/bin/env bash
# deploy.sh
# Legitimate suppression: $DEPLOY_ARGS is intentionally word-split
# because it is a pre-validated list of flags from a trusted config file.
# shellcheck disable=SC2086
exec deploy-tool $DEPLOY_ARGS
# Block suppression for a section that generates dynamic code
# shellcheck disable=SC2016
VARS='$HOME $PATH $USER'
echo "Unexpanded vars: $VARS"
# shellcheck enable=SC2016Configurar ShellCheck mediante .shellcheckrc
Para la configuración de todo el proyecto, ShellCheck busca .shellcheckrc desde el directorio del script hacia arriba hasta /. Esto permite evitar la repetición de opciones en cada invocación y mantiene sencillos los scripts de la barrera.
Directivas útiles en .shellcheckrc:
shell=bash: sustituye la detección del dialecto (útil para archivos sin shebang)enable=all: activa las comprobaciones opcionales (por ejemplo,avoid-nullary-conditionsyrequire-variable-braces)disable=SC2059: supresión para todo el proyecto en una excepción justificadaexternal-sources=true: sigue y comprueba las directivassource/.
# .shellcheckrc — project root
shell=bash
enable=all
external-sources=true
# SC2312: consider invoking this command separately to avoid masking its
# return value — suppressed project-wide because we use set -e.
# Rationale: errexit already aborts on failure; masking risk is mitigated.
disable=SC2312Script reforzado de extremo a extremo: antes y después
La forma más eficaz de asimilar los resultados de ShellCheck es refactorizar un script realista, pasando de un estado con errores a otro limpio y reforzado. El script siguiente realiza una copia de seguridad de un directorio y se escribió sin tener en cuenta la seguridad. ShellCheck detecta en él al menos cinco reglas distintas.
Estudie ambas versiones. La versión posterior supera shellcheck --severity=warning sin directivas de supresión y es considerablemente más segura frente a entradas controladas por un atacante:
#!/usr/bin/env bash
# BEFORE — multiple ShellCheck violations
DEST=$1
SRC=$2
DATE=`date +%Y%m%d`
if [ ! -d $DEST ]; then
mkdir $DEST
fi
cp -r $SRC $DEST/$DATE
echo Done
#!/usr/bin/env bash
# AFTER — ShellCheck-clean and hardened
set -euo pipefail
DEST="${1:?Usage: backup.sh <dest> <src>}"
SRC="${2:?Usage: backup.sh <dest> <src>}"
DATE=$(date +%Y%m%d)
if [ ! -d "$DEST" ]; then
mkdir -p -- "$DEST"
fi
cp -r -- "$SRC" "$DEST/$DATE"
echo 'Done' >&2Comprobación de conocimientos: ShellCheck en una barrera de seguridad
Compruebe su comprensión del papel de ShellCheck como barrera de seguridad.
Repaso: análisis estático como barrera de seguridad
En esta lección ha aprendido a convertir ShellCheck en una barrera de seguridad obligatoria en su flujo de trabajo de Bash:
- ShellCheck realiza análisis estático sin ejecutar los scripts, y detecta errores de entrecomillado, riesgos de inyección y patrones inseguros antes del tiempo de ejecución.
- Cada resultado incluye un código SC (por ejemplo,
SC2086) enlazado con documentación detallada y orientación para corregirlo. - La escala de gravedad —
error,warning,infoystyle— permite ajustar la barrera:--severity=warninges el umbral de seguridad recomendado. - Use resultados legibles por máquinas (
--format=json) para automatizar informes, realizar un seguimiento de tendencias e integrar SAST. - Suprima con moderación: dirija siempre la supresión a una sola línea, documente siempre el motivo en un comentario y nunca suprima globalmente salvo que
.shellcheckrclo justifique. - Combine ShellCheck con
set -euo pipefail, entrecomillado explícito, terminadores-- argumenty validación de entradas para una defensa en profundidad.
Un script que supera ShellCheck no es automáticamente seguro, pero un script que no supera ShellCheck nunca debería llegar a producción.
Preguntas frecuentes
¿La lección «Análisis estático y auditoría con ShellCheck» es gratis?
Sí — el texto completo de «Análisis estático y auditoría con ShellCheck» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.
¿Qué aprenderé en «Análisis estático y auditoría con ShellCheck»?
Integre ShellCheck en una barrera de seguridad e interprete sus resultados para reforzar cada script. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar DevOps Bootcamp?
No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Análisis estático y auditoría con ShellCheck»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?
Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Prevención de inyección de comandos y argumentos
- Gestión segura de secretos e higiene del entorno
- Ejecución con mínimos privilegios y disciplina con sudo
- Análisis estático y auditoría con ShellCheck