Files
mercadodevida/work/artifacts/F-006/security.json
rikrdo 546971280f feat(F-006): users profile, addresses and RBAC
- users module: profile + address CRUD behind use cases (users_profiles,
  users_addresses)
- roles customer/admin on identity_users; role resolved from DB per request
- shared auth contract (Authenticate, requireRole, requireOwnerOrAdmin)
  injected from composition root; users never imports identity
- authorization runs before existence checks; address SQL scoped by user_id
- @fastify/cookie registered once at app root (cross-module)
- migrations 003_identity_roles + 004_users (reversible)
- no new npm dependencies; tests: unit 52, integration 22

Gates: reviewer/security/qa APPROVED; verify.sh green
2026-08-15 09:28:15 +02:00

41 lines
2.2 KiB
JSON

{
"feature_id": "F-006",
"agent": "security",
"verdict": "APPROVED",
"threat_model": [
{
"vector": "SQL injection",
"result": "mitigated",
"evidence": "Every query parameterized ($n). The only dynamic SQL (profile upsert, address update) builds SET clauses from a hardcoded column whitelist; user input only flows into parameter values."
},
{
"vector": "Privilege escalation (client sets own role)",
"result": "mitigated",
"evidence": "register/login schemas accept only email+password; no route writes identity_users.role. Role is returned from DB values, never echoed from input. DB CHECK constraint limits role to customer|admin."
},
{
"vector": "Broken access control / IDOR",
"result": "mitigated",
"evidence": "Each users route runs authenticate -> requireOwnerOrAdmin before data access; address repository scopes every query by user_id, so a valid foreign addressId is unreachable. Integration tests prove cross-user reads and mutations all return 403."
},
{
"vector": "Resource existence enumeration",
"result": "mitigated",
"evidence": "Authorization (403) is evaluated before existence checks (404) for non-owners, so a stranger cannot learn whether another user's profile exists."
},
{
"vector": "Stale authorization (demotion/promotion)",
"result": "mitigated",
"evidence": "Session authenticator resolves role from identity_users on every request; role changes apply immediately without re-login, and no role state is trusted from the client."
},
{
"vector": "Session security",
"result": "unchanged from F-005",
"evidence": "Opaque token in HttpOnly/SameSite=Lax cookie, SHA-256 hash stored server-side, expiry + revocation enforced in SQL. Cookie plugin moved to app root (cross-module), same policy."
}
],
"dependencies_added": [],
"notes": "No new npm dependencies; spec/tech.md unchanged. The 403-vs-404 ordering is correct for anti-enumeration. GET /users admin list returning only profiled users is not a security issue (admin-only surface).",
"evidence": "npm run test:integration: 22 passed including forged-cookie 401, cross-user 403 battery, and logout revocation regression"
}