{
  "feature_id": "F-152",
  "agent": "security",
  "stage": "security_gate",
  "verdict": "APPROVED",
  "summary": "F-152 no amplía superficie de autorización ni expone nuevos endpoints públicos. No se hardcodean credenciales SMTP (se leen de store_settings vía pool); el email de confirmación reusa el patrón existente de order-status-mailer (read-only pool lookup de identity_users.email). Los emails son best-effort y no bloquean el registro/webhook. Personalización HTML se escapa (XSS-safe). El gate outcome.kind==='processed' previene envíos duplicados ante replay de webhooks.",
  "evidence": [
    "No se añaden endpoints públicos nuevos: POST /auth/register y POST /payments/webhook ya existían",
    "No se modifica RBAC ni requireRole en ningún route; register sigue siendo público (comportamiento previsto)",
    "SMTP credentials no aparecen en código: SettingsWelcomeMailer.readSmtpOptions lee store_settings (smtp_host/port/secure/user/pass/from) vía pg pool",
    "Order confirmation email lookup es read-only pool (orders_orders.user_id -> identity_users.email), mismo patrón que sendOrderStatusEmail/order-status-mailer",
    "Best-effort: welcome .catch(request.log.warn); order confirmation en try/catch con warn -> no excepción al cliente",
    "Idempotencia webhook: outcome.kind==='processed' garantiza un solo email por PaymentSucceeded (PaymentsService devuelve kind:'duplicate' antes de transición)",
    "XSS-safe: buildWelcomeEmail HTML-escapea name (test unitario)",
    "No new data at rest; no new tokens/secrets"
  ],
  "checks": [
    {"item": "Autorización / RBAC intacta", "ok": true, "evidence": "No se modifican requireRole ni middlewares de auth; register sigue público, webhook sigue sin auth (preexistente)"},
    {"item": "No credenciales SMTP en código", "ok": true, "evidence": "SettingsWelcomeMailer readSmtpOptions(pool) consulta store_settings; prettier/eslint no detectan secrets; git diff --check limpio"},
    {"item": "Lookup de email cliente read-only", "ok": true, "evidence": "payments.routes usa pool.query SELECT identity_users.email FROM orders_orders o JOIN identity_users; no writes"},
    {"item": "Best-effort no expone errores internos", "ok": true, "evidence": "welcome .catch(warn); order-conf try/catch warn; registro/webhook nunca propagan error SMTP al cliente"},
    {"item": "Idempotencia frente a replay", "ok": true, "evidence": "gate outcome.kind==='processed' + PaymentsService kind:'duplicate'"},
    {"item": "XSS en personalización", "ok": true, "evidence": "buildWelcomeEmail escapea name (test)"},
    {"item": "Data at rest / secrets", "ok": true, "evidence": "No se crean tablas ni columnas nuevas; no se añaden env vars/secretos nuevos"}
  ],
  "issues": [],
  "notes": "Hardening futuro opcional: validar/descartar emails con header CRLF injection en readSmtpOptions antes de pasar a nodemailer; fuera del scope de F-152.",
  "timestamp": "2026-08-22T05:05:10Z"
}
