diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 8631094..fb3a3f1 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -260,6 +260,51 @@ jobs: --exit-code 0 \ "${MANIFEST_FILE}" + # Modo auditoría: sin --error a propósito, Semgrep siempre termina + # con exit 0 aunque reporte hallazgos. Es la primera vuelta — se + # revisan los resultados en conjunto antes de decidir qué reglas + # deberían pasar a bloquear el pipeline más adelante. + - name: Escaneo SAST (Semgrep) — modo auditoría, no bloquea + shell: bash + run: | + set -euo pipefail + SEMGREP_IMAGE="semgrep/semgrep:1.173.0" + + docker run --rm \ + -v "${{ github.workspace }}:/src" \ + -w /src \ + "${SEMGREP_IMAGE}" \ + semgrep scan \ + --config=p/typescript \ + --config=p/react \ + --config=p/nextjs \ + --config=p/security-audit \ + --json \ + --output=semgrep-report.json \ + "${APP_DIR}" + + echo "=== Resumen Semgrep ===" + docker run --rm \ + -v "${{ github.workspace }}:/src" \ + -w /src \ + "${SEMGREP_IMAGE}" \ + python3 -c " +import json +data = json.load(open('semgrep-report.json')) +results = data.get('results', []) +print(f'Hallazgos: {len(results)}') +for r in results: + print(f\" [{r['extra']['severity']}] {r['check_id']} - {r['path']}:{r['start']['line']}\") +" + + - name: Publicar reporte de Semgrep + if: always() + uses: actions/upload-artifact@v3 + with: + name: semgrep-report + path: semgrep-report.json + if-no-files-found: ignore + - name: Login en Gitea Registry uses: docker/login-action@v2 with: diff --git a/workloads/docs-portal/docs/devsecops/sast.md b/workloads/docs-portal/docs/devsecops/sast.md new file mode 100644 index 0000000..bdc4324 --- /dev/null +++ b/workloads/docs-portal/docs/devsecops/sast.md @@ -0,0 +1,115 @@ +# SAST (Semgrep) — análisis estático de código + +!!! info "Qué problema resuelve" + SAST significa *Static Application Security Testing*: analizar el + **código fuente** en busca de patrones de programación inseguros, sin + ejecutar la aplicación. Semgrep lee cada archivo `.ts`/`.tsx` y lo + compara contra un catálogo de reglas — cada regla describe una forma + de escribir código que suele terminar en una vulnerabilidad conocida + (inyección, XSS, uso inseguro de una API, etc.). + +## En qué se diferencia de Gitleaks y Trivy + +Las tres herramientas ya integradas a este pipeline analizan cosas +completamente distintas — vale la pena tenerlo claro porque a primera +vista "escaneo de seguridad" suena como una sola categoría: + +| Herramienta | Qué mira | Pregunta que responde | +|---|---|---| +| [Gitleaks](gitleaks.md) | El texto de los archivos y el historial de git | "¿Hay una credencial commiteada?" | +| [Trivy](trivy.md) | Paquetes instalados (imagen) y configuración (YAML) | "¿Alguna dependencia tiene una CVE conocida? ¿El manifiesto de Kubernetes es inseguro?" | +| **Semgrep (SAST)** | La **lógica** del código que escribimos nosotros | "¿Esta función, tal como está escrita, abre una vulnerabilidad?" | + +Ninguna de las tres reemplaza a las otras. Una dependencia puede estar +100% al día (sin CVEs, Trivy contento) y aun así el código propio puede +construir una URL con un string sin sanitizar y quedar abierto a SSRF — +eso solo lo detecta un análisis de la lógica del código, que es +exactamente lo que hace Semgrep. + +## Qué tipo de bugs detecta (ejemplos genéricos) + +Los rulesets usados (`p/typescript`, `p/react`, `p/nextjs`, +`p/security-audit`) cubren, entre otras cosas: + +- **Inyección**: construir queries, comandos de shell o URLs concatenando + strings con datos que vienen del usuario, en vez de usar una API + parametrizada. +- **XSS en React/Next.js**: usar `dangerouslySetInnerHTML` con contenido + que no pasó por un sanitizador. +- **SSRF**: hacer un `fetch()`/request server-side hacia una URL que + construye el propio usuario, sin validar el host de destino. +- **Criptografía insegura**: algoritmos de hash débiles (`md5`, `sha1`) + usados para contraseñas o tokens, en vez de un KDF diseñado para eso. +- **`eval` / `new Function()`** sobre datos no confiables. +- **ReDoS**: expresiones regulares con backtracking exponencial que un + input malicioso puede usar para colgar el proceso. +- **Prototype pollution**: merges/asignaciones dinámicas de objetos que + permiten sobrescribir `__proto__`. + +## Resultado real de esta primera corrida + +```text +Scanning 72 files tracked by git with 292 Code rules: + ts 87 rules 39 files + js 81 rules 1 file + json 1 rule 6 files + +Ran 91 rules on 72 files: 0 findings. +``` + +`workloads/ecommerce` salió limpio con estos rulesets: **0 hallazgos**. + +!!! warning "0 hallazgos no significa 'código perfecto'" + Significa que ningún patrón conocido de estos 91 rules hizo match — + no es una garantía de ausencia de bugs, solo de ausencia de *estos* + patrones específicos. SAST tiene falsos negativos por naturaleza (un + bug de lógica de negocio nuevo, específico de esta app, no está en + ningún ruleset genérico). El valor de correr esto en cada build no es + "una vez limpio, siempre limpio" — es que si alguien introduce a + futuro uno de estos patrones conocidos (por ejemplo, un + `dangerouslySetInnerHTML` sin sanitizar en un componente nuevo), el + pipeline lo va a marcar en ese mismo push. + +## Por qué modo auditoría (no bloquea) en esta primera integración + +El step corre **sin** la flag `--error` a propósito — Semgrep siempre +termina con `exit 0`, reporta lo que encuentra pero nunca frena el +pipeline. Es la primera vez que esta herramienta corre sobre el repo: la +idea es revisar juntos qué reglas de los 91 activos generan ruido (falsos +positivos específicos de este código) antes de decidir cuáles deberían +pasar a bloquear. + +```mermaid +flowchart LR + A[Semgrep scan] --> B{hallazgos?} + B -- "sí" --> C["se reportan en el log
+ artifact JSON"] + B -- "no" --> C + C --> D["✅ pipeline sigue
(exit 0 siempre)"] +``` + +Una vez que se decida qué reglas son suficientemente confiables para +esta app, el paso natural es agregar `--error` **con un subconjunto** +de reglas (no las 91 completas) usando `--config` más específico o +`.semgrepignore` / reglas individuales marcadas como bloqueantes — +todavía no se hizo ese recorte. + +## Los rulesets elegidos + +- `p/typescript` — patrones generales de TypeScript/JavaScript. +- `p/react` — específico de componentes React (hooks mal usados, XSS vía + props/render, etc.). +- `p/nextjs` — patrones propios del framework (App Router, API routes, + middlewares). +- `p/security-audit` — catálogo transversal de seguridad (inyección, + criptografía débil, deserialización insegura) sin atarse a un + framework específico. + +Son rulesets **públicos y gratuitos** del registro de Semgrep — no +requieren cuenta ni login (`semgrep login` solo hace falta para acceder a +reglas adicionales de pago, que no se usan acá). + +## Dónde ver el resultado + +El JSON completo (`semgrep-report.json`) se publica como artifact del +pipeline (`semgrep-report`) en cada corrida, tenga o no hallazgos — +mismo patrón que Gitleaks y Trivy. diff --git a/workloads/docs-portal/mkdocs.yml b/workloads/docs-portal/mkdocs.yml index 8c02f9e..ee971a8 100644 --- a/workloads/docs-portal/mkdocs.yml +++ b/workloads/docs-portal/mkdocs.yml @@ -101,6 +101,7 @@ nav: - DevSecOps: - Gitleaks (secretos): devsecops/gitleaks.md - Trivy (imagen + IaC): devsecops/trivy.md + - SAST (Semgrep): devsecops/sast.md extra: social: