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.
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
.phpvs archivos existentes · DB viva verificada por API. - 🤖 IA:
ai_status.phpen 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
🚨 Bloqueadores de Producción (cerrar antes de GO)
4 hallazgos que impiden certificar GO. Evidencia: lectura de código + requests HTTP en vivo.
| ID | Sev. | Hallazgo & Evidencia | Remediació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
.env en disco → APP_KEY ausenteNi ./.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.
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.
WEBHOOK_HMAC_STRICT desactivadoLa 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.
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).
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.
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.
/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.
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)
- ✅ Admin con 2FA/TOTP + ruta aleatoria
- ✅
api-revendedor admin_*: fail-closed + rate limit (429) - ✅
pointsPOST:POINTS_API_SECRETfail-closed + rate limit - ✅ WooCommerce webhook: firma HMAC verificada (401)
- ✅ WA webhook: token + verify + HMAC opt-in
- ✅ CSRF en formularios CRM ·
jjsonanti-session-fix activo - ✅ SQL 100% parametrizado · 0
echode superglobals · 0 XSS corto - ✅ 0 secrets en git ·
.gitignorecompleto - ✅
/.env,/.git,/data,/logs,config.json→ 403 - ✅
install.php→ 403 + guard localhost ·env.phpno filtra (body vacío)
- ✅ 20+ páginas → 200 (landing, tienda, revendedor, puntos, admin, dashboard, auditorías…)
- ✅ Rutas protegidas → 302 sin sesión
- ✅ 26 handlers
onclickdel CRM → todos con función definida - ✅ Todos los
href="*.php"referenciados existen en el repo - ✅ DB viva confirmada (API
pointsdevuelve 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)
- ✅ Suite: 15/15 (100%) en el commit auditado
- ✅
.gitlab-ci.ymlv2.0: 4 stages (validate → staging → test → prod) - ✅
dev=staging=main=aeb378ben 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)
| Proveedor | Llaves | Estado |
|---|---|---|
| Groq | 3/3 | ✅ ok,ok,ok |
| OpenRouter | 1/1 | ✅ ok |
| Gemini | 0/6 | ⚠️ overload ×3 · timeout ×2 · error |
| OpenAI | 0/1 | ✖ quota |
| Claude | 0/1 | ✖ error (saldo) |
| Ollama (túnel) | 1/1 | ⚠️ /api/tags 200 · chat timeout (modelo frío) |
- ▸
ai_status.php→ 200 en 25–33 s (paralelocurl_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), fixCURLINFO_TOTAL_TIMEy refactor paralelo: desplegados y verificados en prod
- ① 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)
- B4: bloquear
.bak/.backup/.sqliteen .htaccess + limpiar backups del webroot - B1: auth en
api.php?endpoint=reset* - B2: auth en POST products + ocultar
buyPriceen GET - B3: token en todos los POST de
api-dropshipping.php - Re-crawl de verificación + suite 15/15 + deploy + commit 3 ramas
- M1: crear
.envconAPP_KEY+ migrar settings a cifrado - M2: rotar
EXTENSION_QUEUE_KEYyWA_VERIFY_TOKEN(quitar defaults) - M3: activar
WEBHOOK_HMAC_STRICT - M4: CSP estricta
- M8: verificar cron backup hPanel + pipeline CI
- M5: retirar
amr/(379 MB) + docs/Dockerfiles del webroot (con aprobación) - M6: rate limit en domain-checker
- M7: decisión sobre demo en prod
- Deuda: partir monolitos (index 1480 L, admin, api.php)
- 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.
| ID | Acción aplicada | Verificació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 ✅ |
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).
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.
| ID | Acción aplicada | Verificació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 ✅ |
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.
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.