- 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>
397 lines
14 KiB
Markdown
397 lines
14 KiB
Markdown
# 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)
|
|
1. [ ] **Implement Redis distributed lock for escrow hold/release operations**
|
|
- Use `@nestjs/common` or external lock service
|
|
- Prevent concurrent state mutations
|
|
|
|
2. [ ] **Add database constraints validation**
|
|
- Verify Prisma schema has proper constraints
|
|
- Add unique index on (userId, idempotencyKey)
|
|
|
|
3. [ ] **Audit all error messages**
|
|
- Ensure no secrets leak in responses
|
|
- Test error cases manually
|
|
|
|
### HIGH (Before First Deployment)
|
|
4. [ ] **Add comprehensive test suite**
|
|
- Race condition tests
|
|
- Callback replay tests
|
|
- IDOR tests
|
|
|
|
5. [ ] **Secrets audit**
|
|
- Verify no hardcoded values
|
|
- Check .env/.gitignore
|
|
- Document secret rotation procedure
|
|
|
|
6. [ ] **Stress test callbacks**
|
|
- Simulate provider retry storms
|
|
- Verify rate limiting works
|
|
|
|
### MEDIUM (Near-term)
|
|
7. [ ] **Add more detailed audit logging**
|
|
- Payment status transitions
|
|
- Failed callback attempts
|
|
- Refund requests/approvals
|
|
|
|
8. [ ] **Create incident response playbook**
|
|
- Double payment detection
|
|
- Stuck order recovery
|
|
- Provider integration issues
|
|
|
|
### NICE-TO-HAVE
|
|
9. [ ] **Field-level encryption for sensitive data**
|
|
- Payment callback data
|
|
- Provider transaction IDs
|
|
|
|
10. [ ] **Webhook signature verification monitoring**
|
|
- Alert on verification failures
|
|
- Track provider replay attempts
|
|
|