timsquad 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.ko.md +288 -0
  2. package/README.md +158 -151
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.d.ts.map +1 -0
  5. package/dist/commands/compile.js +170 -0
  6. package/dist/commands/compile.js.map +1 -0
  7. package/dist/commands/daemon.d.ts.map +1 -1
  8. package/dist/commands/daemon.js +95 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/full.js +1 -0
  11. package/dist/commands/full.js.map +1 -1
  12. package/dist/commands/git/pr.js +6 -5
  13. package/dist/commands/git/pr.js.map +1 -1
  14. package/dist/commands/git/release.js +2 -7
  15. package/dist/commands/git/release.js.map +1 -1
  16. package/dist/commands/improve.js +2 -2
  17. package/dist/commands/improve.js.map +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +12 -3
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/log.d.ts.map +1 -1
  22. package/dist/commands/log.js +2 -2
  23. package/dist/commands/log.js.map +1 -1
  24. package/dist/commands/metrics.d.ts.map +1 -1
  25. package/dist/commands/metrics.js +6 -2
  26. package/dist/commands/metrics.js.map +1 -1
  27. package/dist/commands/retro.js +8 -8
  28. package/dist/commands/retro.js.map +1 -1
  29. package/dist/commands/session.js +3 -3
  30. package/dist/commands/session.js.map +1 -1
  31. package/dist/commands/skills.d.ts +12 -0
  32. package/dist/commands/skills.d.ts.map +1 -0
  33. package/dist/commands/skills.js +228 -0
  34. package/dist/commands/skills.js.map +1 -0
  35. package/dist/commands/status.js +1 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/upgrade.d.ts.map +1 -1
  38. package/dist/commands/upgrade.js +23 -1
  39. package/dist/commands/upgrade.js.map +1 -1
  40. package/dist/daemon/entry.js +3 -3
  41. package/dist/daemon/entry.js.map +1 -1
  42. package/dist/daemon/event-queue.d.ts.map +1 -1
  43. package/dist/daemon/event-queue.js +2 -2
  44. package/dist/daemon/event-queue.js.map +1 -1
  45. package/dist/daemon/index.d.ts +4 -2
  46. package/dist/daemon/index.d.ts.map +1 -1
  47. package/dist/daemon/index.js +214 -52
  48. package/dist/daemon/index.js.map +1 -1
  49. package/dist/daemon/jsonl-watcher.d.ts +1 -0
  50. package/dist/daemon/jsonl-watcher.d.ts.map +1 -1
  51. package/dist/daemon/jsonl-watcher.js.map +1 -1
  52. package/dist/daemon/meta-cache.d.ts +1 -0
  53. package/dist/daemon/meta-cache.d.ts.map +1 -1
  54. package/dist/daemon/meta-cache.js +9 -0
  55. package/dist/daemon/meta-cache.js.map +1 -1
  56. package/dist/daemon/session-notes.d.ts +33 -0
  57. package/dist/daemon/session-notes.d.ts.map +1 -0
  58. package/dist/daemon/session-notes.js +74 -0
  59. package/dist/daemon/session-notes.js.map +1 -0
  60. package/dist/daemon/session-state.d.ts +27 -0
  61. package/dist/daemon/session-state.d.ts.map +1 -0
  62. package/dist/daemon/session-state.js +165 -0
  63. package/dist/daemon/session-state.js.map +1 -0
  64. package/dist/daemon/shutdown.d.ts.map +1 -1
  65. package/dist/daemon/shutdown.js +9 -1
  66. package/dist/daemon/shutdown.js.map +1 -1
  67. package/dist/index.js +4 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/lib/agent-generator.d.ts +4 -0
  70. package/dist/lib/agent-generator.d.ts.map +1 -1
  71. package/dist/lib/agent-generator.js +52 -3
  72. package/dist/lib/agent-generator.js.map +1 -1
  73. package/dist/lib/compile-rules.d.ts +66 -0
  74. package/dist/lib/compile-rules.d.ts.map +1 -0
  75. package/dist/lib/compile-rules.js +114 -0
  76. package/dist/lib/compile-rules.js.map +1 -0
  77. package/dist/lib/compiler.d.ts +105 -0
  78. package/dist/lib/compiler.d.ts.map +1 -0
  79. package/dist/lib/compiler.js +368 -0
  80. package/dist/lib/compiler.js.map +1 -0
  81. package/dist/lib/config.d.ts +1 -0
  82. package/dist/lib/config.d.ts.map +1 -1
  83. package/dist/lib/config.js +8 -1
  84. package/dist/lib/config.js.map +1 -1
  85. package/dist/lib/project.d.ts.map +1 -1
  86. package/dist/lib/project.js +8 -3
  87. package/dist/lib/project.js.map +1 -1
  88. package/dist/lib/skill-generator.d.ts.map +1 -1
  89. package/dist/lib/skill-generator.js +22 -1
  90. package/dist/lib/skill-generator.js.map +1 -1
  91. package/dist/lib/template.d.ts.map +1 -1
  92. package/dist/lib/template.js +6 -0
  93. package/dist/lib/template.js.map +1 -1
  94. package/dist/types/config.d.ts +1 -0
  95. package/dist/types/config.d.ts.map +1 -1
  96. package/dist/types/config.js +12 -1
  97. package/dist/types/config.js.map +1 -1
  98. package/dist/types/project.d.ts +1 -1
  99. package/dist/types/project.d.ts.map +1 -1
  100. package/dist/types/project.js +2 -0
  101. package/dist/types/project.js.map +1 -1
  102. package/package.json +4 -4
  103. package/templates/base/agents/base/tsq-architect.md +2 -2
  104. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  105. package/templates/base/knowledge/checklists/plan-quality.md +31 -0
  106. package/templates/base/knowledge/checklists/stability-verification.md +14 -0
  107. package/templates/base/skills/controller/SKILL.md +111 -0
  108. package/templates/base/skills/controller/references/README.md +35 -0
  109. package/templates/base/skills/controller/rules/README.md +18 -0
  110. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  111. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  112. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  113. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  114. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  115. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  116. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  117. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  118. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  119. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  120. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  121. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  122. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  123. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  124. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  125. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  126. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  127. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  128. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  129. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  130. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  131. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  132. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  133. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  134. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  135. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  136. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  137. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  138. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  139. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  140. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  141. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  142. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  143. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  144. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  145. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  146. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  147. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  148. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  149. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  150. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  151. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  152. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  153. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  154. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  155. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  156. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  157. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  158. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  159. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  160. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  161. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  162. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  163. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  164. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  165. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  166. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  167. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  168. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  169. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  170. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  171. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  172. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  173. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  174. package/templates/base/skills/stability-verification/SKILL.md +64 -0
  175. package/templates/base/skills/stability-verification/references/release-checklist.md +34 -0
  176. package/templates/base/skills/stability-verification/references/security-fix-patterns.md +112 -0
  177. package/templates/base/skills/stability-verification/rules/verification-layers.md +67 -0
  178. package/templates/base/skills/stability-verification/rules/verification-workflow.md +69 -0
  179. package/templates/base/skills/stability-verification/scripts/verify.sh +294 -0
  180. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  181. package/templates/platforms/claude-code/rules/build-gate.md +28 -0
  182. package/templates/platforms/claude-code/rules/completion-verification.md +30 -0
  183. package/templates/platforms/claude-code/rules/context-monitor.md +23 -0
  184. package/templates/platforms/claude-code/rules/plan-review.md +45 -0
  185. package/templates/platforms/claude-code/rules/quality-guards.md +43 -0
  186. package/templates/platforms/claude-code/rules/session-notes.md +18 -0
  187. package/templates/platforms/claude-code/rules/skill-suggest.md +27 -0
  188. package/templates/platforms/claude-code/scripts/build-gate.sh +73 -0
  189. package/templates/platforms/claude-code/scripts/completion-guard.sh +93 -0
  190. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  191. package/templates/platforms/claude-code/scripts/safe-guard.sh +83 -0
  192. package/templates/platforms/claude-code/scripts/skill-rules.json +85 -0
  193. package/templates/platforms/claude-code/scripts/skill-suggest.sh +105 -0
  194. package/templates/platforms/claude-code/settings.json +111 -3
  195. package/templates/project-types/mobile-app/config.yaml +123 -0
  196. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,168 @@
1
+ ---
2
+ title: Mobile Security Checklist (OWASP MASVS)
3
+ category: reference
4
+ source: owasp
5
+ tags: owasp, masvs, checklist, mobsf, frida, penetration-testing
6
+ ---
7
+
8
+ # Mobile Security Checklist (OWASP MASVS)
9
+
10
+ OWASP Mobile Application Security Verification Standard (MASVS) L1/L2 기준 체크리스트.
11
+ Flutter 앱 보안 검증을 위한 참조 문서.
12
+
13
+ ## Key Concepts
14
+
15
+ - **MASVS L1**: 표준 보안 (모든 모바일 앱 필수)
16
+ - **MASVS L2**: 심층 방어 (금융, 의료, 개인정보 처리 앱)
17
+ - **MASTG**: Mobile Application Security Testing Guide (테스트 방법론)
18
+
19
+ ## MASVS L1 체크리스트 (필수)
20
+
21
+ ### MASVS-STORAGE: 데이터 저장
22
+
23
+ | # | 항목 | Flutter 구현 |
24
+ |---|------|-------------|
25
+ | S-1 | 민감 데이터를 안전한 저장소에 보관 | `flutter_secure_storage` (Keychain/EncryptedSharedPreferences) |
26
+ | S-2 | 로그에 민감 데이터 미출력 | Release 빌드에서 `debugPrint` 제거, `kDebugMode` 체크 |
27
+ | S-3 | 백업에서 민감 데이터 제외 | Android `backup_rules.xml`, iOS `isExcludedFromBackup` |
28
+ | S-4 | 클립보드에 민감 데이터 자동 클리어 | `Clipboard.setData` 후 타이머로 클리어 |
29
+ | S-5 | 키보드 캐시에 민감 데이터 미저장 | `enableSuggestions: false`, `autocorrect: false` |
30
+ | S-6 | 서드파티 라이브러리에 민감 데이터 미전달 | Analytics에 PII 미포함 확인 |
31
+
32
+ ### MASVS-CRYPTO: 암호화
33
+
34
+ | # | 항목 | Flutter 구현 |
35
+ |---|------|-------------|
36
+ | C-1 | 검증된 암호화 알고리즘 사용 | AES-256 (`encrypt` 패키지), SHA-256 |
37
+ | C-2 | 커스텀 암호화 구현 금지 | 표준 라이브러리만 사용 (자체 구현 X) |
38
+ | C-3 | 하드코딩된 암호화 키 없음 | 키는 `flutter_secure_storage` 또는 빌드 주입 |
39
+ | C-4 | 적절한 키 길이 사용 | AES: 256비트, RSA: 2048비트+ |
40
+
41
+ ### MASVS-AUTH: 인증
42
+
43
+ | # | 항목 | Flutter 구현 |
44
+ |---|------|-------------|
45
+ | A-1 | 서버 측 인증 구현 | 클라이언트 인증은 UX, 실제 검증은 서버 |
46
+ | A-2 | 세션 토큰 안전 저장 | `flutter_secure_storage` |
47
+ | A-3 | 세션 만료 처리 | Dio interceptor 401 자동 갱신 |
48
+ | A-4 | 생체 인증 적용 (해당 시) | `local_auth`, PIN 폴백 |
49
+
50
+ ### MASVS-NETWORK: 네트워크
51
+
52
+ | # | 항목 | Flutter 구현 |
53
+ |---|------|-------------|
54
+ | N-1 | TLS 1.2+ 사용 | Dart HttpClient 기본 지원 |
55
+ | N-2 | 인증서 검증 활성화 | `badCertificateCallback` 기본 거부 |
56
+ | N-3 | 인증서/공개키 pinning | Dio `IOHttpClientAdapter` + `SecurityContext` |
57
+ | N-4 | cleartext 트래픽 차단 | Android `network_security_config.xml`, iOS ATS |
58
+
59
+ ### MASVS-RESILIENCE: 복원력
60
+
61
+ | # | 항목 | Flutter 구현 |
62
+ |---|------|-------------|
63
+ | R-1 | 코드 난독화 | `--obfuscate --split-debug-info` |
64
+ | R-2 | 디버그 감지 | `kDebugMode`, `kReleaseMode` 분기 |
65
+ | R-3 | 루팅/탈옥 감지 | `flutter_jailbreak_detection` 패키지 |
66
+ | R-4 | 무결성 검증 | 앱 서명 검증, 변조 감지 |
67
+
68
+ ## MASVS L2 추가 항목 (금융/의료)
69
+
70
+ | # | 항목 | Flutter 구현 |
71
+ |---|------|-------------|
72
+ | L2-1 | 스크린샷 방지 | Android `FLAG_SECURE`, iOS 오버레이 |
73
+ | L2-2 | 앱 스위처 블러 | `WidgetsBindingObserver` + 오버레이 |
74
+ | L2-3 | 고급 루팅 감지 | Magisk/Frida 감지 로직 |
75
+ | L2-4 | 런타임 무결성 검증 | 코드 주입 감지, 후킹 방지 |
76
+ | L2-5 | SSL pinning 우회 감지 | 핀 실패 시 서버 리포트 |
77
+ | L2-6 | 화이트박스 암호화 | 키 보호 강화 (HSM 연동) |
78
+
79
+ ## 보안 테스트 도구
80
+
81
+ ### MobSF (Mobile Security Framework)
82
+
83
+ ```bash
84
+ # 설치 및 실행
85
+ docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf
86
+
87
+ # 사용법:
88
+ # 1. http://localhost:8000 접속
89
+ # 2. APK/IPA 파일 업로드
90
+ # 3. 자동 정적 분석 (하드코딩 키, 취약 API, 권한 등)
91
+ # 4. 보고서 확인 및 취약점 수정
92
+
93
+ # 분석 항목:
94
+ # - 하드코딩된 시크릿/API 키
95
+ # - 안전하지 않은 권한
96
+ # - 취약한 암호화 사용
97
+ # - 디버그 모드 활성화
98
+ # - 백업 허용 여부
99
+ # - cleartext 트래픽 허용
100
+ ```
101
+
102
+ ### Frida (동적 분석)
103
+
104
+ ```bash
105
+ # Frida 설치
106
+ pip install frida-tools
107
+
108
+ # 앱 프로세스 확인
109
+ frida-ps -U
110
+
111
+ # SSL pinning 우회 테스트 (자체 앱 검증용)
112
+ frida -U -f com.example.app -l ssl_bypass.js
113
+
114
+ # 주요 테스트:
115
+ # - SSL pinning 우회 가능 여부
116
+ # - 메모리 내 토큰 노출 여부
117
+ # - 루팅 감지 우회 가능 여부
118
+ # - 암호화 키 메모리 노출 여부
119
+ ```
120
+
121
+ ### 기타 도구
122
+
123
+ ```
124
+ 정적 분석:
125
+ - MobSF: APK/IPA 자동 정적 분석
126
+ - semgrep: 소스 코드 패턴 매칭 (시크릿 하드코딩 등)
127
+ - gitleaks: git history에서 시크릿 검색
128
+
129
+ 동적 분석:
130
+ - Frida: 런타임 후킹/분석
131
+ - Objection: Frida 기반 모바일 분석 도구
132
+ - Charles Proxy: 네트워크 트래픽 분석
133
+
134
+ Android 전용:
135
+ - apktool: APK 디컴파일/리패키징
136
+ - jadx: DEX → Java 소스 변환
137
+ - drozer: Android 보안 감사
138
+
139
+ iOS 전용:
140
+ - Hopper: 바이너리 역공학
141
+ - class-dump: Objective-C 헤더 덤프
142
+ ```
143
+
144
+ ## 보안 리뷰 프로세스
145
+
146
+ ```
147
+ 릴리즈 전 보안 체크:
148
+ 1. MobSF 정적 분석 → 하드코딩 키, 취약 설정 확인
149
+ 2. MASVS L1 체크리스트 통과
150
+ 3. SSL pinning 동작 확인 (Charles Proxy 차단 확인)
151
+ 4. 난독화 확인 (jadx/apktool로 디컴파일 시 심볼 난독화)
152
+ 5. flutter_secure_storage 동작 확인 (루팅 기기 테스트)
153
+ 6. Crashlytics 디버그 심볼 업로드 확인
154
+ 7. (L2) Frida 동적 분석 → 런타임 보호 검증
155
+
156
+ 주기적 보안 감사:
157
+ - 분기별: 의존성 취약점 스캔 (flutter pub outdated)
158
+ - 반기별: 외부 보안 감사 (해당 시)
159
+ - 상시: gitleaks pre-commit 훅 (시크릿 커밋 방지)
160
+ ```
161
+
162
+ ## References
163
+
164
+ - [OWASP MASVS](https://mas.owasp.org/MASVS/)
165
+ - [OWASP MASTG](https://mas.owasp.org/MASTG/)
166
+ - [MobSF Documentation](https://mobsf.github.io/docs/)
167
+ - [Frida Documentation](https://frida.re/docs/)
168
+ - [Flutter Security Best Practices](https://docs.flutter.dev/security)
@@ -0,0 +1,206 @@
1
+ ---
2
+ title: API Key Protection
3
+ impact: HIGH
4
+ impactDescription: "키 하드코딩 → git history 영구 노출, 앱 디컴파일 시 즉시 탈취"
5
+ tags: dart-define, envied, environment, api-key, secret, build-injection
6
+ ---
7
+
8
+ ## API Key Protection
9
+
10
+ **Impact: HIGH (키 하드코딩 → git history 영구 노출, 앱 디컴파일 시 즉시 탈취)**
11
+
12
+ API 키, 시크릿을 소스 코드에서 분리. 빌드 시 주입하고 런타임에 안전하게 참조.
13
+
14
+ ### 방법 1: --dart-define (빌드 인자)
15
+
16
+ **Incorrect (소스 코드에 키 하드코딩):**
17
+ ```dart
18
+ class ApiConfig {
19
+ // 소스 코드에 직접 → git push 시 영구 노출
20
+ // 앱 디컴파일 시 문자열 검색으로 즉시 발견
21
+ static const apiKey = 'sk-1234567890abcdef';
22
+ static const googleMapsKey = 'AIzaSyB_XXXXXXXXXXXXXXXXXXXXXXX';
23
+ }
24
+ ```
25
+
26
+ **Correct (--dart-define로 빌드 시 주입):**
27
+ ```dart
28
+ class ApiConfig {
29
+ // 컴파일 타임 상수 — 빌드 시 --dart-define으로 주입
30
+ static const apiKey = String.fromEnvironment('API_KEY');
31
+ static const googleMapsKey = String.fromEnvironment('GOOGLE_MAPS_KEY');
32
+ static const sentryDsn = String.fromEnvironment('SENTRY_DSN');
33
+
34
+ /// 필수 키 검증 (앱 시작 시)
35
+ static void validate() {
36
+ assert(apiKey.isNotEmpty, 'API_KEY not provided via --dart-define');
37
+ assert(googleMapsKey.isNotEmpty, 'GOOGLE_MAPS_KEY not provided');
38
+ }
39
+ }
40
+ ```
41
+
42
+ ```bash
43
+ # 빌드 명령어
44
+ flutter run \
45
+ --dart-define=API_KEY=sk-1234567890abcdef \
46
+ --dart-define=GOOGLE_MAPS_KEY=AIzaSyB_XXX \
47
+ --dart-define=SENTRY_DSN=https://xxx@sentry.io/123
48
+
49
+ # 릴리즈 빌드
50
+ flutter build apk \
51
+ --dart-define=API_KEY=$API_KEY \
52
+ --dart-define=GOOGLE_MAPS_KEY=$GOOGLE_MAPS_KEY \
53
+ --release
54
+
55
+ # --dart-define-from-file (Flutter 3.7+)
56
+ flutter run --dart-define-from-file=config/dev.env
57
+ ```
58
+
59
+ ```properties
60
+ # config/dev.env (gitignore 대상)
61
+ API_KEY=sk-dev-1234567890
62
+ GOOGLE_MAPS_KEY=AIzaSyB_dev_XXX
63
+ SENTRY_DSN=https://dev@sentry.io/123
64
+ ```
65
+
66
+ ### 방법 2: envied 패키지 (코드 생성 + 난독화)
67
+
68
+ ```yaml
69
+ # pubspec.yaml
70
+ dependencies:
71
+ envied: ^0.5.4
72
+ dev_dependencies:
73
+ envied_generator: ^0.5.4
74
+ build_runner: ^2.4.0
75
+ ```
76
+
77
+ ```dart
78
+ // lib/config/env.dart
79
+ import 'package:envied/envied.dart';
80
+
81
+ part 'env.g.dart';
82
+
83
+ @Envied(path: '.env', obfuscate: true) // obfuscate: 문자열 난독화
84
+ abstract class Env {
85
+ @EnviedField(varName: 'API_KEY')
86
+ static const String apiKey = _Env.apiKey;
87
+
88
+ @EnviedField(varName: 'GOOGLE_MAPS_KEY')
89
+ static const String googleMapsKey = _Env.googleMapsKey;
90
+
91
+ @EnviedField(varName: 'SENTRY_DSN', defaultValue: '')
92
+ static const String sentryDsn = _Env.sentryDsn;
93
+ }
94
+
95
+ // .env (프로젝트 루트, .gitignore 필수)
96
+ // API_KEY=sk-1234567890abcdef
97
+ // GOOGLE_MAPS_KEY=AIzaSyB_XXXXXXX
98
+ ```
99
+
100
+ ```bash
101
+ # 코드 생성
102
+ dart run build_runner build --delete-conflicting-outputs
103
+
104
+ # env.g.dart 생성됨 (obfuscate: true → XOR 난독화된 바이트 배열)
105
+ # → 단순 문자열 검색으로 키 추출 불가
106
+ ```
107
+
108
+ ### 환경별 설정 분리
109
+
110
+ ```dart
111
+ // lib/config/app_config.dart
112
+ enum AppEnvironment { dev, staging, prod }
113
+
114
+ class AppConfig {
115
+ final String apiBaseUrl;
116
+ final String apiKey;
117
+ final bool enableLogging;
118
+
119
+ const AppConfig._({
120
+ required this.apiBaseUrl,
121
+ required this.apiKey,
122
+ required this.enableLogging,
123
+ });
124
+
125
+ factory AppConfig.fromEnvironment() {
126
+ const env = String.fromEnvironment('APP_ENV', defaultValue: 'dev');
127
+
128
+ switch (env) {
129
+ case 'prod':
130
+ return const AppConfig._(
131
+ apiBaseUrl: 'https://api.example.com',
132
+ apiKey: String.fromEnvironment('API_KEY'),
133
+ enableLogging: false,
134
+ );
135
+ case 'staging':
136
+ return const AppConfig._(
137
+ apiBaseUrl: 'https://staging-api.example.com',
138
+ apiKey: String.fromEnvironment('API_KEY'),
139
+ enableLogging: true,
140
+ );
141
+ default: // dev
142
+ return const AppConfig._(
143
+ apiBaseUrl: 'https://dev-api.example.com',
144
+ apiKey: String.fromEnvironment('API_KEY', defaultValue: 'dev-key'),
145
+ enableLogging: true,
146
+ );
147
+ }
148
+ }
149
+ }
150
+
151
+ // Riverpod Provider
152
+ final appConfigProvider = Provider<AppConfig>((ref) {
153
+ return AppConfig.fromEnvironment();
154
+ });
155
+ ```
156
+
157
+ ### .gitignore 설정
158
+
159
+ ```gitignore
160
+ # 환경 파일
161
+ .env
162
+ .env.*
163
+ config/dev.env
164
+ config/staging.env
165
+ config/prod.env
166
+
167
+ # envied 생성 파일 (선택: 커밋 vs 생성)
168
+ # lib/config/env.g.dart
169
+
170
+ # Firebase 설정 파일 (키 포함)
171
+ google-services.json
172
+ GoogleService-Info.plist
173
+ firebase_options.dart
174
+ ```
175
+
176
+ ### CI/CD 연동
177
+
178
+ ```yaml
179
+ # GitHub Actions 예시
180
+ # 시크릿은 GitHub Secrets에 저장
181
+ - name: Build APK
182
+ run: |
183
+ flutter build apk --release \
184
+ --dart-define=API_KEY=${{ secrets.API_KEY }} \
185
+ --dart-define=GOOGLE_MAPS_KEY=${{ secrets.GOOGLE_MAPS_KEY }}
186
+
187
+ # 또는 .env 파일 생성 (envied 사용 시)
188
+ - name: Create .env
189
+ run: |
190
+ echo "API_KEY=${{ secrets.API_KEY }}" >> .env
191
+ echo "GOOGLE_MAPS_KEY=${{ secrets.GOOGLE_MAPS_KEY }}" >> .env
192
+ - name: Generate env
193
+ run: dart run build_runner build
194
+ ```
195
+
196
+ ### 규칙
197
+
198
+ - API 키/시크릿 → 소스 코드에 절대 하드코딩 금지
199
+ - `--dart-define` 또는 `--dart-define-from-file` → 빌드 시 주입
200
+ - `String.fromEnvironment()` → 컴파일 타임 상수로 참조
201
+ - `envied` + `obfuscate: true` → 디컴파일 시 문자열 검색 방지
202
+ - `.env` 파일 → `.gitignore` 필수
203
+ - `google-services.json` / `GoogleService-Info.plist` → `.gitignore` 권장
204
+ - 환경별 (dev/staging/prod) → 설정 분리, 프로덕션 키는 CI/CD에서만 주입
205
+ - `assert()` → 앱 시작 시 필수 키 존재 검증 (디버그 빌드에서 조기 발견)
206
+ - 키 노출 사고 시 → 즉시 키 로테이션 + git history에서 제거 (`git filter-branch`)
@@ -0,0 +1,248 @@
1
+ ---
2
+ title: Authentication & Token Management
3
+ impact: CRITICAL
4
+ impactDescription: "토큰 미갱신 → 강제 로그아웃, 토큰 평문 저장 → 계정 탈취"
5
+ tags: token, refresh, biometric, local_auth, dio-interceptor, session
6
+ ---
7
+
8
+ ## Authentication & Token Management
9
+
10
+ **Impact: CRITICAL (토큰 미갱신 → 강제 로그아웃, 토큰 평문 저장 → 계정 탈취)**
11
+
12
+ Access/Refresh Token 라이프사이클, Dio interceptor 자동 갱신, biometric 인증, 세션 타임아웃.
13
+
14
+ ### 의존성
15
+
16
+ ```yaml
17
+ # pubspec.yaml
18
+ dependencies:
19
+ dio: ^5.7.0
20
+ local_auth: ^2.3.0
21
+ flutter_secure_storage: ^9.2.0
22
+ ```
23
+
24
+ ### 토큰 라이프사이클
25
+
26
+ **Incorrect (Access Token만 사용, 갱신 없음):**
27
+ ```dart
28
+ // Access Token 하나만 저장 → 만료 시 강제 로그아웃
29
+ final response = await dio.get('/api/data',
30
+ options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
31
+ );
32
+ // 401 응답 → 사용자에게 "다시 로그인하세요" → UX 파괴
33
+ ```
34
+
35
+ **Correct (Access + Refresh Token 분리, 자동 갱신):**
36
+ ```dart
37
+ class AuthInterceptor extends Interceptor {
38
+ final SecureStorageService _storage;
39
+ final Dio _tokenDio; // 토큰 갱신 전용 Dio (인터셉터 없음)
40
+ bool _isRefreshing = false;
41
+ final _pendingRequests = <({RequestOptions options, ErrorInterceptorHandler handler})>[];
42
+
43
+ AuthInterceptor({
44
+ required SecureStorageService storage,
45
+ required Dio tokenDio,
46
+ }) : _storage = storage,
47
+ _tokenDio = tokenDio;
48
+
49
+ @override
50
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
51
+ final token = await _storage.getAccessToken();
52
+ if (token != null) {
53
+ options.headers['Authorization'] = 'Bearer $token';
54
+ }
55
+ handler.next(options);
56
+ }
57
+
58
+ @override
59
+ void onError(DioException err, ErrorInterceptorHandler handler) async {
60
+ if (err.response?.statusCode != 401) {
61
+ return handler.next(err);
62
+ }
63
+
64
+ // 이미 갱신 중이면 대기열에 추가
65
+ if (_isRefreshing) {
66
+ _pendingRequests.add((options: err.requestOptions, handler: handler));
67
+ return;
68
+ }
69
+
70
+ _isRefreshing = true;
71
+
72
+ try {
73
+ final refreshToken = await _storage.getRefreshToken();
74
+ if (refreshToken == null) {
75
+ _forceLogout();
76
+ return handler.next(err);
77
+ }
78
+
79
+ // 토큰 갱신 요청
80
+ final response = await _tokenDio.post('/auth/refresh', data: {
81
+ 'refresh_token': refreshToken,
82
+ });
83
+
84
+ final newAccess = response.data['access_token'] as String;
85
+ final newRefresh = response.data['refresh_token'] as String;
86
+ await _storage.saveTokens(
87
+ accessToken: newAccess,
88
+ refreshToken: newRefresh,
89
+ );
90
+
91
+ // 원래 요청 재시도
92
+ err.requestOptions.headers['Authorization'] = 'Bearer $newAccess';
93
+ final retryResponse = await _tokenDio.fetch(err.requestOptions);
94
+ handler.resolve(retryResponse);
95
+
96
+ // 대기 중인 요청 재시도
97
+ for (final pending in _pendingRequests) {
98
+ pending.options.headers['Authorization'] = 'Bearer $newAccess';
99
+ _tokenDio.fetch(pending.options).then(
100
+ (r) => pending.handler.resolve(r),
101
+ onError: (e) => pending.handler.reject(e as DioException),
102
+ );
103
+ }
104
+ } on DioException {
105
+ // Refresh Token도 만료 → 강제 로그아웃
106
+ await _storage.deleteTokens();
107
+ _forceLogout();
108
+ handler.next(err);
109
+ } finally {
110
+ _isRefreshing = false;
111
+ _pendingRequests.clear();
112
+ }
113
+ }
114
+
115
+ void _forceLogout() {
116
+ // 로그아웃 이벤트 발행 (GoRouter redirect에서 감지)
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Biometric 인증 (local_auth)
122
+
123
+ **Incorrect (biometric 실패 시 폴백 없음):**
124
+ ```dart
125
+ final isAuthenticated = await LocalAuthentication().authenticate(
126
+ localizedReason: 'Verify identity',
127
+ );
128
+ if (!isAuthenticated) {
129
+ // 실패 → 아무 처리 없음 → 사용자 막힘
130
+ }
131
+ ```
132
+
133
+ **Correct (biometric + PIN 폴백):**
134
+ ```dart
135
+ class BiometricService {
136
+ final LocalAuthentication _localAuth = LocalAuthentication();
137
+
138
+ /// 생체 인증 가능 여부 확인
139
+ Future<bool> isAvailable() async {
140
+ final canCheck = await _localAuth.canCheckBiometrics;
141
+ final isDeviceSupported = await _localAuth.isDeviceSupported();
142
+ return canCheck && isDeviceSupported;
143
+ }
144
+
145
+ /// 사용 가능한 인증 방식 목록
146
+ Future<List<BiometricType>> getAvailableBiometrics() async {
147
+ return _localAuth.getAvailableBiometrics();
148
+ }
149
+
150
+ /// 생체 인증 실행
151
+ Future<bool> authenticate({String reason = '본인 확인이 필요합니다'}) async {
152
+ try {
153
+ return await _localAuth.authenticate(
154
+ localizedReason: reason,
155
+ options: const AuthenticationOptions(
156
+ stickyAuth: true, // 앱 전환 후에도 인증 유지
157
+ biometricOnly: false, // 기기 PIN/패턴 폴백 허용
158
+ useErrorDialogs: true, // 시스템 에러 다이얼로그 표시
159
+ ),
160
+ );
161
+ } on PlatformException catch (e) {
162
+ // NotAvailable, NotEnrolled, LockedOut, PermanentlyLockedOut
163
+ debugPrint('Biometric error: ${e.code}');
164
+ return false;
165
+ }
166
+ }
167
+ }
168
+
169
+ // Riverpod Provider
170
+ final biometricServiceProvider = Provider<BiometricService>((ref) {
171
+ return BiometricService();
172
+ });
173
+
174
+ final biometricAvailableProvider = FutureProvider<bool>((ref) async {
175
+ return ref.watch(biometricServiceProvider).isAvailable();
176
+ });
177
+ ```
178
+
179
+ ### 세션 타임아웃
180
+
181
+ ```dart
182
+ class SessionManager {
183
+ static const _inactivityTimeout = Duration(minutes: 5);
184
+ Timer? _timer;
185
+ final SecureStorageService _storage;
186
+
187
+ SessionManager({required SecureStorageService storage})
188
+ : _storage = storage;
189
+
190
+ /// 사용자 활동 시마다 호출 (탭, 스크롤 등)
191
+ void resetTimer() {
192
+ _timer?.cancel();
193
+ _timer = Timer(_inactivityTimeout, _onTimeout);
194
+ }
195
+
196
+ void _onTimeout() {
197
+ // 민감 화면(잔액, 거래내역) → 재인증 요구
198
+ // 일반 화면 → 경고만 표시
199
+ }
200
+
201
+ /// AppLifecycleState.paused → 백그라운드 진입 시간 기록
202
+ Future<void> onBackground() async {
203
+ await _storage.saveBackgroundTime(DateTime.now());
204
+ }
205
+
206
+ /// AppLifecycleState.resumed → 경과 시간 확인
207
+ Future<bool> onForeground() async {
208
+ final bgTime = await _storage.getBackgroundTime();
209
+ if (bgTime == null) return true;
210
+
211
+ final elapsed = DateTime.now().difference(bgTime);
212
+ if (elapsed > _inactivityTimeout) {
213
+ return false; // 재인증 필요
214
+ }
215
+ return true;
216
+ }
217
+
218
+ void dispose() {
219
+ _timer?.cancel();
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### iOS/Android 설정
225
+
226
+ ```xml
227
+ <!-- iOS: Info.plist (Face ID 사용 이유 설명) -->
228
+ <key>NSFaceIDUsageDescription</key>
229
+ <string>앱 잠금 해제를 위해 Face ID를 사용합니다.</string>
230
+ ```
231
+
232
+ ```xml
233
+ <!-- Android: MainActivity.kt 변경 불필요 -->
234
+ <!-- AndroidManifest.xml: 권한 자동 추가됨 (local_auth) -->
235
+ <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
236
+ ```
237
+
238
+ ### 규칙
239
+
240
+ - Access Token (단수명 15-30분) + Refresh Token (장수명 7-30일) 분리
241
+ - 401 응답 → Dio interceptor에서 자동 갱신 → 원래 요청 재시도
242
+ - 동시 요청 중 401 → 갱신 1회만 실행, 나머지는 대기열 → 갱신 후 일괄 재시도
243
+ - Refresh Token 만료 → `deleteTokens()` + 강제 로그아웃
244
+ - Biometric → `biometricOnly: false` (기기 PIN/패턴 폴백 필수)
245
+ - `stickyAuth: true` → 앱 전환 후 돌아와도 인증 세션 유지
246
+ - 세션 타임아웃 → 비활성 5분 후 민감 화면 재인증 요구
247
+ - 백그라운드 → 진입 시간 기록, 복귀 시 경과 확인
248
+ - iOS → `NSFaceIDUsageDescription` plist 필수 (없으면 크래시)