30 lines
2.9 KiB
JSON
30 lines
2.9 KiB
JSON
{
|
|
"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"
|
|
}
|