Seguridad Web — OnnixConnect
Guía de referencia para las medidas de seguridad implementadas en OnnixConnect. Cubre rate limiting, sanitización de logs SOAP, timeout de sesión e IP lock en tokens API.
Resumen de cambios (2026-04-29)
| Feature | Archivos principales | Estado |
|---|---|---|
| Rate limiting en todos los endpoints | AppServiceProvider, routes/api.php, routes/web.php | Activo |
| Sanitización de logs SOAP | SifenHttpClient, SifenBatchService | Activo |
| Session timeout configurable (web) | CheckSessionTimeout, config/security.php | Activo |
| IP lock en tokens Sanctum (API) | CheckTokenIp, migración created_ip | Activo |
1. Rate Limiting
Limiters definidos (AppServiceProvider::boot())
| Nombre | Límite | Aplica a | Clave |
|---|---|---|---|
login | 5 req / 15 min | POST /api/auth/token, POST /login web | email + IP |
api | 100 req / 15 min | Todos los endpoints de lectura autenticados | IP |
sifen:send | 60 req / 15 min | Envíos SIFEN: sendSync, sendBatch, store, sign, reenviar, eventos | IP + user ID |
sifen:admin | 10 req / 15 min | Gestión de emisores (crear, editar, cert, logo) + escritura multitenant | IP |
Comportamiento al exceder el límite
Laravel retorna automáticamente:
HTTP/1.1 429 Too Many Requests
Retry-After: 847
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
El cliente debe respetar el header Retry-After (segundos hasta poder reintentar).
Estructura en routes/api.php
auth:sanctum + throttle:api → todos los endpoints autenticados
└── throttle:sifen:admin → emitters CRUD + multitenant escritura
└── throttle:sifen:send → envíos a SIFEN
throttle:login → POST /api/auth/token + POST /login web
Agregar rate limiting a endpoints nuevos
Todo endpoint nuevo bajo auth:sanctum hereda automáticamente throttle:api.
Para endpoints sensibles, agregar al sub-grupo correspondiente:
// Endpoint administrativo
Route::middleware(['permission:emitter:manage', 'throttle:sifen:admin'])->group(function () {
Route::post('/nuevo-endpoint-admin', ...);
});
// Endpoint de envío SIFEN
Route::middleware(['permission:sifen:send', 'throttle:sifen:send'])->group(function () {
Route::post('/nuevo-endpoint-send', ...);
});
2. Sanitización de Logs SOAP
Problema original
SifenHttpClient::guardarLog() persistía el XML SOAP completo en disco, incluyendo datos criptográficos del contribuyente.
Solución implementada
SifenHttpClient::sanitizarParaLog() reemplaza antes de persistir:
<ds:SignatureValue>ABCD...==</ds:SignatureValue>
→ <ds:SignatureValue>[REDACTED]</ds:SignatureValue>
<ds:X509Certificate>MIIC...==</ds:X509Certificate>
→ <ds:X509Certificate>[REDACTED]</ds:X509Certificate>
<ds:DigestValue>HASH...==</ds:DigestValue>
→ <ds:DigestValue>[REDACTED]</ds:DigestValue>
<xDE>UEsD...==</xDE>
→ <xDE>[BASE64_ZIP_REDACTED]</xDE>
Se preservan: CDC, dCodRes, dMsgRes, dFecProc, estructura XML.
SifenBatchService reemplazó el log del XML completo del lote por un JSON de metadata:
{
"batch_id": 1,
"timestamp": "2026-04-29T14:30:00-03:00",
"dte_count": 3,
"cdc_list": ["0180069563..."],
"cod_res": "0300",
"prot_lote": "01234567890123"
}
Invariante de seguridad
La sanitización ocurre después de que cURL envía el payload a SIFEN. El XML original nunca se modifica. Verificado con 4 tests en SifenHttpClientTest.
3. Session Timeout (Web)
Cómo funciona
El middleware CheckSessionTimeout se aplica globalmente al grupo web. En cada request autenticado:
- Lee
session('last_activity'). - Si
now() - last_activity > SESSION_TIMEOUT_MINUTES * 60→ cierra sesión y redirige al login. - Si no → actualiza
last_activity.
Configuración
SESSION_TIMEOUT_ENABLED=true
SESSION_TIMEOUT_MINUTES=30
Cuándo desactivarlo
- Clientes con formularios lentos o trabajo no guardado.
- Entornos de testing/staging.
SESSION_TIMEOUT_ENABLED=false
4. IP Lock en Tokens Sanctum (API)
Cómo funciona
Al crear un token (POST /api/auth/token), se guarda la IP del cliente en personal_access_tokens.created_ip.
En cada request posterior, el middleware CheckTokenIp compara la IP actual con la registrada. Si no coinciden:
HTTP 401
{
"message": "Token inválido: la solicitud proviene de una IP diferente a la registrada. Generá un nuevo token desde tu IP actual.",
"code": "TOKEN_IP_MISMATCH"
}
El token queda revocado inmediatamente.
Configuración
API_IP_LOCK_ENABLED=true
Cuándo desactivarlo
- Empresa con balanceo de carga o múltiples IPs de salida.
- CDN o proxy con IP dinámica.
Tokens anteriores al feature
Tokens con created_ip = NULL están exentos. Para que queden bajo IP lock, el usuario debe revocarlo y generar uno nuevo.
5. Variables de Entorno de Seguridad
| Variable | Default | Descripción |
|---|---|---|
SESSION_TIMEOUT_ENABLED | true | Activa el timeout por inactividad en sesión web |
SESSION_TIMEOUT_MINUTES | 30 | Minutos de inactividad antes de cerrar sesión web |
API_IP_LOCK_ENABLED | true | Revoca tokens API si cambia la IP de origen |
6. Cómo una Empresa Consume la API
Paso 1 — Setup
El admin crea una cuenta de servicio dedicada con rol operador y permiso sifen:send.
Paso 2 — Obtener token desde IP fija
POST /api/auth/token
Content-Type: application/json
{
"email": "erp@empresa.com.py",
"password": "...",
"device_name": "erp-produccion-v1"
}
Respuesta:
{
"token": "5|abc123xyz...",
"type": "Bearer",
"expires_at": "2026-07-29T00:00:00Z"
}
Guardar el token en variable de entorno o secrets manager. Nunca en el código fuente.
Paso 3 — Usar el token
POST /api/sifen/dtes/sync
Authorization: Bearer 5|abc123xyz...
Content-Type: application/json
Paso 4 — Manejo de errores
| HTTP | code | Causa | Acción |
|---|---|---|---|
401 | TOKEN_IP_MISMATCH | Cambio de IP | Generar nuevo token desde la IP actual |
401 | — | Token expirado | Re-autenticar con POST /api/auth/token |
429 | — | Rate limit | Esperar Retry-After segundos y reintentar |
422 | — | Payload inválido | Revisar el cuerpo del request |
Paso 5 — Rotación de token
POST /api/auth/token/refresh
Authorization: Bearer 5|abc123xyz...
Devuelve nuevo token sin pedir password. El anterior queda revocado.
Consideración con IP Lock
Con API_IP_LOCK_ENABLED=true, el servidor cliente debe tener IP estática. Si el proveedor rota IPs, usar un proxy con IP fija o desactivar el lock.
7. Tests de Seguridad
# Solo seguridad
php artisan test tests/Feature/RateLimitingTest.php
php artisan test tests/Unit/SifenHttpClientTest.php
# Suite completa
php artisan test
# Expected: 168 passed, 1 skipped
RateLimitingTest (15 tests): limiters definidos, 429 en todos los grupos, headers correctos, middleware en login web.
SifenHttpClientTest — sanitización (4 tests): payload intacto, campos criptográficos redactados, CDC preservado.
8. Pendientes (Próximos Sprints)
| Item | Prioridad |
|---|---|
| Headers HTTP de seguridad (CSP, X-Frame-Options, HSTS) | Alta |
SESSION_ENCRYPT=true en producción | Media |
SESSION_SECURE_COOKIE=true con HTTPS | Media |
Reducir token_expiry_days de 365 a 30-90 días | Media |
| Logging intentos fallidos de autenticación | Media |
| FormRequests en los ~25 endpoints con validación inline | Baja |
| IP ranges CIDR para API lock | Baja |
| UI toggle session timeout en módulo de Configuración | Baja |