ENV & SECURITY AUDIT REPORT
Repo: D:\welife
Date: 2026-07-31
Scope: Environment variables · secrets hygiene · Expo TLS · startup validation
Domain: D-OPS · WP: WP-ENV-SEC-01
Final status: PASS_WITH_OWNER_ACTION
1. Executive summary
| Metric |
Count |
| Biến phát hiện (code + compose + examples) |
~85 |
| Biến hợp lệ / đang dùng đúng scope |
~55 |
| Biến còn thiếu giá trị thật (OWNER_REQUIRED) |
1+ (Mapbox; payments/SMS optional) |
| Biến không dùng trong runtime API |
1 (WS_PUBLIC_URL — chỉ deploy/docs) |
| Biến sai phạm vi |
0 (không có secret trong EXPO_PUBLIC_*) |
| Rủi ro bảo mật (cần chú ý) |
4 |
| Owner decisions required |
3 |
.env / api/.env / mobile/.env* không bị Git track.
- Backup timestamp:
.env-backups/20260731-133124/ (gitignored).
- TLS Node trên Windows: fail mặc định → OK với
NODE_OPTIONS=--use-system-ca (không tắt verify).
- Không có
eas.json → bỏ qua EAS secret provisioning.
- Không rotate JWT/DB production (tránh invalidate session).
2. Environment inventory
| Variable |
Component |
Required |
Scope |
Status |
Action |
COMPOSE_PROJECT_NAME |
root compose |
no |
public |
OK |
giữ |
POSTGRES_USER/PASSWORD/DB/PORT |
root compose |
yes |
secret (pw) |
OK |
giữ; prod không dùng mẫu |
REDIS_PORT/PASSWORD |
root compose |
yes |
secret (pw) |
OK |
giữ |
MINIO_ROOT_USER/PASSWORD + ports + bucket |
root compose |
yes |
secret |
PRODUCTION_RISK |
app dùng S3_*; root = bootstrap |
API_PORT |
root compose |
yes |
public |
OK |
giữ |
MAILPIT_UI_PORT/SMTP_PORT |
root compose |
no |
public |
MISSING→OK |
đã bổ sung vào root .env |
NODE_ENV PORT |
api |
yes |
public |
OK |
|
API_PUBLIC_URL |
api |
yes |
public |
OK |
prod phải https |
WS_PUBLIC_URL |
api |
no |
public |
UNUSED |
giữ cho deploy scripts; không đọc trong api/src |
CORS_ORIGINS |
api |
yes (prod) |
public |
OK local * |
prod cấm * (assertSecureBoot) |
TRUST_PROXY BODY_JSON_LIMIT |
api |
no |
public |
OK |
|
SWAGGER_ENABLED HEALTH_DETAIL_ENABLED HTTP_DEBUG |
api |
no |
public |
OK |
|
JWT_ACCESS_SECRET JWT_REFRESH_SECRET |
api |
yes |
secret |
OK |
khác nhau; ≥32 prod |
JWT_ACCESS_TTL JWT_REFRESH_TTL |
api |
no |
public |
OK |
|
OTP_* OTP_DEV_CODE |
api |
partial |
secret (dev code) |
OK local / INSECURE prod |
prod cấm OTP_DEV_CODE |
SMTP_* |
api |
email channel |
secret (pass) |
OK |
|
SMTP_ALLOW_NOAUTH |
api |
no |
public |
OK |
assertSecureBoot |
ADMIN_EMAILS/PHONES ADMIN_BOOTSTRAP_ENABLED |
api |
conditional |
public |
OK |
|
ALLOW_INTERNAL_WALLET_CREDIT |
api |
no |
public |
PRODUCTION_RISK nếu true |
warn prod |
DATABASE_URL + DB_* |
api |
yes |
secret |
DUPLICATED by design |
DATABASE_URL canonical Prisma; DB_* health |
REDIS_URL + host/port/password |
api |
yes |
secret |
DUPLICATED by design |
URL ưu tiên |
S3_* |
api |
yes |
secret |
OK |
map MinIO |
MAPBOX_ACCESS_TOKEN |
api |
no (fallback) |
secret |
OWNER_REQUIRED |
trống → Haversine |
PAYMENTS_MOCK + MoMo/VNPay |
api |
conditional |
secret |
OWNER_REQUIRED |
mock=true local |
SMS_PROVIDER + eSMS/Twilio/HTTP |
api |
conditional |
secret |
OWNER_REQUIRED |
console local |
OPENAI_* |
api |
no |
secret |
OWNER_REQUIRED |
optional assistant |
EXPO_PUBLIC_API_URL |
mobile |
yes |
public |
OK |
hiện https production |
EXPO_PUBLIC_WS_URL |
mobile |
yes |
public |
OK |
https (Socket.IO → WSS) |
EXPO_PUBLIC_ENV |
mobile |
yes |
public |
OK |
production trên máy dev |
EXPO_PUBLIC_API_DEBUG |
mobile |
no |
public |
OK |
code dùng; optional |
EXPO_PUBLIC_MAPBOX_TOKEN |
mobile |
no |
public |
UNUSED |
không có code FE dùng |
Driver/Partner EXPO_PUBLIC_* |
driver/partner |
yes |
public |
OK |
|
Không thêm (không có code): PASSWORD_PEPPER, DATA_ENCRYPTION_KEY, RATE_LIMIT_*, FCM_*, APNS_*, EXPO_ACCESS_TOKEN, WS_HEARTBEAT_*, NODE_TLS_REJECT_UNAUTHORIZED.
3. Files created
.env-backups/20260731-133124/* (local backup)
mobile/.env.local (public client; mirror .env)
scripts/normalize-env-files.mjs
docs/reports/ENV-SECURITY-AUDIT.md (this report)
api/.env.production.example (đã có từ trước; giữ template)
4. Files modified
.gitignore — .env-backups/, !.env.*.example, *.pfx
.env.example — Mailpit ports + comments
.env — grouped; thêm MAILPIT_* (giữ secret cũ)
api/.env — grouped; giữ secret; MAPBOX_ACCESS_TOKEN trống
api/.env.example — đầy đủ theo code
api/src/common/security/assert-secure-boot.ts — URL/PORT/TLS/weak DB/Mapbox warn
scripts/env-check.mjs — client public check, JWT≠, Mapbox warn
mobile/.env + .env.example + .gitignore
driver/.env / partner/.env — comment chuẩn (giữ URL)
5. Variables added
| Name |
Reason |
File |
Secret shown? |
MAILPIT_UI_PORT |
compose profile mail/full |
root .env |
no |
MAILPIT_SMTP_PORT |
compose |
root .env |
no |
MAPBOX_ACCESS_TOKEN= (empty) |
routing code |
api/.env |
n/a empty |
WS_PUBLIC_URL |
deploy parity |
api/.env |
no |
HTTP_DEBUG |
interceptor |
api/.env |
no |
6. Variables still missing
| Variable |
Required by |
Reason missing |
Owner action |
MAPBOX_ACCESS_TOKEN |
routing.service.ts |
chưa cấp token |
dán token Mapbox vào api/.env (+ VPS) rồi restart API |
| MoMo / VNPay secrets |
payments.service.ts |
PAYMENTS_MOCK=true |
khi go-live: điền + PAYMENTS_MOCK=false |
| eSMS / Twilio |
sms.service.ts |
SMS_PROVIDER=console |
khi OTP SMS prod |
OPENAI_API_KEY |
assistant |
optional |
chỉ khi bật AI |
7. Security findings
| Severity |
Finding |
Location |
Remediation |
| Medium |
Node TLS leaf verify fail (AV/corp CA) |
Windows Node 24 |
NODE_OPTIONS=--use-system-ca (user/profile) — không tắt TLS |
| Medium |
App S3 dùng credential trùng MinIO root pattern |
compose + S3_* |
tạo MinIO service account least-privilege (migration có chủ đích) |
| Low |
SSLKEYLOGFILE set trong user env |
Windows Env |
bỏ nếu không debug TLS (có thể lộ key material) |
| Low |
Mobile EXPO_PUBLIC_ENV=production trên máy dev |
mobile/.env* |
OK nếu test prod API; local device → đổi LAN URL + development |
| Info |
WS_PUBLIC_URL unused in Nest |
api/.env |
giữ cho deploy hoặc xóa sau khi dọn scripts |
| Info |
MinIO root trong root .env |
compose |
chấp nhận local; prod rotate nếu từng lộ |
Rejected unsafe workaround: NODE_TLS_REJECT_UNAUTHORIZED=0 — assertSecureBoot chặn.
8. Secret rotation required
| Credential |
Reason |
Rotation impact |
| Không bắt buộc ngay |
Không thấy .env trong Git history tracked |
— |
| Mapbox (khi cấp) |
chưa có |
không impact session |
| MinIO app key (khuyến nghị) |
least privilege |
cần update S3_* + restart |
| JWT prod |
chỉ nếu từng commit/leak |
mọi session logout — cần phê duyệt |
9. Validation results
| Check |
Result |
npm run env:check |
PASS (Mapbox WARN) |
docker compose config --quiet |
PASS (exit 0; output không dump vào báo cáo) |
npm run api:build |
PASS |
| Expo Doctor |
17/18 — expo-location major mismatch (ngoài scope ENV) |
Expo offline (--offline --clear) |
PASS — Metro Waiting on http://localhost:8081; load .env.local .env |
| Expo online |
PASS với NODE_OPTIONS=--use-system-ca (doctor chạy được) |
TLS fetch(expo.dev) default |
FAIL leaf certificate |
TLS + --use-system-ca |
PASS 200 |
| API connectivity (port 3000) |
free lúc audit (API chưa chạy) |
| WebSocket |
không smoke live; URL scheme mobile = https (Socket.IO OK) |
10. TLS finding
- Root cause: Node 24 không tin CA trung gian (thường antivirus HTTPS inspection / Windows enterprise CA) khi không dùng system trust store.
- Safe fix: User / terminal profile:
[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS','--use-system-ca','User')
Hoặc mỗi session: $env:NODE_OPTIONS='--use-system-ca' trước npx expo … / npm ….
- Unsafe workaround rejected:
NODE_TLS_REJECT_UNAUTHORIZED=0.
- Không đưa
NODE_OPTIONS vào mobile/.env (Expo client bundle / side effects).
11. Remaining blockers
- OWNER:
MAPBOX_ACCESS_TOKEN trống → directions ước lượng.
- OWNER: Cấu hình TLS Windows bền vững (
NODE_OPTIONS=--use-system-ca).
- Optional:
npx expo install expo-location align SDK (không phải ENV).
- Device thật: LAN
192.168.1.109 — nếu không dùng prod API, set:
EXPO_PUBLIC_ENV=development
EXPO_PUBLIC_API_URL=http://192.168.1.109:3000/api/v1
EXPO_PUBLIC_WS_URL=http://192.168.1.109:3000
12. Final status
PASS_WITH_OWNER_ACTION
Lý do không PASS thuần: Mapbox token + TLS user-level config + (khuyến nghị) MinIO least-privilege chưa thực hiện; không tự bịa credential ngoài.
Appendix — Socket.IO vs wss://
Client WELIFE dùng EXPO_PUBLIC_WS_URL dạng https://host (Socket.IO). Transport nâng cấp lên WSS tự động. Không bắt buộc đổi sang scheme wss:// trừ khi đổi client stack.