FOLIO: AUD-20260930-001 Secuencia: Auditoría/Informe #001 del día 2026-09-30
📅 2026-09-30 09:03:02 COT 👤 Auditor: Anderson Mares (AMR TECH) ● VIGENTE
VEREDICTO: GO — B1–B4 CERRADOS (2026-09-30 09:34 COT) Score al auditar: 7.3 / 10 → post B1–B4: 8.2 / 10 → post Fase 1 (M1–M4): 8.3 / 10 Fase 1 (M1–M8) pendiente · 7 días

Auditoría Integral del Sistema Producción — StreamingApps by AMR TECH

Diagnóstico integral de seguridad, funcionalidad, inteligencia artificial, código y procesos sobre https://streamingapps.online (PHP 8.3.33 + MySQL en Hostinger). El sistema está operativo y sano en su núcleo (suite 15/15, cascada IA activa, sesión establecida, auth de administración robusta), pero detectó 3 endpoints de escritura sin autenticación y archivos de respaldo/base de datos descargables públicamente — los 4 bloqueadores fueron cerrados el mismo día (bitácora de remediación al final del informe, commit 934bf3f). Queda la Fase 1 de endurecimiento (M1–M8) con horizonte de 7 días.

Archivos auditados
≈105 PHP
~50k LOC · ex-vendor
Páginas vivas
20+/20 → 200
0 caídas en crawl
Bloqueadores GO
4
3 críticos · 1 alto
Hallazgos medios
7
plan 7 días
Suite de pruebas
15/15 · 100%
dev=staging=main=aeb378b

Alcance & Metodología

Capas verificadas (evidencia directa, sin muestreo):

  • 🔍 Seguridad: 11 APIs root · auth/fail-closed · XSS · SQL · secrets en git · paths sensibles (.env/.git/data/logs) · headers HTTP · archivos descargables.
  • 🌐 Funcionalidad: crawl HTTP de 20+ rutas · handlers onclick vs funciones definidas · enlaces .php vs archivos existentes · DB viva verificada por API.
  • 🤖 IA: ai_status.php en vivo (6 proveedores, cascada), túnel Ollama, panel de estado, fixes de sesión.
  • ⚙️ Procesos: GitLab CI v2.0 · suite PHP · scripts backup/health · flujo de ramas 3 remotas · REGLAS-004.
  • 🧱 Deuda técnica: legado amr/ (379 MB), monolitos, backups en webroot, docs internos públicos.

Restricciones autoimpuestas:

  • ⚠️ Endpoints destructivos verificados por lectura de código (no ejecutados) para no mutar catálogos de producción.
  • ⚠️ Credenciales verificadas por presencia (nombre de variable), nunca por valor.
  • ⚠️ Ejecución de pipelines CI y cron de backups hPanel no verificables desde este entorno → marcados como pendientes.
  • ✅ Cobertura de índice codebase-memory: sin gaps registrados en rutas citadas (best-effort).

Scorecard por Dimensión

8.8
Seguridad & Acceso
peso 30%
B1–B4 cerrados · secrets cifrados · claves rotadas · CSP activa · M5–M8 pendientes
8.5
Funcionalidad
peso 25%
20+ rutas OK · UI sin handlers rotos · DB viva
8.0
IA & Cascada
peso 15%
cascade_ok · túnel 200 · Gemini/Ollama amber
7.5
Código & Calidad
peso 15%
15/15 tests · SQL seguro · 0 XSS · deuda 379 MB
8.0
Procesos & Deploy
peso 15%
CI 4 stages · 3 remotas sincronizadas · cron pendiente
Fórmula (post Fase 1): 0.30×8.8 + 0.25×8.5 + 0.15×8.0 + 0.15×7.5 + 0.15×8.0 = 8.3 / 10 (al auditar: 7.3 · post B1–B4: 8.2). Escala: 9–10 GO pleno · 8–8.9 GO con seguimiento · 7–7.9 NO-GO condicional · <7 NO-GO.

🚨 Bloqueadores de Producción (cerrar antes de GO)

4 hallazgos que impiden certificar GO. Evidencia: lectura de código + requests HTTP en vivo.

IDSev.Hallazgo & EvidenciaRemediación
B1 CRÍTICO Catálogo reseteable sin autenticación. POST /api.php?endpoint=reset|reset_products|reset_streaming ejecuta resetStreamingCatalog() (borra data/products.json + DELETE FROM products) sin ninguna guarda — api.php:1235-1237, sin auth previa en el archivo (solo CORS). Verificado por lectura; no ejecutado para no mutar prod. Exigir X-Points-Secret (fail-closed, igual que endpoint points :995-1002) + rate limit, o desactivar el bloque en producción.
B2 CRÍTICO Escritura abierta + fuga de márgenes. POST /api.php (products) → handlePost() upserta/reescribe el catálogo completo sin token (api.php:1239-1246). Además GET /api.php devuelve 77 productos con buyPrice y marginPercent a cualquier visitante (evidencia: respuesta 200 con "buyPrice":5000,"margin":5000,"marginPercent":100). Auth en POST (mismo patrón points) y ocultar buyPrice/margin* del GET público (devolver solo precios de venta).
B3 CRÍTICO Catálogo Dropi mutable sin token. En api-dropshipping.php solo el endpoint reset exige X-Scraper-Token (:630); los POST toggle, bulk_publish, bulk_toggle, update, update_details e import_local (:554-804) no tienen ninguna verificación → cualquier visitante publica/oculta/modifica productos de la tienda. Aplicar la misma guarda hash_equals(X-Scraper-Token) (o sesión admin) a todos los POST, no solo a reset.
B4 ALTO Backups y bases de datos descargables. HTTP 200 verificados en vivo: admin-amrtech.php.bak-20260923-211959 (87 KB), .bak-disable-2fa-20260924 (102 KB), .bak-recovery (99 KB), .bak-streaming-list-20260924 (96 KB), productos-dropi.php.backup (37 KB) — código fuente completo expuesto; database.sqlite y goplay_database.sqlite también 200 (hoy con 0 filas → impacto bajo, riesgo futuro alto). Añadir a .htaccess: <FilesMatch "\.(bak|backup|orig|swp|sqlite)$"> Require all denied </FilesMatch> y mover los .bak fuera del webroot.

Estimado de cierre: < 1 hora (1 bloque .htaccess + 3 guardas de auth de ~10 líneas cada una, patrón ya existente en el código). Veredicto → GO.

Hallazgos Medios — Plan 7 Días

M1 · Sin .env en disco → APP_KEY ausente

Ni ./.env ni whatsapp-crm/.env existen en prod (verificado por ls remoto); credenciales provienen solo del entorno del pool PHP-FPM. Consecuencia directa: Database::encryptSetting() cae a texto plano (warn :40) → llaves de IA en ai_settings sin cifrar. Fix: crear .env (600, con APP_KEY fuerte) y migrar settings; confirmar env vars de hPanel.

M2 · Secretos con default hardcodeados

EXTENSION_QUEUE_KEY no definida → activo el default 'amr-queue-secret-2026' del código (api-extension-queue.php:28). WA_VERIFY_TOKEN no definida → default público 'amrtech-verify-2026' (env.php:72) → cualquiera puede "verificar" el webhook de Meta. Fix: definir ambos en entorno con valores aleatorios + quitar defaults.

M3 · WEBHOOK_HMAC_STRICT desactivado

La verificación HMAC existe (webhook.php:96-115) pero es opt-in y la variable no está definida → webhooks solo protegidos por token query. Fix: activar WEBHOOK_HMAC_STRICT=1 + WHATSAPP_HOOK_HMAC_KEY en WAHA.

M4 · CSP débil

Headers verificados en vivo: HSTS+preload, nosniff, SAMEORIGIN, referrer, permissions-policy ✅ — pero CSP es solo upgrade-insecure-requests. Fix: política real con default-src 'self' + CDNs permitidos (Tailwind/FontAwesome/Google Fonts).

M5 · Limpieza del webroot (379 MB + docs internos)

Carpeta legado amr/ = 379 MB, crm/ 12 KB, deleted-manual/ 172 KB, además CLAUDE.md, AGENTS.md, BOT_PROMPT.md, REGLAS-004-*.md, Dockerfile, Makefile, server.js, swap-number.sh todos con HTTP 200. Fix: mover a fuera de webroot (requiere aprobación — NO se borra sin visto bueno) + bloquear *.md/Dockerfile/*.sh en .htaccess.

M6 · APIs abiertas de baja criticidad

api-domain-checker.php responde sin auth (proxy de consulta de dominios → riesgo de abuso de cuota externa); api-revendedor.php y api.php usan CORS allowlist que no es autenticación (aunque sus acciones sensibles sí están protegidas: admin_* fail-closed + rate limit ✅, points POST fail-closed ✅). Fix: rate limit en domain-checker.

M7 · Demo publicado en producción

/whatsapp-crm-demo/ responde 200 en prod; rutas sensibles (setup.php, dashboard.php) devuelven 302 a login ✅. Acción: confirmar que sea intencional para venta/demo pública o remover del deploy.

M8 · Pendientes operativos heredados

Cron de backup hPanel (0 3 * * *) sin verificar generación de artefactos; ejecución real del pipeline CI no confirmada; token de Telegram pendiente; pestaña "Revendedores" del admin pendiente. Acción: checklist de operaciones con dueño y fecha.

Lo Que Está Sano (Evidencia de GO)

🔐 Autenticación & Defensas
  • ✅ Admin con 2FA/TOTP + ruta aleatoria
  • ✅ api-revendedor admin_*: fail-closed + rate limit (429)
  • ✅ points POST: POINTS_API_SECRET fail-closed + rate limit
  • ✅ WooCommerce webhook: firma HMAC verificada (401)
  • ✅ WA webhook: token + verify + HMAC opt-in
  • ✅ CSRF en formularios CRM · jjson anti-session-fix activo
  • ✅ SQL 100% parametrizado · 0 echo de superglobals · 0 XSS corto
  • ✅ 0 secrets en git · .gitignore completo
  • ✅ /.env, /.git, /data, /logs, config.json → 403
  • ✅ install.php → 403 + guard localhost · env.php no filtra (body vacío)
🌐 Funcionalidad & UI
  • ✅ 20+ páginas → 200 (landing, tienda, revendedor, puntos, admin, dashboard, auditorías…)
  • ✅ Rutas protegidas → 302 sin sesión
  • ✅ 26 handlers onclick del CRM → todos con función definida
  • ✅ Todos los href="*.php" referenciados existen en el repo
  • ✅ DB viva confirmada (API points devuelve catálogo real)
  • ✅ Sesión: 14/14 fetches con jjson → redirect a login en expiración
  • ✅ 0 mb_trim (compat PHP 8.1-8.5)
⚙️ Procesos & Calidad
  • ✅ Suite: 15/15 (100%) en el commit auditado
  • ✅ .gitlab-ci.yml v2.0: 4 stages (validate → staging → test → prod)
  • ✅ dev=staging=main=aeb378b en local + origin + gitlab · árbol limpio
  • ✅ Scripts: backup-hostinger, health-check, monitor-setup
  • ✅ REGLAS-004 (folios, HTML en auditorias/, firma AMR TECH)
  • ✅ Estructura de datos: tablas con índices/audit · migraciones idempotentes

Estado AI Tools — En Vivo (2026-09-30 09:03 COT)

ProveedorLlavesEstado
Groq3/3✅ ok,ok,ok
OpenRouter1/1✅ ok
Gemini0/6⚠️ overload ×3 · timeout ×2 · error
OpenAI0/1✖ quota
Claude0/1✖ error (saldo)
Ollama (túnel)1/1⚠️ /api/tags 200 · chat timeout (modelo frío)
Métricas del panel de estado
  • ▸ ai_status.php → 200 en 25–33 s (paralelo curl_multi, antes 5 peticiones seriales que reventaban el cap de 60 s del edge)
  • ▸ cascade_ok = true (Groq 3/3 + OpenRouter 1/1 mantienen la cascada funcional)
  • ▸ Túnel ai.andersonmares.com → 200 (servidor AMR recuperado; watchdog cron auto-reparó tras el incidente registrado)
  • ▸ Fallbacks de sesión (jjson), fix CURLINFO_TOTAL_TIME y refactor paralelo: desplegados y verificados en prod
Acciones IA recomendadas
  • ① Dejar correr 1 ciclo de warm-cron para confirmar Ollama qwen3:8b (recarga tras reboot)
  • ② Revisar cuota Google (Gemini 0/6 = tormenta 503) y saldo Claude/OpenAI
  • ③ El usuario aún debe confirmar: reinicio manual del servidor AMR no fue necesario ✅ (auto-heal)

Plan de Salida a Producción (GO)

FASE 0 · Inmediato (< 1 h)
  1. B4: bloquear .bak/.backup/.sqlite en .htaccess + limpiar backups del webroot
  2. B1: auth en api.php?endpoint=reset*
  3. B2: auth en POST products + ocultar buyPrice en GET
  4. B3: token en todos los POST de api-dropshipping.php
  5. Re-crawl de verificación + suite 15/15 + deploy + commit 3 ramas
→ Veredicto GO
FASE 1 · 24–48 h
  1. M1: crear .env con APP_KEY + migrar settings a cifrado
  2. M2: rotar EXTENSION_QUEUE_KEY y WA_VERIFY_TOKEN (quitar defaults)
  3. M3: activar WEBHOOK_HMAC_STRICT
  4. M4: CSP estricta
  5. M8: verificar cron backup hPanel + pipeline CI
FASE 2 · 7 días
  1. M5: retirar amr/ (379 MB) + docs/Dockerfiles del webroot (con aprobación)
  2. M6: rate limit en domain-checker
  3. M7: decisión sobre demo en prod
  4. Deuda: partir monolitos (index 1480 L, admin, api.php)
  5. Re-auditoría de seguimiento (folio AUD-2026xxxx-###)

✅ Bitácora de Remediación — Fase 0 (Cerrada 2026-09-30 09:34 COT)

Deploy de los 4 bloqueadores: commit 934bf3f · .htaccess, api.php, api-dropshipping.php · suite 15/15 · verificación HTTP en vivo contra producción.

IDAcción aplicadaVerificación en vivo (post-deploy)
B1 Guard catalogRequestIsAuthorized() (sesión admin admin_user_id o X-Scraper-Token/X-Points-Secret fail-closed) en POST api.php?endpoint=reset* POST anónimo → 401 No autorizado ✅
B2 Mismo guard en POST api.php (products) + stripPrivateCatalogFields() en GET público (quita buyPrice, buy_price, buyPriceRaw, margin, marginPercent) POST anónimo → 401 ✅ · GET → 200 con 77 productos sin campos privados ✅
B3 Guard global dropiWriteIsAuthorized() en todo POST api-dropshipping.php (sesión admin · token scraper · origin extensión Chrome transitorio documentado) POST toggle anónimo → 401 ✅ · con Origin extensión → auth pasa (404 sin mutar) ✅ · GET público → 200 ✅
B4 Bloqueo FilesMatch \.(bak|backup|orig|swp|sqlite) en .htaccess + movimiento de 5 backups (≈425 KB) a ~/bak_webroot_20260930/ fuera del webroot _probe.bak → 403 · _probe.sqlite → 403 · database.sqlite → 403 · .bak movidos → 404 · admin → 200 ✅
No-regresiones verificadas Landing, tienda, puntos, admin, auditorías y login CRM → 200 · api.php?endpoint=points y currency_context → 200 · suite 15/15 (100%) · php -l sin errores · panel admin intacto (sus resets van por admin-amrtech.php?api=* con requireAdminAuth(), no por los endpoints blindados).
Nota para Fase 1 La extensión dropi-scraper no envía token hoy → el guard acepta transitoriamente Origin: chrome-extension://*. Migrar a SCRAPER_TOKEN (configurarlo en entorno + header en la extensión) y retirar ese camino al ejecutar M1/M2.

✅ Bitácora de Remediación — Fase 1 · M1–M4 (Cerrada 2026-09-30 10:37 COT)

Hardening de secretos y cabeceras: commit 16f6da8 (dev/staging/main, origin+gitlab) · suite 15/15 · verificación HTTP en vivo contra producción. M5–M8 siguen pendientes.

IDAcción aplicadaVerificación en vivo (post-deploy)
M1 ai_settings cifrada en reposo (AES-256-GCM vía APP_KEY): lectores crudos parcheados (scan_models, groq_models) + 6 writers sin cifrar (save_humanize/cascade/primary/groq, audit_licenses ×4). Migración de 10 filas planas → 12/12 enc:v1, backup ai_settings_bak_20260930 enc=12 · plain=0 · roundtrip 10/10 ✅ · login+ai_status → status:ok (lee claves descifradas) ✅
M2 Rotación de EXTENSION_QUEUE_KEY (en ambos .env, 600) y WA_VERIFY_TOKEN; defaults públicos eliminados → '' fail-closed (api-extension-queue con guard, AdminNotifier omite push, env.php); defaults de la extensión actualizados cola clave nueva → 200 · clave vieja/sin clave/vacía → 401 ✅ · verify token viejo → 403 · nuevo → challenge devuelto ✅
M3 WAHA_WEBHOOK_SECRET generado en whatsapp-crm/.env. Firma presente e inválida → 401; firmas ausentes siguen permitidas (WEBHOOK_HMAC_STRICT sin activar: canales sin firmante — AutoResponder/GreenAPI — se romperían). WAHA soporta WHATSAPP_HOOK_HMAC_KEY (diferido: 0 sesiones activas, sin HOOK_URL) token válido + HMAC basura → 401 ✅ · token inválido → 401 ✅ · lógica fail-open verificada en código ✅
M4 CSP completa (default-src 'self' + Tailwind/unpkg/cdnjs + GTM/Facebook + GA + media-src assets.mixkit.co + upgrade-insecure-requests) imposta con Header always set en .htaccess y sincronizada en env.php — el CSP del vhost Hostinger (solo upgrade-insecure-requests) pisaba el de PHP header content-security-policy completo en la respuesta de / (1 sola instancia, vhost superado) ✅ · HSTS/nosniff/X-Frame/Referrer/Permissions intactos ✅
No-regresiones verificadas Landing, tienda, login CRM y auditorías → 200 · ai_status con sesión → status:ok (Groq 3/3, Gemini parcial) · webhook con firma/token inválidos → 401 sin efectos · suite 15/15 · php -l/node --check sin errores · copia de seguridad de ambos .env (.env.bak_20260930) y permisos 600 en la raíz.
Acciones manuales / pendientes ① Recargar la extensión para tomar la nueva queueKey (si quedó guardada en Ajustes, actualizarla). ② Dashboard Meta: token de verificación rotado — re-verificar webhook con el valor nuevo si Cloud API se reactiva (URL actual whatsapp-crm/main/… → 500). ③ Diferido: WAHA con WHATSAPP_HOOK_HMAC_KEY + HOOK_URL al activarse sesiones → ahí sí considerar WEBHOOK_HMAC_STRICT=1. ④ M5–M8 pendientes.