timsquad 3.3.0 → 3.4.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 (135) 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 +94 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +12 -3
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/skills.d.ts +12 -0
  14. package/dist/commands/skills.d.ts.map +1 -0
  15. package/dist/commands/skills.js +231 -0
  16. package/dist/commands/skills.js.map +1 -0
  17. package/dist/commands/upgrade.js +5 -0
  18. package/dist/commands/upgrade.js.map +1 -1
  19. package/dist/daemon/entry.js +3 -3
  20. package/dist/daemon/entry.js.map +1 -1
  21. package/dist/daemon/index.d.ts +3 -2
  22. package/dist/daemon/index.d.ts.map +1 -1
  23. package/dist/daemon/index.js +137 -45
  24. package/dist/daemon/index.js.map +1 -1
  25. package/dist/daemon/meta-cache.d.ts +1 -0
  26. package/dist/daemon/meta-cache.d.ts.map +1 -1
  27. package/dist/daemon/meta-cache.js +9 -0
  28. package/dist/daemon/meta-cache.js.map +1 -1
  29. package/dist/daemon/session-state.d.ts +19 -0
  30. package/dist/daemon/session-state.d.ts.map +1 -0
  31. package/dist/daemon/session-state.js +132 -0
  32. package/dist/daemon/session-state.js.map +1 -0
  33. package/dist/daemon/shutdown.d.ts.map +1 -1
  34. package/dist/daemon/shutdown.js +7 -1
  35. package/dist/daemon/shutdown.js.map +1 -1
  36. package/dist/index.js +4 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/compile-rules.d.ts +66 -0
  39. package/dist/lib/compile-rules.d.ts.map +1 -0
  40. package/dist/lib/compile-rules.js +114 -0
  41. package/dist/lib/compile-rules.js.map +1 -0
  42. package/dist/lib/compiler.d.ts +105 -0
  43. package/dist/lib/compiler.d.ts.map +1 -0
  44. package/dist/lib/compiler.js +368 -0
  45. package/dist/lib/compiler.js.map +1 -0
  46. package/dist/lib/config.d.ts +1 -0
  47. package/dist/lib/config.d.ts.map +1 -1
  48. package/dist/lib/config.js +8 -1
  49. package/dist/lib/config.js.map +1 -1
  50. package/dist/lib/template.d.ts.map +1 -1
  51. package/dist/lib/template.js +6 -0
  52. package/dist/lib/template.js.map +1 -1
  53. package/dist/types/config.d.ts +1 -0
  54. package/dist/types/config.d.ts.map +1 -1
  55. package/dist/types/config.js +12 -1
  56. package/dist/types/config.js.map +1 -1
  57. package/dist/types/project.d.ts +1 -1
  58. package/dist/types/project.d.ts.map +1 -1
  59. package/dist/types/project.js +2 -0
  60. package/dist/types/project.js.map +1 -1
  61. package/package.json +1 -1
  62. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  63. package/templates/base/skills/controller/SKILL.md +111 -0
  64. package/templates/base/skills/controller/references/README.md +35 -0
  65. package/templates/base/skills/controller/rules/README.md +18 -0
  66. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  67. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  68. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  69. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  70. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  71. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  72. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  73. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  74. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  75. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  76. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  77. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  78. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  79. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  80. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  81. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  82. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  83. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  84. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  85. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  86. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  87. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  88. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  89. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  90. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  91. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  92. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  93. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  94. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  95. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  96. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  97. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  98. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  99. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  100. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  101. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  102. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  103. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  104. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  105. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  106. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  107. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  108. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  109. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  110. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  111. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  112. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  113. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  114. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  115. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  116. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  117. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  118. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  119. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  120. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  121. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  122. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  123. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  124. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  125. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  126. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  127. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  128. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  129. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  130. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  131. package/templates/platforms/claude-code/scripts/completion-guard.sh +57 -0
  132. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  133. package/templates/platforms/claude-code/settings.json +75 -3
  134. package/templates/project-types/mobile-app/config.yaml +123 -0
  135. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,271 @@
1
+ ---
2
+ title: Data Protection
3
+ impact: MEDIUM
4
+ impactDescription: "미적용 → 로컬 DB 평문 노출, 스크린샷 유출, 클립보드 탈취"
5
+ tags: encryption, sqlcipher, screenshot, clipboard, flag-secure, screen-blur
6
+ ---
7
+
8
+ ## Data Protection
9
+
10
+ **Impact: MEDIUM (미적용 → 로컬 DB 평문 노출, 스크린샷 유출, 클립보드 탈취)**
11
+
12
+ 로컬 데이터 암호화, 클립보드 보호, 스크린샷 방지, 백그라운드 스크린 블러.
13
+ 저장 데이터와 화면 노출 보호.
14
+
15
+ ### 의존성
16
+
17
+ ```yaml
18
+ # pubspec.yaml
19
+ dependencies:
20
+ encrypt: ^5.0.3 # AES/RSA 암호화
21
+ sqflite_sqlcipher: ^3.1.0 # SQLCipher (암호화된 SQLite)
22
+ # 또는 sqflite + 수동 암호화
23
+ ```
24
+
25
+ ### 로컬 DB 암호화 (SQLCipher)
26
+
27
+ **Incorrect (평문 SQLite):**
28
+ ```dart
29
+ // 기본 sqflite → 평문 저장
30
+ // 루팅 기기에서 DB 파일 복사 → SQLite 브라우저로 열람
31
+ final db = await openDatabase('app.db');
32
+ await db.insert('users', {'name': '홍길동', 'phone': '010-1234-5678'});
33
+ // → /data/data/com.app/databases/app.db 평문
34
+ ```
35
+
36
+ **Correct (SQLCipher 암호화):**
37
+ ```dart
38
+ import 'package:sqflite_sqlcipher/sqflite.dart';
39
+
40
+ class EncryptedDatabase {
41
+ static Database? _db;
42
+
43
+ /// 암호화된 DB 열기
44
+ static Future<Database> getInstance() async {
45
+ if (_db != null) return _db!;
46
+
47
+ // DB 암호화 키 — flutter_secure_storage에서 가져오기
48
+ final storage = const FlutterSecureStorage();
49
+ var dbKey = await storage.read(key: 'db_encryption_key');
50
+
51
+ if (dbKey == null) {
52
+ // 최초 실행: 랜덤 키 생성 후 안전 저장
53
+ dbKey = _generateRandomKey();
54
+ await storage.write(key: 'db_encryption_key', value: dbKey);
55
+ }
56
+
57
+ _db = await openDatabase(
58
+ 'app_encrypted.db',
59
+ password: dbKey, // SQLCipher 암호화 키
60
+ version: 1,
61
+ onCreate: (db, version) async {
62
+ await db.execute('''
63
+ CREATE TABLE users (
64
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
65
+ name TEXT NOT NULL,
66
+ phone TEXT,
67
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
68
+ )
69
+ ''');
70
+ },
71
+ );
72
+
73
+ return _db!;
74
+ }
75
+
76
+ static String _generateRandomKey() {
77
+ final random = Random.secure();
78
+ final bytes = List<int>.generate(32, (_) => random.nextInt(256));
79
+ return base64Encode(bytes);
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### 필드 레벨 암호화 (encrypt 패키지)
85
+
86
+ ```dart
87
+ import 'package:encrypt/encrypt.dart' as encrypt;
88
+
89
+ class FieldEncryption {
90
+ late final encrypt.Encrypter _encrypter;
91
+ late final encrypt.IV _iv;
92
+
93
+ FieldEncryption({required String key}) {
94
+ // AES-256 키 (32바이트)
95
+ final aesKey = encrypt.Key.fromUtf8(key.padRight(32).substring(0, 32));
96
+ _iv = encrypt.IV.fromLength(16);
97
+ _encrypter = encrypt.Encrypter(encrypt.AES(aesKey));
98
+ }
99
+
100
+ /// 민감 필드 암호화
101
+ String encryptField(String plaintext) {
102
+ return _encrypter.encrypt(plaintext, iv: _iv).base64;
103
+ }
104
+
105
+ /// 복호화
106
+ String decryptField(String encrypted) {
107
+ return _encrypter.decrypt64(encrypted, iv: _iv);
108
+ }
109
+ }
110
+
111
+ // 사용 예시
112
+ final encryption = FieldEncryption(key: await getEncryptionKey());
113
+ final encryptedPhone = encryption.encryptField('010-1234-5678');
114
+ // DB에 암호화된 값 저장
115
+ await db.insert('users', {'phone': encryptedPhone});
116
+ ```
117
+
118
+ ### 스크린샷 방지
119
+
120
+ ```dart
121
+ /// Android: FLAG_SECURE 설정
122
+ /// iOS: 화면 캡처 감지 + 오버레이
123
+ class ScreenshotProtection {
124
+
125
+ /// Android: FLAG_SECURE (스크린샷 + 화면 녹화 차단)
126
+ static Future<void> enableAndroid() async {
127
+ if (Platform.isAndroid) {
128
+ // MethodChannel로 네이티브 호출
129
+ const channel = MethodChannel('com.app/security');
130
+ await channel.invokeMethod('enableSecureFlag');
131
+ }
132
+ }
133
+
134
+ static Future<void> disableAndroid() async {
135
+ if (Platform.isAndroid) {
136
+ const channel = MethodChannel('com.app/security');
137
+ await channel.invokeMethod('disableSecureFlag');
138
+ }
139
+ }
140
+ }
141
+
142
+ // Android 네이티브 (MainActivity.kt)
143
+ // class MainActivity: FlutterActivity() {
144
+ // override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
145
+ // super.configureFlutterEngine(flutterEngine)
146
+ // MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.app/security")
147
+ // .setMethodCallHandler { call, result ->
148
+ // when (call.method) {
149
+ // "enableSecureFlag" -> {
150
+ // window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
151
+ // result.success(null)
152
+ // }
153
+ // "disableSecureFlag" -> {
154
+ // window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
155
+ // result.success(null)
156
+ // }
157
+ // }
158
+ // }
159
+ // }
160
+ // }
161
+ ```
162
+
163
+ ### 백그라운드 스크린 블러
164
+
165
+ ```dart
166
+ /// 앱 백그라운드 진입 시 민감 화면 가리기 (앱 스위처에서 노출 방지)
167
+ class AppLifecycleObserver extends WidgetsBindingObserver {
168
+ final GlobalKey<NavigatorState> navigatorKey;
169
+ OverlayEntry? _overlayEntry;
170
+
171
+ AppLifecycleObserver({required this.navigatorKey});
172
+
173
+ @override
174
+ void didChangeAppLifecycleState(AppLifecycleState state) {
175
+ switch (state) {
176
+ case AppLifecycleState.inactive:
177
+ case AppLifecycleState.paused:
178
+ // 앱 스위처에 표시될 때 오버레이
179
+ _showSecurityOverlay();
180
+ case AppLifecycleState.resumed:
181
+ _removeSecurityOverlay();
182
+ default:
183
+ break;
184
+ }
185
+ }
186
+
187
+ void _showSecurityOverlay() {
188
+ _overlayEntry = OverlayEntry(
189
+ builder: (_) => Container(
190
+ color: Colors.white, // 또는 앱 로고가 있는 스플래시
191
+ child: const Center(child: FlutterLogo(size: 100)),
192
+ ),
193
+ );
194
+ navigatorKey.currentState?.overlay?.insert(_overlayEntry!);
195
+ }
196
+
197
+ void _removeSecurityOverlay() {
198
+ _overlayEntry?.remove();
199
+ _overlayEntry = null;
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### 클립보드 보호
205
+
206
+ ```dart
207
+ /// 민감 데이터 복사 후 자동 클리어
208
+ class ClipboardProtection {
209
+ static Timer? _clearTimer;
210
+
211
+ /// 민감 데이터를 클립보드에 복사 + 자동 클리어
212
+ static Future<void> copyWithAutoClean(
213
+ String data, {
214
+ Duration clearAfter = const Duration(seconds: 30),
215
+ }) async {
216
+ await Clipboard.setData(ClipboardData(text: data));
217
+
218
+ _clearTimer?.cancel();
219
+ _clearTimer = Timer(clearAfter, () async {
220
+ // 클립보드 비우기
221
+ await Clipboard.setData(const ClipboardData(text: ''));
222
+ });
223
+ }
224
+
225
+ /// 즉시 클립보드 클리어
226
+ static Future<void> clearNow() async {
227
+ _clearTimer?.cancel();
228
+ await Clipboard.setData(const ClipboardData(text: ''));
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### 안전한 텍스트 입력
234
+
235
+ ```dart
236
+ /// 민감 입력 필드 (비밀번호, PIN)
237
+ class SecureTextField extends StatelessWidget {
238
+ final TextEditingController controller;
239
+ final String label;
240
+
241
+ const SecureTextField({
242
+ super.key,
243
+ required this.controller,
244
+ required this.label,
245
+ });
246
+
247
+ @override
248
+ Widget build(BuildContext context) {
249
+ return TextField(
250
+ controller: controller,
251
+ obscureText: true,
252
+ enableSuggestions: false, // 자동 완성 비활성화
253
+ autocorrect: false, // 자동 수정 비활성화
254
+ enableIMEPersonalizedLearning: false, // 키보드 학습 비활성화
255
+ decoration: InputDecoration(labelText: label),
256
+ );
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### 규칙
262
+
263
+ - 로컬 DB 민감 데이터 → SQLCipher 또는 필드 레벨 암호화
264
+ - DB 암호화 키 → `flutter_secure_storage`에 저장 (코드 하드코딩 금지)
265
+ - 최초 실행 → 랜덤 키 생성 후 안전 저장소에 보관
266
+ - 스크린샷 방지 → Android `FLAG_SECURE`, iOS 오버레이 방식
267
+ - 민감 화면만 선별 적용 (전체 앱 적용 시 UX 저하)
268
+ - 앱 백그라운드 → 스크린 블러/오버레이 (앱 스위처 노출 방지)
269
+ - 클립보드 → 민감 데이터 복사 후 30초 내 자동 클리어
270
+ - 비밀번호/PIN 입력 → `enableSuggestions: false` + `autocorrect: false`
271
+ - `enableIMEPersonalizedLearning: false` → 키보드 학습에 민감 입력 미포함
@@ -0,0 +1,213 @@
1
+ ---
2
+ title: Code Obfuscation
3
+ impact: HIGH
4
+ impactDescription: "미적용 → 앱 디컴파일 시 비즈니스 로직/API 엔드포인트 노출"
5
+ tags: obfuscation, proguard, r8, dsym, split-debug-info, crashlytics
6
+ ---
7
+
8
+ ## Code Obfuscation
9
+
10
+ **Impact: HIGH (미적용 → 앱 디컴파일 시 비즈니스 로직/API 엔드포인트 노출)**
11
+
12
+ Dart 난독화, Android ProGuard/R8 shrinking, iOS dSYM 관리,
13
+ Crashlytics 디버그 심볼 업로드. 릴리즈 빌드 보호.
14
+
15
+ ### Dart 난독화
16
+
17
+ **Incorrect (난독화 없이 릴리즈 빌드):**
18
+ ```bash
19
+ flutter build apk --release
20
+ # → Dart 코드 디컴파일 시 함수명/클래스명 그대로 노출
21
+ # → 비즈니스 로직, API 엔드포인트, 검증 로직 분석 가능
22
+ ```
23
+
24
+ **Correct (난독화 + 디버그 심볼 분리):**
25
+ ```bash
26
+ # APK (Android)
27
+ flutter build apk --release \
28
+ --obfuscate \
29
+ --split-debug-info=build/debug-info/android
30
+
31
+ # App Bundle (Android — Play Store 권장)
32
+ flutter build appbundle --release \
33
+ --obfuscate \
34
+ --split-debug-info=build/debug-info/android
35
+
36
+ # iOS
37
+ flutter build ipa --release \
38
+ --obfuscate \
39
+ --split-debug-info=build/debug-info/ios
40
+
41
+ # --obfuscate: Dart 심볼 이름 난독화 (클래스, 함수, 변수명)
42
+ # --split-debug-info: 디버그 심볼을 별도 디렉토리에 분리 (크래시 분석용)
43
+ ```
44
+
45
+ ### 디버그 심볼 보관
46
+
47
+ ```bash
48
+ # 디버그 심볼 구조
49
+ build/debug-info/
50
+ ├── android/
51
+ │ ├── app.android-arm.symbols # ARM32
52
+ │ ├── app.android-arm64.symbols # ARM64
53
+ │ └── app.android-x64.symbols # x86_64
54
+ └── ios/
55
+ └── app.ios-arm64.symbols # iOS ARM64
56
+
57
+ # 버전별 보관 (크래시 리포트 해석에 필수)
58
+ # CI/CD에서 자동 아카이브 권장
59
+ mkdir -p symbols/v1.2.3
60
+ cp -r build/debug-info/* symbols/v1.2.3/
61
+ ```
62
+
63
+ ### Android ProGuard/R8
64
+
65
+ ```groovy
66
+ // android/app/build.gradle
67
+ android {
68
+ buildTypes {
69
+ release {
70
+ // R8 (ProGuard 후속) — 코드 축소 + 난독화
71
+ minifyEnabled true
72
+ shrinkResources true
73
+
74
+ proguardFiles(
75
+ getDefaultProguardFile('proguard-android-optimize.txt'),
76
+ 'proguard-rules.pro'
77
+ )
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ```
84
+ # android/app/proguard-rules.pro
85
+
86
+ # Flutter 관련 유지
87
+ -keep class io.flutter.** { *; }
88
+ -keep class io.flutter.plugins.** { *; }
89
+
90
+ # firebase_messaging 관련
91
+ -keep class com.google.firebase.messaging.** { *; }
92
+
93
+ # flutter_secure_storage 관련
94
+ -keep class com.it_nomads.fluttersecurestorage.** { *; }
95
+
96
+ # Gson / JSON 직렬화 (사용 시)
97
+ -keep class * implements com.google.gson.TypeAdapterFactory
98
+ -keep class * implements com.google.gson.JsonSerializer
99
+ -keep class * implements com.google.gson.JsonDeserializer
100
+
101
+ # Crashlytics
102
+ -keepattributes SourceFile,LineNumberTable
103
+ -keep public class * extends java.lang.Exception
104
+ ```
105
+
106
+ ### iOS dSYM
107
+
108
+ ```bash
109
+ # Xcode → Build Settings
110
+ # DEBUG_INFORMATION_FORMAT = dwarf-with-dsym (Release)
111
+ # STRIP_SWIFT_SYMBOLS = YES
112
+
113
+ # dSYM 위치 (Xcode 빌드 후)
114
+ # ~/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Products/Release-iphoneos/Runner.app.dSYM
115
+
116
+ # Archive 빌드 시 자동 포함
117
+ # Xcode → Product → Archive → dSYM 포함
118
+ ```
119
+
120
+ ### Crashlytics 심볼 업로드
121
+
122
+ ```yaml
123
+ # pubspec.yaml
124
+ dependencies:
125
+ firebase_crashlytics: ^4.1.0
126
+ ```
127
+
128
+ ```bash
129
+ # Dart 난독화 심볼 업로드
130
+ firebase crashlytics:symbols:upload \
131
+ --app=1:123456:android:abcdef \
132
+ build/debug-info/android/
133
+
134
+ # iOS dSYM 업로드 (Fastlane 사용 시)
135
+ # fastlane에서 자동 업로드 설정 또는:
136
+ firebase crashlytics:symbols:upload \
137
+ --app=1:123456:ios:abcdef \
138
+ build/debug-info/ios/
139
+ ```
140
+
141
+ ```dart
142
+ // Crashlytics 초기화 (main.dart)
143
+ Future<void> main() async {
144
+ WidgetsFlutterBinding.ensureInitialized();
145
+ await Firebase.initializeApp();
146
+
147
+ // Crashlytics 에러 핸들링
148
+ FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
149
+
150
+ // 비동기 에러
151
+ PlatformDispatcher.instance.onError = (error, stack) {
152
+ FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
153
+ return true;
154
+ };
155
+
156
+ runApp(const ProviderScope(child: MyApp()));
157
+ }
158
+ ```
159
+
160
+ ### CI/CD 통합
161
+
162
+ ```yaml
163
+ # GitHub Actions 예시
164
+ - name: Build Release APK
165
+ run: |
166
+ flutter build apk --release \
167
+ --obfuscate \
168
+ --split-debug-info=build/debug-info/android \
169
+ --dart-define=API_KEY=${{ secrets.API_KEY }}
170
+
171
+ - name: Upload debug symbols to Crashlytics
172
+ run: |
173
+ firebase crashlytics:symbols:upload \
174
+ --app=${{ secrets.FIREBASE_APP_ID_ANDROID }} \
175
+ build/debug-info/android/
176
+
177
+ - name: Archive debug symbols
178
+ uses: actions/upload-artifact@v4
179
+ with:
180
+ name: debug-symbols-${{ github.sha }}
181
+ path: build/debug-info/
182
+ retention-days: 90
183
+ ```
184
+
185
+ ### 난독화 효과 확인
186
+
187
+ ```bash
188
+ # 난독화 전 (디컴파일 시)
189
+ class UserRepository {
190
+ Future<User> fetchUserProfile(String userId) async { ... }
191
+ Future<void> updatePaymentMethod(PaymentCard card) async { ... }
192
+ }
193
+
194
+ # 난독화 후 (디컴파일 시)
195
+ class a {
196
+ Future<b> c(String d) async { ... }
197
+ Future<void> e(f g) async { ... }
198
+ }
199
+ # → 함수명/클래스명만 난독화, 문자열 리터럴은 그대로
200
+ # → API 키/URL은 별도 보호 필요 (envied obfuscate 등)
201
+ ```
202
+
203
+ ### 규칙
204
+
205
+ - 릴리즈 빌드 → `--obfuscate --split-debug-info=<path>` 필수
206
+ - `--split-debug-info` → 버전별 보관 (크래시 리포트 해석에 필수)
207
+ - Android → `minifyEnabled true` + `shrinkResources true` (R8)
208
+ - `proguard-rules.pro` → Flutter/Firebase/보안 라이브러리 keep 규칙
209
+ - iOS → `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym` (Release)
210
+ - Crashlytics → 난독화 심볼 업로드 필수 (미업로드 시 크래시 로그 해석 불가)
211
+ - CI/CD → 심볼 아카이브 (최소 90일 보관)
212
+ - 문자열 리터럴 → 난독화 대상 아님 → API 키는 `envied obfuscate` 별도 처리
213
+ - 디버그 빌드 → 난독화 적용하지 않음 (개발 생산성)
@@ -0,0 +1,171 @@
1
+ ---
2
+ title: Secure Storage
3
+ impact: CRITICAL
4
+ impactDescription: "평문 저장 → 루팅/탈옥 시 토큰/시크릿 탈취, 계정 도용"
5
+ tags: flutter_secure_storage, keychain, encrypted-shared-preferences, token
6
+ ---
7
+
8
+ ## Secure Storage
9
+
10
+ **Impact: CRITICAL (평문 저장 → 루팅/탈옥 시 토큰/시크릿 탈취, 계정 도용)**
11
+
12
+ flutter_secure_storage를 사용한 민감 데이터 안전 저장.
13
+ iOS Keychain / Android EncryptedSharedPreferences 기반.
14
+
15
+ ### 의존성
16
+
17
+ ```yaml
18
+ # pubspec.yaml
19
+ dependencies:
20
+ flutter_secure_storage: ^9.2.0
21
+ ```
22
+
23
+ ### 기본 사용
24
+
25
+ **Incorrect (SharedPreferences에 토큰 저장):**
26
+ ```dart
27
+ // SharedPreferences → 평문 XML/plist 파일
28
+ // 루팅된 기기에서 파일 탐색기로 즉시 열람 가능
29
+ final prefs = await SharedPreferences.getInstance();
30
+ await prefs.setString('access_token', token);
31
+ await prefs.setString('refresh_token', refreshToken);
32
+ // → /data/data/com.app/shared_prefs/ 에 평문 저장
33
+ ```
34
+
35
+ **Correct (flutter_secure_storage 사용):**
36
+ ```dart
37
+ class SecureStorageService {
38
+ final FlutterSecureStorage _storage;
39
+
40
+ SecureStorageService()
41
+ : _storage = const FlutterSecureStorage(
42
+ aOptions: AndroidOptions(
43
+ encryptedSharedPreferences: true, // EncryptedSharedPreferences 사용
44
+ ),
45
+ iOptions: IOSOptions(
46
+ accessibility: KeychainAccessibility.first_unlock_this_device,
47
+ ),
48
+ );
49
+
50
+ // === 토큰 관리 ===
51
+
52
+ Future<void> saveTokens({
53
+ required String accessToken,
54
+ required String refreshToken,
55
+ }) async {
56
+ await Future.wait([
57
+ _storage.write(key: _Keys.accessToken, value: accessToken),
58
+ _storage.write(key: _Keys.refreshToken, value: refreshToken),
59
+ ]);
60
+ }
61
+
62
+ Future<String?> getAccessToken() async {
63
+ return _storage.read(key: _Keys.accessToken);
64
+ }
65
+
66
+ Future<String?> getRefreshToken() async {
67
+ return _storage.read(key: _Keys.refreshToken);
68
+ }
69
+
70
+ Future<void> deleteTokens() async {
71
+ await Future.wait([
72
+ _storage.delete(key: _Keys.accessToken),
73
+ _storage.delete(key: _Keys.refreshToken),
74
+ ]);
75
+ }
76
+
77
+ /// 로그아웃 시 모든 민감 데이터 삭제
78
+ Future<void> clearAll() async {
79
+ await _storage.deleteAll();
80
+ }
81
+ }
82
+
83
+ abstract class _Keys {
84
+ static const accessToken = 'access_token';
85
+ static const refreshToken = 'refresh_token';
86
+ static const userPin = 'user_pin_hash';
87
+ static const biometricEnabled = 'biometric_enabled';
88
+ }
89
+ ```
90
+
91
+ ### Android 설정
92
+
93
+ ```kotlin
94
+ // android/app/build.gradle
95
+ android {
96
+ defaultConfig {
97
+ minSdk 23 // EncryptedSharedPreferences 최소 요구
98
+ }
99
+ }
100
+ ```
101
+
102
+ ```xml
103
+ <!-- AndroidManifest.xml (선택: 백업에서 Keystore 제외) -->
104
+ <application
105
+ android:fullBackupContent="@xml/backup_rules"
106
+ android:dataExtractionRules="@xml/data_extraction_rules">
107
+ </application>
108
+
109
+ <!-- res/xml/backup_rules.xml -->
110
+ <!-- <exclude domain="sharedpref" path="FlutterSecureStorage"/> -->
111
+ ```
112
+
113
+ ### iOS 설정
114
+
115
+ ```dart
116
+ // Keychain Accessibility 옵션
117
+ const iOptions = IOSOptions(
118
+ // 기기 잠금 해제 후 접근 가능 (기기 바인딩)
119
+ accessibility: KeychainAccessibility.first_unlock_this_device,
120
+ // iCloud Keychain 동기화 비활성화 (기기 전용)
121
+ synchronizable: false,
122
+ );
123
+
124
+ // Keychain Accessibility 레벨:
125
+ // - first_unlock_this_device: 기기 첫 잠금 해제 후 접근 (권장)
126
+ // - unlocked_this_device: 잠금 해제 상태에서만 접근 (더 엄격)
127
+ // - first_unlock: iCloud 동기화 가능 (덜 안전)
128
+ // - passcode: 패스코드 설정된 기기에서만
129
+ ```
130
+
131
+ ### Riverpod Provider 구성
132
+
133
+ ```dart
134
+ final secureStorageServiceProvider = Provider<SecureStorageService>((ref) {
135
+ return SecureStorageService();
136
+ });
137
+
138
+ /// 토큰 존재 여부 (인증 상태 판단)
139
+ final hasValidTokenProvider = FutureProvider<bool>((ref) async {
140
+ final storage = ref.watch(secureStorageServiceProvider);
141
+ final token = await storage.getAccessToken();
142
+ return token != null;
143
+ });
144
+ ```
145
+
146
+ ### 저장해야 할 것 vs 하지 말아야 할 것
147
+
148
+ ```
149
+ ✅ flutter_secure_storage 저장 대상:
150
+ - Access Token / Refresh Token
151
+ - API 시크릿 (런타임 주입된)
152
+ - 사용자 PIN 해시
153
+ - 생체 인증 활성화 플래그
154
+ - 암호화 키 (DB 암호화용)
155
+
156
+ ❌ flutter_secure_storage에 저장하지 말 것:
157
+ - 대량 데이터 (성능 저하) → 암호화된 DB 사용
158
+ - 사용자 설정 (테마, 언어) → SharedPreferences OK
159
+ - 캐시 데이터 → 일반 파일 시스템
160
+ - 검색이 필요한 데이터 → DB 사용
161
+ ```
162
+
163
+ ### 규칙
164
+
165
+ - 토큰/시크릿 → `flutter_secure_storage` 필수 (SharedPreferences 금지)
166
+ - Android → `encryptedSharedPreferences: true` 설정 (AES-256 암호화)
167
+ - iOS → `KeychainAccessibility.first_unlock_this_device` (기기 바인딩)
168
+ - iOS → `synchronizable: false` (iCloud 동기화 비활성화)
169
+ - 로그아웃 시 → `deleteAll()` 로 모든 민감 데이터 삭제
170
+ - 대량 데이터 → secure_storage 대신 암호화된 DB (SQLCipher) 사용
171
+ - 앱 재설치 시 → Android Keystore 자동 초기화, iOS Keychain 잔존 가능 → 첫 실행 시 정리 로직