99 lines
5.4 KiB
JSON
99 lines
5.4 KiB
JSON
{
|
|
"feature_id": "NOTIF-INVENTORY-ALERTS",
|
|
"verdict": "APPROVED",
|
|
"agent": "security",
|
|
"security": "security",
|
|
"reviewed_at": "2026-08-27T21:25:45Z",
|
|
"scope": [
|
|
"project/src/modules/inventory/api/inventory.routes.ts",
|
|
"project/apps/admin/src/lib/api-client.ts",
|
|
"project/apps/admin/src/components/OrderNotifications.tsx"
|
|
],
|
|
"summary": "Sin nuevos secretos, sin nuevas dependencias, sin eval/source de inputs externos. Endpoint protegido con requireRole(admin). CTE parametrizada con DEFAULT_STORE_ID (no user input). Frontend usa Promise.all sobre 3 endpoints autenticados vía cookie de sesión existente. Sin findings críticos/altos. Verdict: APPROVED.",
|
|
"checks": [
|
|
{
|
|
"id": "no-secrets",
|
|
"description": "Cero secretos nuevos",
|
|
"command": "git diff project/ | grep -iE 'password|secret|token|key|api[_-]?key|credential' | grep -v 'sample\\|example\\|test'",
|
|
"result": "PASS",
|
|
"evidence": "Sin matches."
|
|
},
|
|
{
|
|
"id": "no-new-deps",
|
|
"description": "Cero dependencias nuevas (package.json sin cambios)",
|
|
"command": "git diff --name-only project/apps/admin/package.json project/package.json",
|
|
"result": "PASS",
|
|
"evidence": "Sin cambios en package.json. Solo uso de React hooks existentes, Fastify patterns existentes."
|
|
},
|
|
{
|
|
"id": "no-eval-source",
|
|
"description": "Sin eval/source de inputs externos",
|
|
"command": "grep -nE '\\beval\\(|require\\([\\\"\\'']\\s*\\+|new Function' project/src/modules/inventory/api/inventory.routes.ts project/apps/admin/src/components/OrderNotifications.tsx project/apps/admin/src/lib/api-client.ts",
|
|
"result": "PASS",
|
|
"evidence": "Sin matches. Uso exclusivo de template literals estáticos y string literals hardcoded."
|
|
},
|
|
{
|
|
"id": "endpoint-auth-admin",
|
|
"description": "Endpoint requiere rol admin",
|
|
"command": "grep -B 1 -A 3 'app.get..\\/inventory\\/admin\\/notifications' project/src/modules/inventory/api/inventory.routes.ts",
|
|
"result": "PASS",
|
|
"evidence": "await deps.authenticate(request) + requireRole(user, 'admin') antes de cualquier query. Test 'rejects non-admin with 403' valida."
|
|
},
|
|
{
|
|
"id": "sql-parameterized",
|
|
"description": "SQL parametrizado (sin concatenación de user input)",
|
|
"command": "grep -nE 'pool\\.query\\(|deps\\.pool\\.query\\(' project/src/modules/inventory/api/inventory.routes.ts | head -10",
|
|
"result": "PASS",
|
|
"evidence": "Ambas queries usan pool.query(sql, [DEFAULT_STORE_ID]). DEFAULT_STORE_ID es constante del módulo (no user input). Sin string interpolation de req.params, req.query o req.body en SQL."
|
|
},
|
|
{
|
|
"id": "frontend-no-xss",
|
|
"description": "Frontend no usa dangerouslySetInnerHTML",
|
|
"command": "grep -nE 'dangerouslySetInnerHTML|innerHTML|eval' project/apps/admin/src/components/OrderNotifications.tsx",
|
|
"result": "PASS",
|
|
"evidence": "Sin matches. Renderiza texto vía {item.label} y {item.count} que React escapa automáticamente."
|
|
},
|
|
{
|
|
"id": "href-no-open-redirect",
|
|
"description": "hrefs controlados por backend (no user input)",
|
|
"command": "code review",
|
|
"result": "PASS",
|
|
"evidence": "item.href viene del backend (literales hardcoded: /inventory?filter=low_stock, etc.). Frontend usa el href tal cual sin manipulación. Next Link valida protocolos (rechaza javascript:)."
|
|
},
|
|
{
|
|
"id": "no-cors-regression",
|
|
"description": "Sin nuevos CORS o cookies",
|
|
"command": "git diff project/ | grep -iE 'cors|access-control|set-cookie'",
|
|
"result": "PASS",
|
|
"evidence": "Sin matches. Hereda la config CORS/cookie del proyecto (no modificada)."
|
|
},
|
|
{
|
|
"id": "auth-cookie-session",
|
|
"description": "Peticiones autenticadas vía cookie de sesión existente",
|
|
"command": "grep -nE 'credentials|fetch' project/apps/admin/src/lib/api-client.ts | head -5",
|
|
"result": "PASS",
|
|
"evidence": "El api-client usa api.get() que internamente envía la cookie de sesión backoffice (gestionada por AuthProvider). Sin nuevos métodos de auth."
|
|
},
|
|
{
|
|
"id": "no-rate-limit-bypass",
|
|
"description": "Endpoint pasa por los mismos middlewares globales (rate limit si existe)",
|
|
"command": "code review",
|
|
"result": "PASS",
|
|
"evidence": "El endpoint se registra via app.get() igual que el resto de rutas. Los middlewares globales (incluyendo rate limit si está configurado en build-app.ts) se aplican automáticamente."
|
|
}
|
|
],
|
|
"findings": [],
|
|
"notes": [
|
|
"El endpoint /inventory/admin/notifications solo expone counts agregados, no datos sensibles por producto. Un atacante autenticado como admin ya tiene acceso a /inventory/admin/overview que devuelve detalles.",
|
|
"El campanita (OrderNotifications) está gated por `user.role === 'admin'` en el layout, así que non-admin ni siquiera ve la UI."
|
|
],
|
|
"verdict_rationale": "10/10 checks PASS. Sin secretos, sin nuevas deps, sin eval/source, SQL parametrizado, auth admin en endpoint, frontend sin XSS vector, hrefs controlados por backend, sin cambios en CORS/cookies. APPROVED.",
|
|
"evidence_paths": [
|
|
"work/artifacts/NOTIF-INVENTORY-ALERTS/architect.md",
|
|
"work/artifacts/NOTIF-INVENTORY-ALERTS/implementer.md",
|
|
"work/artifacts/NOTIF-INVENTORY-ALERTS/reviewer.json",
|
|
"project/src/modules/inventory/api/inventory.routes.ts",
|
|
"project/apps/admin/src/lib/api-client.ts",
|
|
"project/apps/admin/src/components/OrderNotifications.tsx"
|
|
]
|
|
} |