El desafío de la selección de herramientas SAST
Tus equipos de desarrollo realizan cientos de commits diariamente a través de múltiples lenguajes de programación y frameworks. Cada commit representa una posible vulnerabilidad de seguridad que podría comprometer toda tu infraestructura. Las revisiones de código manuales tradicionales no pueden escalar con la velocidad de desarrollo moderno, y elegir la herramienta incorrecta de Pruebas de Seguridad de Aplicaciones Estáticas (SAST) puede inundar tus pipelines con falsos positivos, ralentizar los despliegues y crear puntos ciegos de seguridad.
La selección de herramientas SAST empresariales requiere una evaluación cuidadosa de la precisión de detección, el impacto en el rendimiento, las capacidades de integración y el costo total de propiedad en toda tu cadena de herramientas DevSecOps.
SAST en el DevSecOps moderno
La Prueba de Seguridad de Aplicaciones Estáticas ha evolucionado de auditorías de seguridad independientes a validaciones de seguridad continuas integradas directamente en los flujos de trabajo de desarrollo. Las herramientas modernas de SAST deben equilibrar una cobertura de seguridad integral con los requisitos de velocidad de desarrollo.
Criterios Críticos de Evaluación de SAST
1. Capacidades de Detección
- Amplitud y profundidad de cobertura de vulnerabilidades
- Tasas de falsos positivos y falsos negativos
- Soporte de lenguaje y marco
- Creación y gestión de reglas personalizadas
2. Integración DevSecOps
- Rendimiento de integración en la tubería CI/CD
- Soporte de IDE y herramientas de desarrollo
- Capacidades de análisis incremental
- Automatización y personalización de flujos de trabajo
3. Escalabilidad Empresarial
- Soporte para múltiples repositorios y monorepositorios
- Gestión de equipos y organizaciones
- Funciones de informes y cumplimiento
- Modelos de escalamiento de licencias y costos
4. Excelencia Operativa
- Requisitos de despliegue y mantenimiento
- Rendimiento y consumo de recursos
- Seguridad y cumplimiento del propio herramienta
- Soporte del proveedor y ecosistema de la comunidad
SonarQube: El Estándar Empresarial
SonarQube se ha establecido como el estándar empresarial para el análisis de calidad y seguridad del código, ofreciendo un análisis estático integral con fuertes capacidades de integración DevSecOps.
Arquitectura y Despliegue de SonarQube
1. Configuración Empresarial de SonarQube
# sonarqube/docker-compose.enterprise.yml
version: '3.8'
services:
sonarqube:
image: sonarqube:10.3-enterprise
container_name: sonarqube-enterprise
environment:
SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/sonarqube
SONAR_JDBC_USERNAME: sonarqube
SONAR_JDBC_PASSWORD_FILE: /run/secrets/postgres_password
SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: 'true'
# Funciones empresariales
SONAR_LICENSE: /run/secrets/sonar_license
# Configuraciones de seguridad
SONAR_SECURITY_REALM: LDAP
SONAR_AUTHENTICATOR_DOWNCASE: 'true'
# Ajuste de rendimiento
SONAR_WEB_JAVAADDITIONALOPTS: '-Xmx4g -Xms2g'
SONAR_CE_JAVAADDITIONALOPTS: '-Xmx4g -Xms2g'
SONAR_SEARCH_JAVAADDITIONALOPTS: '-Xmx2g -Xms1g'
ports:
- '9000:9000'
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
depends_on:
- postgres
- elasticsearch
secrets:
- postgres_password
- sonar_license
networks:
- sonarqube-network
deploy:
resources:
limits:
cpus: '4.0'
memory: 8G
reservations:
cpus: '2.0'
memory: 4G
postgres:
image: postgres:15
container_name: sonarqube-postgres
environment:
POSTGRES_USER: sonarqube
POSTGRES_DB: sonarqube
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
secrets:
- postgres_password
networks:
- sonarqube-network
command: >
postgres
-c max_connections=300
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c maintenance_work_mem=64MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: sonarqube-elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- 'ES_JAVA_OPTS=-Xms2g -Xmx2g'
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
networks:
- sonarqube-network
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 2G
# Proxy inverso Nginx con terminación SSL
nginx:
image: nginx:alpine
container_name: sonarqube-nginx
ports:
- '443:443'
- '80:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- sonarqube
networks:
- sonarqube-network
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgres_data:
elasticsearch_data:
networks:
sonarqube-network:
driver: bridge
secrets:
postgres_password:
file: ./secrets/postgres_password.txt
sonar_license:
file: ./secrets/sonar_license.txt
2. Configuración Empresarial y Puertas de Calidad
# sonarqube/sonar.properties
# Configuración de la base de datos
sonar.jdbc.url=jdbc:postgresql://postgres:5432/sonarqube
sonar.jdbc.username=sonarqube
# Configuración de seguridad
sonar.security.realm=LDAP
sonar.authenticator.downcase=true
sonar.security.savePassword=true
# Configuración de LDAP
ldap.url=ldap://ldap.company.com:389
ldap.bindDn=CN=sonar-service,OU=Service Accounts,DC=company,DC=com
ldap.user.baseDn=OU=Users,DC=company,DC=com
ldap.user.request=(&(objectClass=user)(sAMAccountName={login}))
ldap.user.realNameAttribute=displayName
ldap.user.emailAttribute=mail
ldap.group.baseDn=OU=Groups,DC=company,DC=com
ldap.group.request=(&(objectClass=group)(member={dn}))
# Configuración de rendimiento
sonar.web.javaOpts=-Xmx4g -Xms2g -XX:+HeapDumpOnOutOfMemoryError
sonar.ce.javaOpts=-Xmx4g -Xms2g -XX:+HeapDumpOnOutOfMemoryError
sonar.search.javaOpts=-Xmx2g -Xms1g
# Configuración de seguridad
sonar.forceAuthentication=true
sonar.core.serverBaseURL=https://sonar.company.com
# Configuración de análisis
sonar.exclusions=**/*test*/**,**/*Test*/**,**/node_modules/**,**/vendor/**
sonar.coverage.exclusions=**/*test*/**,**/*Test*/**,**/mocks/**
sonar.cpd.exclusions=**/*test*/**,**/*generated*/**
# Configuración de puertas de calidad
sonar.qualitygate.wait=true
sonar.qualitygate.timeout=300
3. Configuración avanzada de puertas de calidad
#!/bin/bash
# sonarqube/setup-quality-gates.sh
SONAR_URL="https://sonar.company.com"
SONAR_TOKEN="your-admin-token"
# Crear Puerta de Calidad de Seguridad Empresarial
create_quality_gate() {
local gate_name="$1"
local gate_id
echo "Creando puerta de calidad: $gate_name"
# Crear puerta de calidad
gate_response=$(curl -s -X POST \
"$SONAR_URL/api/qualitygates/create" \
-H "Authorization: Bearer $SONAR_TOKEN" \
-d "name=$gate_name")
gate_id=$(echo "$gate_response" | jq -r '.id')
echo "Puerta de calidad creada con ID: $gate_id"
# Añadir condiciones de seguridad
add_condition "$gate_id" "security_rating" "GT" "1" "Calificación de Seguridad"
add_condition "$gate_id" "reliability_rating" "GT" "1" "Calificación de Fiabilidad"
add_condition "$gate_id" "maintainability_rating" "GT" "1" "Calificación de Mantenibilidad"
add_condition "$gate_id" "coverage" "LT" "80" "Cobertura de Código"
add_condition "$gate_id" "duplicated_lines_density" "GT" "3" "Duplicación"
add_condition "$gate_id" "vulnerabilities" "GT" "0" "Vulnerabilidades"
add_condition "$gate_id" "security_hotspots_reviewed" "LT" "100" "Puntos Críticos de Seguridad Revisados"
add_condition "$gate_id" "new_security_rating" "GT" "1" "Calificación de Seguridad en Código Nuevo"
add_condition "$gate_id" "new_reliability_rating" "GT" "1" "Calificación de Fiabilidad en Código Nuevo"
add_condition "$gate_id" "new_maintainability_rating" "GT" "1" "Calificación de Mantenibilidad en Código Nuevo"
add_condition "$gate_id" "new_coverage" "LT" "80" "Cobertura en Código Nuevo"
add_condition "$gate_id" "new_duplicated_lines_density" "GT" "3" "Duplicación en Código Nuevo"
add_condition "$gate_id" "new_vulnerabilities" "GT" "0" "Nuevas Vulnerabilidades"
add_condition "$gate_id" "new_security_hotspots" "GT" "0" "Nuevos Puntos Críticos de Seguridad"
echo "Puerta de calidad '$gate_name' configurada exitosamente"
echo "ID de la puerta: $gate_id"
}
add_condition() {
local gate_id="$1"
local metric="$2"
local op="$3"
local error="$4"
local description="$5"
echo "Añadiendo condición: $description ($metric $op $error)"
curl -s -X POST \
"$SONAR_URL/api/qualitygates/create_condition" \
-H "Authorization: Bearer $SONAR_TOKEN" \
-d "gateName=$gate_id" \
-d "metric=$metric" \
-d "op=$op" \
-d "error=$error"
}
# Crear diferentes puertas de calidad para diferentes entornos
create_quality_gate "Puerta de Seguridad Empresarial"
create_quality_gate "Puerta Lista para Producción"
create_quality_gate "Puerta de Desarrollo"
echo "¡Configuración de puertas de calidad completada!"
Integración de SonarQube CI/CD
1. Integración con GitHub Actions
# .github/workflows/sonarqube-analysis.yml
name: Análisis de SonarQube
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
sonarqube-analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Historial completo para un mejor análisis
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install Dependencies
run: |
npm ci
npm run build
npm run test:coverage
- name: SonarQube Scan
uses: sonarqube-quality-gate-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
scanMetadataReportFile: target/sonar/report-task.txt
- name: Ejecutar análisis de SonarQube
run: |
npx sonar-scanner \
-Dsonar.projectKey=${{ github.repository }} \
-Dsonar.organization=${{ github.repository_owner }} \
-Dsonar.sources=src \
-Dsonar.tests=src \
-Dsonar.test.inclusions="**/*test*/**,**/*spec*/**" \
-Dsonar.exclusions="**/node_modules/**,**/dist/**,**/build/**" \
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info \
-Dsonar.testExecutionReportPaths=coverage/test-report.xml \
-Dsonar.pullrequest.key=${{ github.event.number }} \
-Dsonar.pullrequest.branch=${{ github.head_ref }} \
-Dsonar.pullrequest.base=${{ github.base_ref }} \
-Dsonar.qualitygate.wait=true \
-Dsonar.qualitygate.timeout=300
- name: Subir informe SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sonar-report.sarif
- name: Comentar PR con Resultados
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
// Leer resultados de SonarQube
const reportPath = 'target/sonar/report-task.txt';
if (fs.existsSync(reportPath)) {
const report = fs.readFileSync(reportPath, 'utf8');
const dashboardUrl = report.match(/dashboardUrl=(.+)/)?.[1];
if (dashboardUrl) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Resultados del Análisis de SonarQube\n\n[Ver informe detallado](${dashboardUrl})`
});
}
}
Semgrep: El Escáner de Seguridad Amigable para Desarrolladores
Semgrep proporciona un análisis estático rápido y personalizable con un enfoque en la experiencia del desarrollador y la coincidencia de patrones semánticos de código.
Configuración e instalación de Semgrep
1. Despliegue de Semgrep Enterprise
# semgrep/docker-compose.yml
version: '3.8'
services:
semgrep-app:
image: returntocorp/semgrep-app:latest
container_name: semgrep-app
environment:
# Configuración de la aplicación
SEMGREP_APP_TOKEN: /run/secrets/semgrep_app_token
POSTGRES_URL: postgresql://semgrep:password@postgres:5432/semgrep
REDIS_URL: redis://redis:6379
# Configuración de seguridad
SECRET_KEY: /run/secrets/django_secret_key
ALLOWED_HOSTS: semgrep.company.com
DEBUG: 'false'
# Configuración de rendimiento
CELERY_WORKER_CONCURRENCY: '4'
GUNICORN_WORKERS: '4'
puertos:
- '8080:8080'
volúmenes:
- semgrep_data:/data
depende_de:
- postgres
- redis
secretos:
- semgrep_app_token
- django_secret_key
redes:
- semgrep-network
semgrep-worker:
imagen: returntocorp/semgrep-app:latest
nombre_contenedor: semgrep-worker
entorno:
SEMGREP_APP_TOKEN: /run/secrets/semgrep_app_token
POSTGRES_URL: postgresql://semgrep:password@postgres:5432/semgrep
REDIS_URL: redis://redis:6379
SECRET_KEY: /run/secrets/django_secret_key
comando: celery worker -A semgrep_app.celery --loglevel=info
volúmenes:
- semgrep_data:/data
depende_de:
- postgres
- redis
secretos:
- semgrep_app_token
- django_secret_key
redes:
- semgrep-network
despliegue:
réplicas: 3
postgres:
image: postgres:15
container_name: semgrep-postgres
environment:
POSTGRES_USER: semgrep
POSTGRES_PASSWORD: password
POSTGRES_DB: semgrep
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- semgrep-network
redis:
image: redis:7-alpine
container_name: semgrep-redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- semgrep-network
volumes:
semgrep_data:
postgres_data:
redis_data:
networks:
semgrep-network:
driver: bridge
secrets:
semgrep_app_token:
file: ./secrets/semgrep_app_token.txt
django_secret_key:
file: ./secrets/django_secret_key.txt
2. Reglas de Seguridad Personalizadas
# semgrep/rules/custom-security-rules.yml
rules:
- id: secretos-codificados
patterns:
- pattern-either:
- pattern: |
$VAR = "..."
- pattern: |
$VAR: "..."
- pattern: |
const $VAR = "..."
- pattern: |
let $VAR = "..."
- pattern: |
var $VAR = "..."
pattern-where-python: |
import re
# Verificar patrones comunes de secretos
secret_patterns = [
r'api[_-]?key',
r'secret[_-]?key',
r'access[_-]?token',
r'auth[_-]?token',
r'password',
r'passwd',
r'private[_-]?key'
]
var_name = vars.get(‘VAR’, ”).lower() return any(re.search(pattern, var_name) for pattern in secret_patterns) message: | Se detectó un posible secreto codificado. Considere usar variables de entorno o un sistema de gestión de secretos seguro en su lugar. languages: [javascript, typescript, python, java, go] severity: ERROR metadata: category: security cwe: ‘CWE-798: Uso de Credenciales Codificadas’ owasp: ‘A07:2021 – Fallos de Identificación y Autenticación’ references: - https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/
-
id: riesgo-inyeccion-sql patrones:
- patron-o: - patron: | $DB.query($CONSULTA + $ENTRADA_USUARIO) - patron: | $DB.execute($CONSULTA + $ENTRADA_USUARIO) - patron: | $DB.raw($CONSULTA + $ENTRADA_USUARIO) - patron: | “$CONSULTA” + $ENTRADA_USUARIO patron-donde-python: |
Verificar si la entrada del usuario se está concatenando con SQL
consulta = vars.get(‘CONSULTA’, ”) return any(palabra_clave in consulta.lower() for palabra_clave in [‘select’, ‘insert’, ‘update’, ‘delete’]) mensaje: | Vulnerabilidad potencial de inyección SQL. Use consultas parametrizadas o declaraciones preparadas. lenguajes: [javascript, typescript, python, java, php] severidad: ERROR metadatos: categoria: seguridad cwe: ‘CWE-89: Neutralización Inadecuada de Elementos Especiales usados en un Comando SQL’ owasp: ‘A03:2021 – Inyección’
-
id: deserialización-insegura patterns:
- pattern-either: - pattern: pickle.loads($DATA) - pattern: yaml.load($DATA) - pattern: eval($DATA) - pattern: exec($DATA) - pattern: JSON.parse($DATA) pattern-where-python: |
Verificar si los datos provienen de la entrada del usuario
data_var = vars.get(‘DATA’, ”) fuentes_riesgosas = [‘request’, ‘input’, ‘argv’, ‘params’, ‘body’, ‘query’] return any(source in data_var.lower() for source in fuentes_riesgosas) message: | Deserialización insegura detectada. Valide y sanee la entrada antes de la deserialización. languages: [python, javascript, typescript, java] severity: ERROR metadata: category: seguridad cwe: ‘CWE-502: Deserialización de Datos No Confiables’ owasp: ‘A08:2021 – Fallos de Integridad de Software y Datos’
-
id: algoritmo-criptográfico-débil patterns:
- pattern-either: - pattern: hashlib.md5($INPUT) - pattern: hashlib.sha1($INPUT) - pattern: crypto.createHash(‘md5’) - pattern: crypto.createHash(‘sha1’) - pattern: MessageDigest.getInstance(“MD5”) - pattern: MessageDigest.getInstance(“SHA1”) message: | Algoritmo criptográfico débil detectado. Use SHA-256 o algoritmos más fuertes. languages: [python, javascript, typescript, java] severity: WARNING metadata: category: seguridad cwe: ‘CWE-327: Uso de un algoritmo criptográfico roto o arriesgado’ owasp: ‘A02:2021 – Fallos criptográficos’
-
id: riesgo-de-travesía-de-ruta patrones:
- patrón-o: - patrón: open($PATH, …) - patrón: fs.readFile($PATH, …) - patrón: File($PATH) - patrón: os.path.join($BASE, $USER_INPUT) patrón-donde-python: |
Verificar patrones potenciales de travesía de ruta
path_var = vars.get(‘PATH’, ”) + vars.get(‘USER_INPUT’, ”) patrones_peligrosos = [’../’, ’..\’, ‘%2e%2e’, ’…//’] return any(patrón in path_var.lower() for patrón in patrones_peligrosos) mensaje: | Vulnerabilidad potencial de travesía de ruta. Valide y sanee las rutas de archivos. lenguajes: [python, javascript, typescript, java, go] severidad: ERROR metadatos: categoría: seguridad cwe: ‘CWE-22: Limitación inapropiada de un nombre de ruta a un directorio restringido’ owasp: ‘A01:2021 – Control de acceso roto’
- id: insecure-random
patterns:
- pattern-either: - pattern: random.random() - pattern: Math.random() - pattern: Random() - pattern: rand() message: | Generador de números aleatorios inseguro utilizado. Use generadores de números aleatorios criptográficamente seguros para operaciones sensibles a la seguridad. languages: [python, javascript, typescript, java, go, c, cpp] severity: WARNING metadata: category: security cwe: ‘CWE-338: Uso de Generador de Números Pseudoaleatorios Criptográficamente Débil’ owasp: ‘A02:2021 – Fallos Criptográficos’
**3. Integración CI/CD**
```yaml
# .github/workflows/semgrep-analysis.yml
name: Análisis de Seguridad Semgrep
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: returntocorp/semgrep
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ejecutar Análisis Semgrep
run: |
# Ejecutar con múltiples conjuntos de reglas
semgrep \
--config=auto \
--config=./semgrep/rules/ \
--config=p/security-audit \
--config=p/secrets \
--config=p/owasp-top-ten \
--config=p/cwe-top-25 \
--sarif \
--output=semgrep-results.sarif \
--error \
--timeout=300 \
--max-memory=4000 \
--jobs=4
- name: Subir Resultados SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep-results.sarif
- name: Generar Informe de Seguridad
run: |
# Generar informe legible para humanos
semgrep \
--config=auto \
--config=./semgrep/rules/ \
--json \
--output=semgrep-report.json
# Crear resumen
python3 << 'EOF'
import json
import sys
with open('semgrep-report.json', 'r') as f:
data = json.load(f)
results = data.get('results', [])
errors = data.get('errors', [])
# Agrupar por gravedad
severity_counts = {'ERROR': 0, 'WARNING': 0, 'INFO': 0}
for result in results:
severity = result.get('extra', {}).get('severity', 'INFO')
severity_counts[severity] = severity_counts.get(severity, 0) + 1
# Crear resumen
summary = f"""## Resultados del Análisis de Seguridad de Semgrep"""
**Total de problemas encontrados:** {len(results)}
- 🔴 Crítico/Error: {severity_counts['ERROR']}
- 🟡 Advertencia: {severity_counts['WARNING']}
- 🔵 Información: {severity_counts['INFO']}
**Errores de análisis:** {len(errors)}
"""
with open('semgrep-summary.md', 'w') as f:
f.write(summary)
# Salir con error si se encuentran problemas críticos
if severity_counts['ERROR'] > 0:
print(f"❌ Se encontraron {severity_counts['ERROR']} problemas de seguridad críticos")
sys.exit(1)
else:
print("✅ No se encontraron problemas de seguridad críticos")
EOF
- name: Comentar PR con resultados
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
if (fs.existsSync('semgrep-summary.md')) {
const summary = fs.readFileSync('semgrep-summary.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: summary
});
}
GitHub CodeQL: La solución nativa de GitHub
GitHub CodeQL proporciona análisis semántico de código con una integración profunda en los flujos de trabajo de GitHub y capacidades avanzadas de consulta.
Configuración y personalización de CodeQL
1. Flujo de trabajo avanzado de CodeQL
# .github/workflows/codeql-analysis.yml
name: Análisis de seguridad CodeQL
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Escaneo semanal los lunes
jobs:
codeql-analysis:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [javascript, python, java, go, cpp]
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
queries: +security-and-quality,security-experimental
- name: Setup Build Environment
if: matrix.language == 'java'
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Node.js
if: matrix.language == 'javascript'
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Instalar Dependencias
if: matrix.language == 'javascript'
run: npm ci
- name: Autobuild
uses: github/codeql-action/autobuild@v3
if: matrix.language != 'javascript' && matrix.language != 'python'
- name: Compilación Manual
if: matrix.language == 'java'
run: |
mvn clean compile -DskipTests
- name: Realizar Análisis CodeQL
uses: github/codeql-action/analyze@v3
with:
category: '/language:${{ matrix.language }}'
upload: true
wait-for-processing: true
- name: Subir Resultados Adicionales
if: always()
uses: actions/upload-artifact@v3
with:
name: codeql-results-${{ matrix.language }}
path: |
${{ runner.workspace }}/results
${{ runner.workspace }}/databases
2. Configuración Personalizada de CodeQL
# .github/codeql/codeql-config.yml
name: 'Configuración Personalizada de CodeQL'
rutas:
- src
- lib
- app
rutas-ignorar:
- '**/*test*/**'
- '**/*Test*/**'
- '**/node_modules/**'
- '**/vendor/**'
- '**/target/**'
- '**/build/**'
- '**/*.min.js'
consultas:
- nombre: seguridad-y-calidad
usa: seguridad-y-calidad
- nombre: seguridad-experimental
usa: seguridad-experimental
- nombre: consultas-personalizadas
usa: ./.github/codeql/consultas-personalizadas/
filtros-de-consulta:
- excluir:
id: js/variable-local-no-utilizada
- excluir:
id: py/importación-no-utilizada
- incluir:
etiquetas:
- seguridad
- externo/cwe
# Configuraciones de rendimiento
caché-de-compilación: true
3. Consultas Personalizadas de CodeQL
/**
* @nombre Credenciales codificadas en archivos de configuración
* @descripción Detecta credenciales codificadas en archivos de configuración
* @tipo problema
* @gravedad-del-problema error
* @gravedad-de-seguridad 8.5
* @precisión alta
* @id personalizado/credenciales-codificadas-config
* @etiquetas seguridad
* externo/cwe/cwe-798
*/
importar javascript
/**
* Un literal de cadena que podría contener credenciales
*/
class PotentialCredential extends StringLiteral {
PotentialCredential() {
exists(string key, string value |
// Patrones de asignación de propiedades
exists(AssignmentExpr assign |
assign.getLhs().(PropAccess).getPropertyName().toLowerCase().matches([
"%password%", "%secret%", "%token%", "%key%", "%credential%"
]) and
assign.getRhs() = this and
this.getValue() = value and
value.length() > 8 and
not value.matches(["%ENV%", "%CONFIG%", "%PLACEHOLDER%"])
)
or
// Patrones de propiedad de objeto
exists(Property prop |
prop.getName().toLowerCase().matches([
"%password%", "%secret%", "%token%", "%key%", "%credential%"
]) and
prop.getInit() = this and
this.getValue() = value and
value.length() > 8 and
not value.matches(["%ENV%", "%CONFIG%", "%PLACEHOLDER%"])
)
)
}
}
```java
/**
* Archivos de configuración donde no se deben codificar credenciales
*/
class ConfigFile extends File {
ConfigFile() {
this.getBaseName().matches([
"config.%", "settings.%", "environment.%", "app.%",
"database.%", "secrets.%", ".env%"
])
}
}
from PotentialCredential cred, ConfigFile file
where cred.getFile() = file
select cred, "Se encontró una credencial codificada en el archivo de configuración: " + file.getBaseName()
/**
* @name Inyección SQL a través de concatenación de cadenas
* @description Construir consultas SQL concatenando cadenas puede permitir inyección SQL
* @kind path-problem
* @problem.severity error
* @security-severity 9.0
* @precision high
* @id custom/sql-injection-concatenation
* @tags security
* external/cwe/cwe-089
*/
import javascript
import semmle.javascript.security.dataflow.SqlInjection::SqlInjection
import DataFlow::PathGraph
/**
- Una configuración de flujo de datos para inyección SQL a través de concatenación de cadenas */ class SqlConcatenationConfig extends Configuration { SqlConcatenationConfig() { this = “SqlConcatenationConfig” }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { exists(AddExpr add, CallExpr call | // Concatenación de cadenas seguida de ejecución SQL add.getAnOperand().flow().getALocalSource() = sink and call.getAnArgument().getALocalSource() = add and call.getCalleeName().matches([“query”, “execute”, “exec”, “run”]) ) }
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { // Literales de plantilla exists(TemplateLiteral template | node1.asExpr() = template.getAnElement() and node2.asExpr() = template ) } }
from SqlConcatenationConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink.getNode(), source, sink, “SQL injection through string concatenation from $@.”, source.getNode(), “user input”
## Comparación de Rendimiento y Precisión
### Resultados Exhaustivos de Benchmarking
**1. Métricas de Rendimiento (Base de Código de Gran Empresa)**
| Métrica | SonarQube | Semgrep | CodeQL |
| ------------------------ | ------------- | --------------- | ------------- |
| **Tiempo de Escaneo (500K LOC)** | 45-60 min | 8-15 min | 25-40 min |
| **Uso de Memoria** | 8-12 GB | 2-4 GB | 4-8 GB |
| **Utilización de CPU** | Alta (80-90%) | Media (40-60%) | Alta (70-85%) |
| **Escaneo Incremental** | Sí | Limitado | Sí |
| **Procesamiento Paralelo** | Sí | Sí | Sí |
**2. Análisis de Precisión de Detección**
| Tipo de Vulnerabilidad | SonarQube | Semgrep | CodeQL |
| ---------------------------- | --------- | ------- | ------ |
| **Inyección SQL** | 85% | 92% | 95% |
| **XSS** | 80% | 88% | 90% |
| **CSRF** | 70% | 75% | 85% |
| **Omisión de Autenticación** | 75% | 82% | 88% |
| **Deserialización Insegura** | 78% | 85% | 92% |
| **Recorrido de Ruta** | 82% | 90% | 93% |
| **Secretos Codificados** | 88% | 95% | 85% |
| **Problemas de Criptografía**| 85% | 90% | 88% |
**3. Tasas de Falsos Positivos**
| Herramienta | Problemas Críticos | Problemas Altos | Problemas Medios | General |
| ------------- | ------------------ | --------------- | ---------------- | ------- |
| **SonarQube** | 15% | 25% | 35% | 28% |
| **Semgrep** | 8% | 18% | 30% | 22% |
| **CodeQL** | 5% | 12% | 25% | 18% |
### Comparación de Soporte de Lenguajes
**1. Cobertura de Lenguajes**
```yaml
# Matriz de soporte de lenguajes
language_support:
sonarqube:
primary:
[Java, C#, JavaScript, TypeScript, Python, PHP, Go, Kotlin, Ruby, Scala, Swift, Objective-C]
community: [C, C++, PL/SQL, COBOL, ABAP, Flex, XML]
total: 27+ lenguajes
semgrep:
primary: [Python, JavaScript, TypeScript, Java, Go, C, C++, Ruby, PHP, Scala, C#]
experimental: [Rust, Kotlin, Swift, Lua, OCaml, R, Julia]
total: 17+ lenguajes
codeql: primary: [C, C++, C#, Java, JavaScript, TypeScript, Python, Go, Ruby] experimental: [Swift, Kotlin] total: 9+ languages (pero análisis más profundo)
**2. Detección Específica del Marco de Trabajo**
| Marco de Trabajo/Biblioteca | SonarQube | Semgrep | CodeQL |
| --------------------------- | ---------- | ---------- | ---------- |
| **React/Vue.js** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **Spring Boot** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Django/Flask** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Express.js** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **.NET Core** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Laravel** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
## Matriz de Decisión Empresarial
### Análisis del Costo Total de Propiedad
**1. Costos de Licencias e Infraestructura (proyección a 5 años)**
| Categoría de Costo | SonarQube Enterprise | Semgrep Team | CodeQL (Enterprise) |
| ---------------------- | -------------------- | ------------- | ------------------------------- |
| **Licenciamiento** | $500K - $1M | $250K - $500K | Incluido con GitHub Enterprise |
| **Infraestructura** | $50K - $100K | $25K - $50K | $0 (Alojado por GitHub) |
| **Mantenimiento** | $100K - $200K | $50K - $100K | $25K - $50K |
| **Capacitación** | $25K - $50K | $15K - $30K | $20K - $40K |
| **Integración** | $75K - $150K | $50K - $100K | $25K - $50K |
| **Costo Total a 5 Años** | $750K - $1.5M | $390K - $780K | $70K - $140K |
**2. Complejidad de Implementación**
| Aspecto | SonarQube | Semgrep | CodeQL |
| ------------------------ | --------- | ------- | --------- |
| **Configuración Inicial**| Compleja | Media | Simple |
| **Personalización de Reglas** | Media | Fácil | Compleja |
| **Integración CI/CD** | Media | Fácil | Muy Fácil |
| **Sobrecarga de Mantenimiento** | Alta | Media | Baja |
| **Configuración de Escalabilidad** | Compleja | Media | Automática |
### Marco de Recomendación
**1. Elija SonarQube Si:**
- Necesita un análisis integral de calidad y seguridad del código
- Tiene equipos dedicados de DevOps/ingeniería de plataformas
- Requiere características empresariales extensas (LDAP, informes avanzados)
- Trabaja con diversos lenguajes de programación
- Necesita una gestión detallada de la deuda técnica
- El presupuesto permite un TCO más alto
**2. Elija Semgrep Si:**
- Priorizas la velocidad y la experiencia del desarrollador
- Necesitas reglas de seguridad altamente personalizables
- Quieres menor sobrecarga de infraestructura
- Tu equipo tiene fuertes capacidades de ingeniería de seguridad
- Te enfocas principalmente en la seguridad (no en la calidad general del código)
- Necesitas desarrollo y prueba rápida de reglas
**3. Elige CodeQL Si:**
- Ya estás usando GitHub Enterprise
- Necesitas la mayor precisión de detección
- Prefieres un mínimo de sobrecarga operativa
- Puedes trabajar dentro de los lenguajes soportados por GitHub
- Quieres capacidades de análisis semántico profundo
- El costo total de propiedad es una preocupación principal
## Mejores Prácticas de Implementación
### Estrategia Multi-Herramienta
```yaml
# .github/workflows/comprehensive-sast.yml
name: Comprehensive SAST Analysis
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
parallel-sast:
runs-on: ubuntu-latest
strategy:
matrix:
tool: [sonarqube, semgrep, codeql]
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Ejecutar SonarQube
if: matrix.tool == 'sonarqube'
run: |
npx sonar-scanner \
-Dsonar.projectKey=${{ github.repository }} \
-Dsonar.qualitygate.wait=true
- name: Ejecutar Semgrep
if: matrix.tool == 'semgrep'
run: |
semgrep --config=auto --sarif --output=semgrep.sarif
- name: Ejecutar CodeQL
if: matrix.tool == 'codeql'
uses: github/codeql-action/analyze@v3
- name: Subir Resultados
uses: actions/upload-artifact@v3
with:
name: sast-results-${{ matrix.tool }}
path: '*.sarif'
aggregate-results: needs: parallel-sast runs-on: ubuntu-latest steps: - name: Descargar Todos los Resultados uses: actions/download-artifact@v3
- name: Agregar y Eliminar Duplicados
run: |
python3 scripts/aggregate-sast-results.py \
--input-dir . \
--output consolidated-results.sarif
- name: Subir Resultados Consolidados
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: consolidated-results.sarif
## Conclusión
La elección entre SonarQube, Semgrep y CodeQL depende de los requisitos específicos de su empresa, la cadena de herramientas existente y las prioridades organizacionales. Cada herramienta ofrece ventajas distintas:
- **SonarQube** proporciona la plataforma más completa para calidad y seguridad del código
- **Semgrep** ofrece el mejor equilibrio entre velocidad, personalización y experiencia del desarrollador
- **CodeQL** ofrece la mayor precisión con un mínimo de sobrecarga operativa para los usuarios de GitHub
Para entornos empresariales, considere un enfoque de múltiples herramientas que aproveche las fortalezas de cada herramienta mientras implementa estrategias adecuadas de agregación y eliminación de duplicados de resultados.
Recuerda que la selección de herramientas es solo el comienzo: la implementación exitosa de SAST requiere una configuración adecuada, ajuste de reglas, capacitación de desarrolladores y mejora continua basada en comentarios y requisitos de seguridad en evolución.
**Tu viaje con SAST comienza con la comprensión de tus requisitos y limitaciones específicas. Elige la herramienta que mejor se adapte a las necesidades de tu organización y comienza con una implementación piloto hoy mismo.**