- Remove `type` keyword from NestJS injectable class imports across all modules to fix runtime DI resolution (330+ handler/listener files) - Offset CI docker-compose ports (5433/6380/8109/9002) to avoid conflicts with running dev containers - Update .env.test, playwright.config.ts, and e2e workflow to use isolated CI ports with configurable overrides - Fix prisma/seed.ts to use deterministic IDs for Prisma 7 upsert compatibility (phoneHash replaced phone as unique index) - Add dedicated Docker bridge network for CI service containers Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
14 KiB
14 KiB
GoodGo Platform - Payment Module Security Checklist
Critical Files for Security Review
🔴 HIGHEST PRIORITY (Review First)
1. Callback Signature Verification
Files:
infrastructure/services/vnpay.service.ts(lines 72-105)infrastructure/services/momo.service.ts(lines 102-147)infrastructure/services/zalopay.service.ts(lines 98-144)
Security Checklist:
- Verify crypto.timingSafeEqual() is used for all HMAC comparisons
- Confirm signature verification keys are correct
- Check that hash algorithms match provider specs (VNPay: SHA512, MoMo/ZaloPay: SHA256)
- Verify signature data reconstruction matches provider documentation exactly
- Test replay attack scenarios - are old callbacks rejected?
- Confirm parameter ordering in signature is correct
- Check for timing attacks - all implementations use constant-time compare
- Verify rawData logging doesn't leak sensitive signature data
2. Race Condition Protection - Payment Callbacks
File:
application/commands/handle-callback/handle-callback.handler.ts(lines 32-110)infrastructure/repositories/prisma-payment.repository.ts(lines 84-109)
Security Checklist:
- Confirm updateIfStatus() uses WHERE clause with status IN array
- Verify Prisma returns null on P2025 error correctly
- Test concurrent callback scenarios for same payment
- Verify idempotent response for already-processed payments
- Confirm events are only emitted once per unique callback
- Check that PROCESSING status is used as intermediate state
- Verify no race condition between null check and event publishing
3. Race Condition Protection - Escrow Operations
Files:
application/commands/hold-escrow/hold-escrow.handler.ts(lines 23-67)application/commands/release-escrow/release-escrow.handler.ts(lines 24-45)
Security Checklist:
- ❌ CRITICAL: No Redis lock present - concurrent requests can cause state corruption
- Check if multiple simultaneous hold operations exist in logs
- Verify no order/escrow can be in two states simultaneously
- Test: what happens if hold and release called concurrently?
- Test: what happens if hold called twice quickly?
- Implement: Redis distributed lock for escrow operations
- Document: expected behavior under concurrent access
4. Financial Amount Validation
Files:
domain/value-objects/money.vo.ts(lines 1-21)domain/value-objects/platform-fee.vo.ts(lines 1-31)
Security Checklist:
- Verify max limit 999_999_999_999 VND is enforced
- Confirm zero/negative amounts are rejected
- Test: can amount be set to negative via SQL injection?
- Verify platform fee calculation: (amount * 5) / 100 = correct
- Check: does fee calculation handle rounding correctly?
- Test: edge case of 1 VND order - correct fee?
- Verify seller payout calculation: amount - fee ≥ 0
- Test: can seller payout ever be negative?
🟠 HIGH PRIORITY
5. Order/Escrow State Machine
Files:
domain/entities/order.entity.ts(lines 22-32 state machine definition)domain/entities/escrow.entity.ts(lines 74-148 state transitions)
Security Checklist:
- Verify VALID_TRANSITIONS whitelist is complete and correct
- Test: can any invalid transition occur?
- Test: PAYMENT_PENDING → ESCROW_HELD without PAYMENT_CONFIRMED?
- Verify DISPUTE state can transition to ESCROW_RELEASED or REFUNDED
- Test: what happens if order tries to transition to invalid state?
- Check: are all state changes persisted atomically?
- Verify: timestamp fields updated correctly for each transition
- Test: out-of-order callbacks don't corrupt state
6. Idempotency Protection
Files:
application/commands/create-order/create-order.handler.ts(lines 32-38)application/commands/create-payment/create-payment.handler.ts(handler not fully shown)infrastructure/repositories/prisma-order.repository.ts(line 18-22)infrastructure/repositories/prisma-payment.repository.ts(lines 24-29)
Security Checklist:
- Verify idempotencyKey is unique per user/request
- Test: duplicate requests with same key return same order
- Test: duplicate requests with same key don't double-charge
- Check: is idempotencyKey stored in database?
- Verify: database unique constraint on idempotencyKey
- Test: what if callback arrives before order created?
- Test: what if payment created but callback lost?
- Check: TTL on idempotency keys to prevent bloat
7. Authorization & Ownership Verification
Files:
presentation/controllers/orders.controller.ts(lines 44-116)presentation/controllers/payments.controller.ts(lines 52-139)
Security Checklist:
- Verify JwtAuthGuard on all user endpoints
- Check: can user view other user's orders/payments?
- Verify: buyer authorization checked in order queries
- Verify: only seller/buyer can access their transactions
- Test: IDOR vulnerabilities - user A accessing user B's order
- Check: admin-only endpoints use RolesGuard
- Test: non-admin user can't call hold/release escrow
- Verify: user.sub (JWT subject) properly extracted
8. Refund Security
Files:
application/commands/refund-payment/refund-payment.handler.ts(not fully shown)infrastructure/services/vnpay.service.ts(lines 107-169)infrastructure/services/momo.service.ts(lines 149-202)infrastructure/services/zalopay.service.ts(lines 146-197)
Security Checklist:
- Only ADMIN role can initiate refunds
- Verify: refund amount ≤ original payment amount
- Check: can refund amount be negative?
- Test: can payment be refunded multiple times?
- Verify: refund status tracking in Payment entity
- Check: refund provider response validation
- Test: partial refunds - are multiple refunds tracked?
- Verify: funds actually sent back to customer (not to app)
9. Rate Limiting on Callbacks
File:
presentation/controllers/payments.controller.ts(lines 75-89)
Security Checklist:
- Confirm: @Throttle decorator with 20 requests per 60s
- Check: @EndpointRateLimit with 100 requests per 60s
- Verify: rate limit key is IP-based
- Test: callback flooding attack mitigated?
- Check: admin bypass disabled for callbacks
- Verify: rate limit storage mechanism (in-memory? Redis?)
- Test: legitimate callback bursts (payment provider retries)
- Check: rate limit errors logged appropriately
🟡 MEDIUM PRIORITY
10. Configuration & Secrets Management
Files:
infrastructure/services/vnpay.service.ts(lines 27-32)infrastructure/services/momo.service.ts(lines 27-31)infrastructure/services/zalopay.service.ts(lines 27-30)
Security Checklist:
- Verify: all secrets loaded from ConfigService (not hardcoded)
- Check: .env file is in .gitignore
- Confirm: secrets aren't logged anywhere
- Verify: hash secrets are properly long (recommend 32+ chars)
- Check: sandbox/production URLs separated by env
- Test: missing config throws error early (not at payment time)
- Verify: secret rotation mechanism exists or planned
- Check: env variables encrypted at rest in CI/CD
11. Database Constraints
Files:
- Check Prisma schema for order/escrow/payment models
Security Checklist:
- Verify: unique constraints on idempotencyKey per user
- Check: NOT NULL constraints on critical fields
- Verify: ONE-TO-ONE relationship order ↔ escrow
- Check: foreign key constraints prevent orphans
- Verify: status fields have CHECK constraints or enums
- Check: amount fields are proper numeric types (not strings)
- Verify: no direct user-provided IDs used without validation
- Check: database indexes on frequently queried fields
12. Error Handling & Information Disclosure
Files:
- All handlers catch errors and log appropriately
Security Checklist:
- Verify: error messages don't leak sensitive data
- Check: stack traces not exposed to clients
- Verify: generic error messages for failed operations
- Check: error codes are documented (E.g., OrderNotFound)
- Test: invalid amount shows appropriate error
- Verify: failed callbacks logged with provider context
- Check: refund failures don't expose retry mechanism
- Verify: no SQL queries exposed in error messages
13. Logging & Audit Trail
Files:
- All handlers use logger.log() and logger.error()
Security Checklist:
- Verify: critical operations logged (payment, refund, escrow changes)
- Check: logs include user context (userId, orderId, etc)
- Verify: logs include timestamp and status transition
- Check: no sensitive data logged (payment secrets, full CC info)
- Verify: log rotation configured
- Check: logs are tamper-proof (signed/hashed)
- Test: audit trail shows complete order/payment lifecycle
- Verify: invalid callbacks logged with details for investigation
🟢 LOWER PRIORITY
14. Test Coverage
Files:
application/__tests__/handle-callback-edge-cases.handler.spec.ts(edge cases)- All
__tests__files
Security Checklist:
- Check: test coverage for callback signature verification
- Verify: tests for race condition scenarios
- Check: tests for idempotency edge cases
- Verify: tests for invalid state transitions
- Check: tests for authorization failures
- Verify: tests for concurrent escrow operations
- Check: tests for amount validation edge cases
- Verify: tests for provider failure scenarios
15. API Documentation
Files:
- Controller decorators (@ApiOperation, @ApiResponse)
Security Checklist:
- Verify: endpoints documented with security requirements
- Check: response schemas don't include secrets
- Verify: rate limits documented
- Check: authorization requirements clearly stated
- Verify: error responses documented
- Check: webhook signature verification explained
- Verify: callback retry behavior documented
- Check: provider-specific behavior differences noted
Attack Scenarios to Test
Scenario 1: Callback Flooding
Attack: Send 1000 callbacks per second
Expected: Rate limiter blocks after 100 per 60s
Expected: Payment status unchanged after first successful callback
Check: No double-charging
Scenario 2: Replay Attack
Attack: Resend old successful callback
Expected: Payment already in terminal state, idempotent response
Expected: No double-charging
Check: Logs show replay attempt
Scenario 3: Concurrent Escrow Release
Attack: Call /orders/{id}/escrow/release twice simultaneously
Expected: One succeeds, one fails with ESCROW_INVALID_STATE
Current Risk: ⚠️ Could succeed twice without Redis lock
Scenario 4: Forged Callback
Attack: Send callback with invalid HMAC signature
Expected: Validation exception, payment rejected
Check: Signature verification uses constant-time compare
Scenario 5: Order/Escrow State Desync
Attack: Order in PAYMENT_CONFIRMED, Escrow in RELEASED
Expected: Invalid state machine - shouldn't be possible
Check: Are order + escrow updates atomic?
Scenario 6: Integer Overflow
Attack: Send payment for 999_999_999_999 VND
Expected: Money VO rejects (max limit)
Attack: Send fee calculation for large amount
Expected: No integer overflow, correct 5% fee calculated
Scenario 7: Authorization Bypass
Attack: Get another user's order ID, call /orders/{theirID}
Expected: 404 or Forbidden (not found to prevent enumeration)
Check: Ownership verified in query handler
Scenario 8: Double Refund
Attack: Call /payments/{id}/refund twice
Expected: Second call fails (payment already REFUNDED)
Check: State machine prevents invalid transition
Security Metrics
| Metric | Status | Target |
|---|---|---|
| All callbacks verify HMAC signature | ✅ YES | 100% |
| Race conditions protected with locks/atomicity | ⚠️ PARTIAL | 100% (escrow ops need locks) |
| Idempotency keys enforced | ✅ YES | 100% |
| Authorization on all endpoints | ✅ YES | 100% |
| Amount validation (min/max) | ✅ YES | 100% |
| Rate limiting on callbacks | ✅ YES | 100% |
| Error messages don't leak secrets | ⚠️ NEEDS REVIEW | 100% |
| Logging captures audit trail | ✅ YES | 100% |
| Test coverage for security | ⚠️ PARTIAL | >80% |
| Database constraints | ⚠️ NEEDS VERIFICATION | 100% |
Recommended Actions
CRITICAL (Do Before Production)
-
Implement Redis distributed lock for escrow hold/release operations
- Use
@nestjs/commonor external lock service - Prevent concurrent state mutations
- Use
-
Add database constraints validation
- Verify Prisma schema has proper constraints
- Add unique index on (userId, idempotencyKey)
-
Audit all error messages
- Ensure no secrets leak in responses
- Test error cases manually
HIGH (Before First Deployment)
-
Add comprehensive test suite
- Race condition tests
- Callback replay tests
- IDOR tests
-
Secrets audit
- Verify no hardcoded values
- Check .env/.gitignore
- Document secret rotation procedure
-
Stress test callbacks
- Simulate provider retry storms
- Verify rate limiting works
MEDIUM (Near-term)
-
Add more detailed audit logging
- Payment status transitions
- Failed callback attempts
- Refund requests/approvals
-
Create incident response playbook
- Double payment detection
- Stuck order recovery
- Provider integration issues
NICE-TO-HAVE
-
Field-level encryption for sensitive data
- Payment callback data
- Provider transaction IDs
-
Webhook signature verification monitoring
- Alert on verification failures
- Track provider replay attempts