activo 0.4.3 → 0.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 (166) hide show
  1. package/README.md +203 -1
  2. package/data/2026-03-04_20-54.json +181 -0
  3. package/data/2026-03-04_20-56.json +181 -0
  4. package/data/apex-rulesets/egov.yaml +469 -0
  5. package/data/apex-rulesets/modernize.yaml +687 -0
  6. package/data/apex-rulesets/quality.yaml +1677 -0
  7. package/data/apex-rulesets/rule-schema.yaml +587 -0
  8. package/data/apex-rulesets/secure.yaml +1688 -0
  9. package/data/apex-rulesets/spring.yaml +455 -0
  10. package/data/apex-rulesets/sql-format.yaml +99 -0
  11. package/data/apex-rulesets/sql-oracle.yaml +281 -0
  12. package/data/apex-rulesets/sql.yaml +1660 -0
  13. package/dist/cli/headless.d.ts.map +1 -1
  14. package/dist/cli/headless.js +32 -10
  15. package/dist/cli/headless.js.map +1 -1
  16. package/dist/cli/index.js +31 -3
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent.d.ts +3 -3
  19. package/dist/core/agent.d.ts.map +1 -1
  20. package/dist/core/agent.js +255 -17
  21. package/dist/core/agent.js.map +1 -1
  22. package/dist/core/commands.d.ts +2 -1
  23. package/dist/core/commands.d.ts.map +1 -1
  24. package/dist/core/commands.js +61 -9
  25. package/dist/core/commands.js.map +1 -1
  26. package/dist/core/config.d.ts +14 -0
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +41 -4
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/conversation.d.ts +2 -2
  31. package/dist/core/conversation.d.ts.map +1 -1
  32. package/dist/core/conversation.js.map +1 -1
  33. package/dist/core/intentRouter.d.ts +43 -0
  34. package/dist/core/intentRouter.d.ts.map +1 -0
  35. package/dist/core/intentRouter.js +804 -0
  36. package/dist/core/intentRouter.js.map +1 -0
  37. package/dist/core/llm/anthropic.d.ts +24 -0
  38. package/dist/core/llm/anthropic.d.ts.map +1 -0
  39. package/dist/core/llm/anthropic.js +226 -0
  40. package/dist/core/llm/anthropic.js.map +1 -0
  41. package/dist/core/llm/ollama.d.ts +5 -14
  42. package/dist/core/llm/ollama.d.ts.map +1 -1
  43. package/dist/core/llm/ollama.js +3 -0
  44. package/dist/core/llm/ollama.js.map +1 -1
  45. package/dist/core/llm/types.d.ts +22 -0
  46. package/dist/core/llm/types.d.ts.map +1 -0
  47. package/dist/core/llm/types.js +2 -0
  48. package/dist/core/llm/types.js.map +1 -0
  49. package/dist/core/mcp/client.d.ts +6 -0
  50. package/dist/core/mcp/client.d.ts.map +1 -1
  51. package/dist/core/mcp/client.js +16 -0
  52. package/dist/core/mcp/client.js.map +1 -1
  53. package/dist/core/mcp/init.d.ts +12 -0
  54. package/dist/core/mcp/init.d.ts.map +1 -0
  55. package/dist/core/mcp/init.js +55 -0
  56. package/dist/core/mcp/init.js.map +1 -0
  57. package/dist/core/mcp/logger.d.ts +14 -0
  58. package/dist/core/mcp/logger.d.ts.map +1 -0
  59. package/dist/core/mcp/logger.js +50 -0
  60. package/dist/core/mcp/logger.js.map +1 -0
  61. package/dist/core/tools/analyzeAll.d.ts.map +1 -1
  62. package/dist/core/tools/analyzeAll.js +16 -28
  63. package/dist/core/tools/analyzeAll.js.map +1 -1
  64. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  65. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  66. package/dist/core/tools/analyzePatterns.js +293 -0
  67. package/dist/core/tools/analyzePatterns.js.map +1 -0
  68. package/dist/core/tools/apexPaths.d.ts +14 -0
  69. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  70. package/dist/core/tools/apexPaths.js +54 -0
  71. package/dist/core/tools/apexPaths.js.map +1 -0
  72. package/dist/core/tools/apexUtils.d.ts +36 -0
  73. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  74. package/dist/core/tools/apexUtils.js +83 -0
  75. package/dist/core/tools/apexUtils.js.map +1 -0
  76. package/dist/core/tools/explainIssue.d.ts +3 -0
  77. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  78. package/dist/core/tools/explainIssue.js +181 -0
  79. package/dist/core/tools/explainIssue.js.map +1 -0
  80. package/dist/core/tools/fixGen.d.ts +3 -0
  81. package/dist/core/tools/fixGen.d.ts.map +1 -0
  82. package/dist/core/tools/fixGen.js +338 -0
  83. package/dist/core/tools/fixGen.js.map +1 -0
  84. package/dist/core/tools/generateImprovements.d.ts +21 -0
  85. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  86. package/dist/core/tools/generateImprovements.js +602 -0
  87. package/dist/core/tools/generateImprovements.js.map +1 -0
  88. package/dist/core/tools/generateReport.d.ts +3 -0
  89. package/dist/core/tools/generateReport.d.ts.map +1 -0
  90. package/dist/core/tools/generateReport.js +315 -0
  91. package/dist/core/tools/generateReport.js.map +1 -0
  92. package/dist/core/tools/index.d.ts +7 -0
  93. package/dist/core/tools/index.d.ts.map +1 -1
  94. package/dist/core/tools/index.js +62 -23
  95. package/dist/core/tools/index.js.map +1 -1
  96. package/dist/core/tools/javaAst.d.ts.map +1 -1
  97. package/dist/core/tools/javaAst.js +191 -0
  98. package/dist/core/tools/javaAst.js.map +1 -1
  99. package/dist/core/tools/recommendProfile.d.ts +3 -0
  100. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  101. package/dist/core/tools/recommendProfile.js +334 -0
  102. package/dist/core/tools/recommendProfile.js.map +1 -0
  103. package/dist/core/tools/ruleGen.d.ts +3 -0
  104. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  105. package/dist/core/tools/ruleGen.js +1103 -0
  106. package/dist/core/tools/ruleGen.js.map +1 -0
  107. package/dist/core/tools/standards.d.ts.map +1 -1
  108. package/dist/core/tools/standards.js +7 -3
  109. package/dist/core/tools/standards.js.map +1 -1
  110. package/dist/ui/App.d.ts.map +1 -1
  111. package/dist/ui/App.js +86 -35
  112. package/dist/ui/App.js.map +1 -1
  113. package/dist/ui/components/InputBox.d.ts +1 -3
  114. package/dist/ui/components/InputBox.d.ts.map +1 -1
  115. package/dist/ui/components/InputBox.js +146 -5
  116. package/dist/ui/components/InputBox.js.map +1 -1
  117. package/dist/ui/components/MessageList.d.ts +3 -1
  118. package/dist/ui/components/MessageList.d.ts.map +1 -1
  119. package/dist/ui/components/MessageList.js +13 -7
  120. package/dist/ui/components/MessageList.js.map +1 -1
  121. package/dist/ui/components/StatusBar.d.ts +1 -1
  122. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  123. package/dist/ui/components/StatusBar.js +3 -2
  124. package/dist/ui/components/StatusBar.js.map +1 -1
  125. package/dist/ui/components/ToolStatus.d.ts +3 -1
  126. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  127. package/dist/ui/components/ToolStatus.js +19 -4
  128. package/dist/ui/components/ToolStatus.js.map +1 -1
  129. package/package.json +7 -1
  130. package/demo.gif +0 -0
  131. package/demo.tape +0 -53
  132. package/screenshot.png +0 -0
  133. package/src/cli/banner.ts +0 -38
  134. package/src/cli/headless.ts +0 -63
  135. package/src/cli/index.ts +0 -57
  136. package/src/core/agent.ts +0 -237
  137. package/src/core/commands.ts +0 -118
  138. package/src/core/config.ts +0 -98
  139. package/src/core/conversation.ts +0 -235
  140. package/src/core/llm/ollama.ts +0 -351
  141. package/src/core/mcp/client.ts +0 -143
  142. package/src/core/tools/analyzeAll.ts +0 -494
  143. package/src/core/tools/ast.ts +0 -826
  144. package/src/core/tools/builtIn.ts +0 -221
  145. package/src/core/tools/cache.ts +0 -570
  146. package/src/core/tools/cssAnalysis.ts +0 -324
  147. package/src/core/tools/dependencyAnalysis.ts +0 -363
  148. package/src/core/tools/embeddings.ts +0 -746
  149. package/src/core/tools/frontendAst.ts +0 -802
  150. package/src/core/tools/htmlAnalysis.ts +0 -466
  151. package/src/core/tools/index.ts +0 -160
  152. package/src/core/tools/javaAst.ts +0 -812
  153. package/src/core/tools/memory.ts +0 -655
  154. package/src/core/tools/mybatisAnalysis.ts +0 -322
  155. package/src/core/tools/openapiAnalysis.ts +0 -431
  156. package/src/core/tools/pythonAnalysis.ts +0 -477
  157. package/src/core/tools/sqlAnalysis.ts +0 -298
  158. package/src/core/tools/standards.test.ts +0 -186
  159. package/src/core/tools/standards.ts +0 -889
  160. package/src/core/tools/types.ts +0 -38
  161. package/src/ui/App.tsx +0 -334
  162. package/src/ui/components/InputBox.tsx +0 -37
  163. package/src/ui/components/MessageList.tsx +0 -80
  164. package/src/ui/components/StatusBar.tsx +0 -36
  165. package/src/ui/components/ToolStatus.tsx +0 -38
  166. package/tsconfig.json +0 -21
@@ -0,0 +1,1688 @@
1
+ # Secure Coding Guide Ruleset
2
+ version: "1.0"
3
+ profile: "secure"
4
+
5
+ languages:
6
+ - language: java
7
+ rules:
8
+ # ==================== 1. 입력데이터 검증 및 표현 ====================
9
+
10
+ # 1.1 SQL 삽입
11
+ - id: "secure-sql-001"
12
+ name: "Statement 직접 사용 금지"
13
+ severity: "critical"
14
+ category: "sql-injection"
15
+ description: "createStatement() 사용은 SQL Injection에 취약합니다. PreparedStatement를 사용하세요."
16
+ enabled: true
17
+ pattern:
18
+ type: "ast-method-call"
19
+ method: "^createStatement$"
20
+ custom:
21
+ rule_id: "SEC-SQL-001"
22
+ fix: "PreparedStatement 사용"
23
+
24
+ - id: "secure-sql-002"
25
+ name: "문자열 연결 쿼리 금지"
26
+ severity: "critical"
27
+ category: "sql-injection"
28
+ description: "SQL 문자열 연결은 SQL Injection에 취약합니다."
29
+ enabled: true
30
+ pattern:
31
+ type: "ast-filtered-regex"
32
+ regex: "\"SELECT[^\"]*\"\\s*\\+|\"INSERT[^\"]*\"\\s*\\+|\"UPDATE[^\"]*\"\\s*\\+|\"DELETE[^\"]*\"\\s*\\+"
33
+ custom:
34
+ rule_id: "SEC-SQL-002"
35
+ fix: "PreparedStatement와 바인드 변수 사용"
36
+
37
+ - id: "secure-sql-003"
38
+ name: "MyBatis ${} 사용 금지"
39
+ severity: "critical"
40
+ category: "sql-injection"
41
+ description: "MyBatis에서 ${}는 SQL Injection에 취약합니다. #{}를 사용하세요."
42
+ enabled: true
43
+ pattern:
44
+ type: "regex"
45
+ regex: "\\$\\{[^}]+\\}"
46
+ file_extensions: ["xml"]
47
+ custom:
48
+ rule_id: "SEC-SQL-003"
49
+ fix: "#{} 사용"
50
+
51
+ # 1.2 코드 삽입
52
+ - id: "secure-code-001"
53
+ name: "ScriptEngine.eval() 사용 금지"
54
+ severity: "critical"
55
+ category: "code-injection"
56
+ description: "ScriptEngine.eval()로 외부 입력 실행은 코드 삽입 공격에 취약합니다."
57
+ enabled: true
58
+ pattern:
59
+ type: "ast-method-call"
60
+ method: "^eval$"
61
+ qualifier: "(?i)engine"
62
+ custom:
63
+ rule_id: "SEC-CODE-001"
64
+
65
+ - id: "secure-code-002"
66
+ name: "Runtime.exec() 외부입력 금지"
67
+ severity: "critical"
68
+ category: "code-injection"
69
+ description: "Runtime.exec()에 외부 입력값 사용은 명령어 삽입에 취약합니다."
70
+ enabled: false
71
+ pattern:
72
+ type: "regex"
73
+ regex: "Runtime\\.getRuntime\\(\\)\\.exec\\s*\\("
74
+ custom:
75
+ rule_id: "SEC-CODE-002"
76
+
77
+ - id: "secure-code-003"
78
+ name: "ProcessBuilder 외부입력 금지"
79
+ severity: "critical"
80
+ category: "code-injection"
81
+ description: "ProcessBuilder에 외부 입력값 사용은 명령어 삽입에 취약합니다."
82
+ enabled: false
83
+ pattern:
84
+ type: "regex"
85
+ regex: "new\\s+ProcessBuilder\\s*\\("
86
+ custom:
87
+ rule_id: "SEC-CODE-003"
88
+
89
+ # 1.3 경로 조작 및 자원 삽입
90
+ - id: "secure-path-001"
91
+ name: "파일 경로 외부입력 검증"
92
+ severity: "critical"
93
+ category: "path-traversal"
94
+ description: "외부 입력값을 파일 경로로 직접 사용하면 경로 조작 공격에 취약합니다."
95
+ enabled: true
96
+ pattern:
97
+ type: "ast-filtered-regex"
98
+ regex: "new\\s+File\\s*\\([^)]*request\\.getParameter"
99
+ custom:
100
+ rule_id: "SEC-PATH-001"
101
+
102
+ - id: "secure-path-002"
103
+ name: "경로순회 문자 검증 필수"
104
+ severity: "critical"
105
+ category: "path-traversal"
106
+ description: "파일 접근 시 경로순회 문자(..)를 검증해야 합니다."
107
+ enabled: true
108
+ pattern:
109
+ type: "method-analysis"
110
+ conditions:
111
+ - "file-access-without-path-validation"
112
+ custom:
113
+ rule_id: "SEC-PATH-002"
114
+
115
+ - id: "secure-path-003"
116
+ name: "정규화 경로 검증"
117
+ severity: "high"
118
+ category: "path-traversal"
119
+ description: "파일 접근 시 getCanonicalPath()로 경로를 정규화하여 검증해야 합니다."
120
+ enabled: true
121
+ pattern:
122
+ type: "method-analysis"
123
+ conditions:
124
+ - "file-access-without-canonical-path"
125
+ custom:
126
+ rule_id: "SEC-PATH-003"
127
+
128
+ # 1.4 크로스사이트 스크립트 (XSS)
129
+ - id: "secure-xss-001"
130
+ name: "출력값 인코딩 필수"
131
+ severity: "critical"
132
+ category: "xss"
133
+ description: "외부 입력값을 출력할 때 HTML 인코딩이 필요합니다."
134
+ enabled: true
135
+ pattern:
136
+ type: "ast-filtered-regex"
137
+ regex: "out\\.print(ln)?\\s*\\([^)]*request\\.getParameter"
138
+ custom:
139
+ rule_id: "SEC-XSS-001"
140
+
141
+ - id: "secure-xss-002"
142
+ name: "innerHTML 사용 주의"
143
+ severity: "critical"
144
+ category: "xss"
145
+ description: "innerHTML에 변수 할당은 XSS에 취약합니다."
146
+ enabled: true
147
+ pattern:
148
+ type: "regex"
149
+ regex: "\\.innerHTML\\s*="
150
+ file_extensions: ["js", "html", "jsp"]
151
+ custom:
152
+ rule_id: "SEC-XSS-002"
153
+
154
+ - id: "secure-xss-003"
155
+ name: "document.write 금지"
156
+ severity: "high"
157
+ category: "xss"
158
+ description: "document.write() 사용은 XSS에 취약합니다."
159
+ enabled: true
160
+ pattern:
161
+ type: "regex"
162
+ regex: "document\\.write\\s*\\("
163
+ file_extensions: ["js", "html", "jsp"]
164
+ custom:
165
+ rule_id: "SEC-XSS-003"
166
+
167
+ # 1.5 운영체제 명령어 삽입
168
+ - id: "secure-cmd-001"
169
+ name: "Runtime.exec 외부입력 금지"
170
+ severity: "critical"
171
+ category: "command-injection"
172
+ description: "외부 입력값을 명령어로 직접 실행하면 명령어 삽입에 취약합니다."
173
+ enabled: true
174
+ pattern:
175
+ type: "ast-filtered-regex"
176
+ regex: "Runtime\\.getRuntime\\(\\)\\.exec\\s*\\([^)]*\\+|Runtime\\.getRuntime\\(\\)\\.exec\\s*\\([^\"']"
177
+ custom:
178
+ rule_id: "SEC-CMD-001"
179
+
180
+ - id: "secure-cmd-002"
181
+ name: "ProcessBuilder 외부입력 금지"
182
+ severity: "critical"
183
+ category: "command-injection"
184
+ description: "ProcessBuilder에 외부 입력값 사용은 명령어 삽입에 취약합니다."
185
+ enabled: true
186
+ pattern:
187
+ type: "ast-filtered-regex"
188
+ regex: "new\\s+ProcessBuilder\\s*\\([^)]*\\+"
189
+ custom:
190
+ rule_id: "SEC-CMD-002"
191
+
192
+ # 1.6 위험한 형식 파일 업로드
193
+ - id: "secure-file-001"
194
+ name: "파일 확장자 검증 필수"
195
+ severity: "critical"
196
+ category: "file-upload"
197
+ description: "파일 업로드 시 확장자 검증이 필요합니다."
198
+ enabled: true
199
+ pattern:
200
+ type: "method-analysis"
201
+ conditions:
202
+ - "file-upload-without-extension-validation"
203
+ custom:
204
+ rule_id: "SEC-FILE-001"
205
+
206
+ - id: "secure-file-002"
207
+ name: "실행 파일 확장자 금지"
208
+ severity: "critical"
209
+ category: "file-upload"
210
+ description: "jsp, asp, php, exe 등 실행 파일 업로드를 금지해야 합니다."
211
+ enabled: true
212
+ pattern:
213
+ type: "method-analysis"
214
+ conditions:
215
+ - "executable-extension-allowed"
216
+ custom:
217
+ rule_id: "SEC-FILE-002"
218
+
219
+ # 1.7 신뢰되지 않는 URL 주소로 자동접속 연결
220
+ - id: "secure-redir-001"
221
+ name: "리다이렉트 URL 검증"
222
+ severity: "critical"
223
+ category: "open-redirect"
224
+ description: "외부 입력값으로 직접 리다이렉트하면 피싱 공격에 악용될 수 있습니다."
225
+ enabled: true
226
+ pattern:
227
+ type: "ast-filtered-regex"
228
+ regex: "response\\.sendRedirect\\s*\\([^)]*request\\.getParameter"
229
+ custom:
230
+ rule_id: "SEC-REDIR-001"
231
+
232
+ - id: "secure-redir-002"
233
+ name: "도메인 화이트리스트"
234
+ severity: "critical"
235
+ category: "open-redirect"
236
+ description: "리다이렉트 시 도메인 화이트리스트 검증이 필요합니다."
237
+ enabled: true
238
+ pattern:
239
+ type: "method-analysis"
240
+ conditions:
241
+ - "redirect-without-domain-validation"
242
+ custom:
243
+ rule_id: "SEC-REDIR-002"
244
+
245
+ # 1.8 부적절한 XML 외부개체 참조 (XXE)
246
+ - id: "secure-xxe-001"
247
+ name: "XML 외부엔티티 비활성화"
248
+ severity: "critical"
249
+ category: "xxe"
250
+ description: "DocumentBuilderFactory 사용 시 XXE 방지 설정이 필요합니다."
251
+ enabled: true
252
+ pattern:
253
+ type: "method-analysis"
254
+ conditions:
255
+ - "documentbuilderfactory-without-xxe-protection"
256
+ custom:
257
+ rule_id: "SEC-XXE-001"
258
+
259
+ - id: "secure-xxe-002"
260
+ name: "SAXParser XXE 설정"
261
+ severity: "critical"
262
+ category: "xxe"
263
+ description: "SAXParserFactory 사용 시 XXE 방지 설정이 필요합니다."
264
+ enabled: true
265
+ pattern:
266
+ type: "method-analysis"
267
+ conditions:
268
+ - "saxparserfactory-without-xxe-protection"
269
+ custom:
270
+ rule_id: "SEC-XXE-002"
271
+
272
+ # 1.9 XML 삽입 (XQuery/XPath)
273
+ - id: "secure-xpath-001"
274
+ name: "XPath 외부입력 검증"
275
+ severity: "critical"
276
+ category: "xpath-injection"
277
+ description: "XPath 쿼리에 외부 입력값 직접 사용은 XPath 삽입에 취약합니다."
278
+ enabled: true
279
+ pattern:
280
+ type: "ast-filtered-regex"
281
+ regex: "xpath\\.evaluate\\s*\\([^)]*\\+"
282
+ custom:
283
+ rule_id: "SEC-XPATH-001"
284
+
285
+ - id: "secure-xquery-001"
286
+ name: "XQuery 외부입력 검증"
287
+ severity: "critical"
288
+ category: "xquery-injection"
289
+ description: "XQuery에 외부 입력값 직접 사용은 XQuery 삽입에 취약합니다."
290
+ enabled: true
291
+ pattern:
292
+ type: "ast-filtered-regex"
293
+ regex: "executeQuery\\s*\\([^)]*\\+"
294
+ custom:
295
+ rule_id: "SEC-XQUERY-001"
296
+
297
+ # 1.10 LDAP 삽입
298
+ - id: "secure-ldap-001"
299
+ name: "LDAP 필터 외부입력 검증"
300
+ severity: "critical"
301
+ category: "ldap-injection"
302
+ description: "LDAP 필터에 외부 입력값 직접 사용은 LDAP 삽입에 취약합니다."
303
+ enabled: true
304
+ pattern:
305
+ type: "ast-filtered-regex"
306
+ regex: "\\(\\s*&\\s*\\([^)]*=.*\\+|search\\s*\\([^)]*\\+"
307
+ custom:
308
+ rule_id: "SEC-LDAP-001"
309
+
310
+ # 1.11 크로스사이트 요청 위조 (CSRF)
311
+ - id: "secure-csrf-001"
312
+ name: "CSRF 토큰 검증 필수"
313
+ severity: "medium"
314
+ category: "csrf"
315
+ description: "POST 요청 처리 시 CSRF 토큰 검증이 필요합니다. Spring Security CsrfFilter 사용 시 Controller 레벨 검증은 불필요."
316
+ enabled: true
317
+ pattern:
318
+ type: "method-analysis"
319
+ conditions:
320
+ - "post-handler-without-csrf-token"
321
+ custom:
322
+ rule_id: "SEC-CSRF-001"
323
+
324
+ - id: "secure-csrf-002"
325
+ name: "중요 기능 재인증"
326
+ severity: "high"
327
+ category: "csrf"
328
+ description: "계좌이체, 비밀번호 변경 시 재인증이 필요합니다."
329
+ enabled: true
330
+ pattern:
331
+ type: "method-analysis"
332
+ conditions:
333
+ - "sensitive-operation-without-reauth"
334
+ custom:
335
+ rule_id: "SEC-CSRF-002"
336
+
337
+ # 1.12 서버사이드 요청 위조 (SSRF)
338
+ - id: "secure-ssrf-001"
339
+ name: "URL 외부입력 검증"
340
+ severity: "critical"
341
+ category: "ssrf"
342
+ description: "외부 입력값으로 URL 접근은 SSRF 공격에 취약합니다."
343
+ enabled: true
344
+ pattern:
345
+ type: "ast-filtered-regex"
346
+ regex: "new\\s+URL\\s*\\([^)]*request\\.getParameter"
347
+ custom:
348
+ rule_id: "SEC-SSRF-001"
349
+
350
+ - id: "secure-ssrf-002"
351
+ name: "내부망 접근 차단"
352
+ severity: "critical"
353
+ category: "ssrf"
354
+ description: "내부망 IP 접근 차단 검증이 필요합니다."
355
+ enabled: true
356
+ pattern:
357
+ type: "method-analysis"
358
+ conditions:
359
+ - "url-access-without-internal-ip-check"
360
+ custom:
361
+ rule_id: "SEC-SSRF-002"
362
+
363
+ # 1.13 HTTP 응답분할
364
+ - id: "secure-http-001"
365
+ name: "헤더 개행문자 검증"
366
+ severity: "critical"
367
+ category: "http-response-splitting"
368
+ description: "HTTP 응답 헤더에 개행문자가 포함되면 응답분할 공격에 취약합니다."
369
+ enabled: true
370
+ pattern:
371
+ type: "method-analysis"
372
+ conditions:
373
+ - "header-value-without-crlf-validation"
374
+ custom:
375
+ rule_id: "SEC-HTTP-001"
376
+
377
+ # 1.14 정수형 오버플로우
378
+ - id: "secure-int-001"
379
+ name: "배열 크기 검증"
380
+ severity: "high"
381
+ category: "integer-overflow"
382
+ description: "외부 입력값을 배열 크기로 사용할 때 범위 검증이 필요합니다."
383
+ enabled: true
384
+ pattern:
385
+ type: "ast-filtered-regex"
386
+ regex: "new\\s+\\w+\\s*\\[\\s*[^\\]]*parseInt|new\\s+\\w+\\s*\\[\\s*[^\\]]*getParameter"
387
+ custom:
388
+ rule_id: "SEC-INT-001"
389
+
390
+ # 1.15 보안기능 결정에 사용되는 부적절한 입력값
391
+ - id: "secure-input-001"
392
+ name: "가격/권한 서버 조회"
393
+ severity: "critical"
394
+ category: "insecure-input"
395
+ description: "결제 금액, 사용자 권한을 클라이언트에서 받으면 조작 위험이 있습니다."
396
+ enabled: true
397
+ pattern:
398
+ type: "method-analysis"
399
+ conditions:
400
+ - "price-or-permission-from-client"
401
+ custom:
402
+ rule_id: "SEC-INPUT-001"
403
+
404
+ - id: "secure-input-002"
405
+ name: "히든필드 신뢰 금지"
406
+ severity: "critical"
407
+ category: "insecure-input"
408
+ description: "hidden 필드 값으로 보안결정을 하면 조작 위험이 있습니다."
409
+ enabled: true
410
+ pattern:
411
+ type: "method-analysis"
412
+ conditions:
413
+ - "security-decision-from-hidden-field"
414
+ custom:
415
+ rule_id: "SEC-INPUT-002"
416
+
417
+ # 1.16 메모리 버퍼 오버플로우 (C/C++ 관련 - Java에서는 참조용)
418
+ - id: "secure-buf-001"
419
+ name: "안전한 문자열 함수 사용"
420
+ severity: "critical"
421
+ category: "buffer-overflow"
422
+ description: "strcpy, strcat, gets, sprintf는 버퍼 오버플로우에 취약합니다."
423
+ enabled: false
424
+ pattern:
425
+ type: "regex"
426
+ regex: "\\b(strcpy|strcat|gets|sprintf)\\s*\\("
427
+ custom:
428
+ rule_id: "SEC-BUF-001"
429
+ note: "C/C++ 전용 규칙"
430
+
431
+ # 1.17 포맷 스트링 삽입
432
+ - id: "secure-fmt-001"
433
+ name: "포맷 스트링 외부입력 금지"
434
+ severity: "critical"
435
+ category: "format-string"
436
+ description: "String.format()에 외부 입력값을 직접 사용하면 포맷 스트링 공격에 취약합니다."
437
+ enabled: true
438
+ pattern:
439
+ type: "ast-filtered-regex"
440
+ regex: "String\\.format\\s*\\([^,)]*request\\.getParameter"
441
+ custom:
442
+ rule_id: "SEC-FMT-001"
443
+
444
+ # 1.18 입력데이터 검증 누락 (CWE-20)
445
+
446
+ - id: "secure-valid-001"
447
+ name: "@RequestBody 입력 검증 누락"
448
+ severity: "medium"
449
+ category: "input-validation"
450
+ description: "@RequestBody로 VO/DTO 객체를 바인딩할 때 @Valid 어노테이션이 없으면 입력값 검증이 수행되지 않습니다. Map<String,Object> 타입은 제외합니다."
451
+ enabled: true
452
+ tags:
453
+ - "CWE-20"
454
+ - "input-validation"
455
+ pattern:
456
+ type: "regex"
457
+ regex: "@RequestBody\\s+(?!@Valid\\b)(?!Map\\b)(?!HashMap\\b)(?!LinkedHashMap\\b)(?!List\\b)(?!Set\\b)(?!Collection\\b)(?!String\\b)[A-Z]\\w+"
458
+ custom:
459
+ rule_id: "SEC-VALID-001"
460
+ fix: "@Valid @RequestBody MyVO vo 형태로 @Valid를 추가하세요"
461
+
462
+ - id: "secure-valid-002"
463
+ name: "@ModelAttribute 입력 검증 누락"
464
+ severity: "low"
465
+ category: "input-validation"
466
+ description: "@ModelAttribute로 VO 객체를 바인딩할 때 @Valid 어노테이션이 없으면 폼 입력값 검증이 수행되지 않습니다."
467
+ enabled: true
468
+ tags:
469
+ - "CWE-20"
470
+ - "input-validation"
471
+ pattern:
472
+ type: "regex"
473
+ regex: "@ModelAttribute\\s+(?!@Valid\\b)[A-Z]\\w+"
474
+ custom:
475
+ rule_id: "SEC-VALID-002"
476
+ fix: "@Valid @ModelAttribute MyVO vo 형태로 @Valid를 추가하세요"
477
+
478
+ # ==================== 2. 보안기능 ====================
479
+
480
+ # 2.1 적절한 인증 없는 중요기능 허용
481
+ - id: "secure-auth-001"
482
+ name: "중요기능 인증 필수"
483
+ severity: "critical"
484
+ category: "authentication"
485
+ description: "회원정보 수정, 결제 시 세션 검증이 필요합니다."
486
+ enabled: true
487
+ pattern:
488
+ type: "method-analysis"
489
+ conditions:
490
+ - "sensitive-function-without-auth-check"
491
+ custom:
492
+ rule_id: "SEC-AUTH-001"
493
+
494
+ - id: "secure-auth-002"
495
+ name: "재인증 필요"
496
+ severity: "critical"
497
+ category: "authentication"
498
+ description: "비밀번호 변경, 계좌이체 시 재인증이 필요합니다."
499
+ enabled: true
500
+ pattern:
501
+ type: "method-analysis"
502
+ conditions:
503
+ - "password-change-without-reauth"
504
+ custom:
505
+ rule_id: "SEC-AUTH-002"
506
+
507
+ # 2.2 부적절한 인가
508
+ - id: "secure-authz-001"
509
+ name: "자원 접근 권한 검증"
510
+ severity: "high"
511
+ category: "authorization"
512
+ description: "삭제, 수정 시 권한 검증이 필요합니다. SecurityFilterChain에서 URL 패턴별로 처리 가능합니다."
513
+ enabled: true
514
+ pattern:
515
+ type: "method-analysis"
516
+ conditions:
517
+ - "resource-modification-without-authz"
518
+ custom:
519
+ rule_id: "SEC-AUTHZ-001"
520
+
521
+ - id: "secure-authz-002"
522
+ name: "관리자 기능 보호"
523
+ severity: "low"
524
+ category: "authorization"
525
+ description: "관리자 URL 접근 권한 검증이 필요합니다. (참고: JSP 앱에서는 SecurityFilterChain 설정이 우선)"
526
+ enabled: true
527
+ pattern:
528
+ type: "method-analysis"
529
+ conditions:
530
+ - "admin-function-without-role-check"
531
+ custom:
532
+ rule_id: "SEC-AUTHZ-002"
533
+
534
+ # 2.3 중요한 자원에 대한 잘못된 권한 설정
535
+ - id: "secure-perm-001"
536
+ name: "파일 권한 최소화"
537
+ severity: "critical"
538
+ category: "permission"
539
+ description: "setWritable(true, false)는 모든 사용자에게 쓰기 권한을 부여합니다."
540
+ enabled: true
541
+ pattern:
542
+ type: "ast-filtered-regex"
543
+ regex: "setWritable\\s*\\(\\s*true\\s*,\\s*false\\s*\\)"
544
+ custom:
545
+ rule_id: "SEC-PERM-001"
546
+
547
+ - id: "secure-perm-002"
548
+ name: "파일 권한 전체 공개"
549
+ severity: "critical"
550
+ category: "permission"
551
+ description: "setReadable(true, false)는 모든 사용자에게 읽기 권한을 부여합니다."
552
+ enabled: true
553
+ pattern:
554
+ type: "ast-filtered-regex"
555
+ regex: "setReadable\\s*\\(\\s*true\\s*,\\s*false\\s*\\)"
556
+ custom:
557
+ rule_id: "SEC-PERM-002"
558
+
559
+ # 2.4 취약한 암호화 알고리즘 사용
560
+ - id: "secure-crypto-001"
561
+ name: "DES 사용 금지"
562
+ severity: "critical"
563
+ category: "weak-crypto"
564
+ description: "DES는 취약한 암호화 알고리즘입니다. AES를 사용하세요."
565
+ enabled: true
566
+ pattern:
567
+ type: "ast-filtered-regex"
568
+ regex: "Cipher\\.getInstance\\s*\\(\\s*[\"']DES[\"']"
569
+ custom:
570
+ rule_id: "SEC-CRYPTO-001"
571
+ fix: "AES/GCM/NoPadding 사용"
572
+
573
+ - id: "secure-crypto-002"
574
+ name: "MD5 사용 금지"
575
+ severity: "critical"
576
+ category: "weak-crypto"
577
+ description: "MD5는 취약한 해시 알고리즘입니다. SHA-256 이상을 사용하세요."
578
+ enabled: true
579
+ pattern:
580
+ type: "ast-filtered-regex"
581
+ regex: "MessageDigest\\.getInstance\\s*\\(\\s*[\"']MD5[\"']"
582
+ custom:
583
+ rule_id: "SEC-CRYPTO-002"
584
+ fix: "SHA-256 이상 사용"
585
+
586
+ - id: "secure-crypto-003"
587
+ name: "SHA1 사용 금지"
588
+ severity: "high"
589
+ category: "weak-crypto"
590
+ description: "SHA-1은 취약한 해시 알고리즘입니다. SHA-256 이상을 사용하세요."
591
+ enabled: true
592
+ pattern:
593
+ type: "ast-filtered-regex"
594
+ regex: "MessageDigest\\.getInstance\\s*\\(\\s*[\"']SHA-?1[\"']"
595
+ custom:
596
+ rule_id: "SEC-CRYPTO-003"
597
+ fix: "SHA-256 이상 사용"
598
+
599
+ - id: "secure-crypto-004"
600
+ name: "RC4 사용 금지"
601
+ severity: "critical"
602
+ category: "weak-crypto"
603
+ description: "RC4는 취약한 암호화 알고리즘입니다."
604
+ enabled: true
605
+ pattern:
606
+ type: "ast-filtered-regex"
607
+ regex: "Cipher\\.getInstance\\s*\\(\\s*[\"']RC4[\"']"
608
+ custom:
609
+ rule_id: "SEC-CRYPTO-004"
610
+
611
+ # 2.5 중요정보 평문저장/전송
612
+ - id: "secure-plain-001"
613
+ name: "비밀번호 평문 저장 금지"
614
+ severity: "critical"
615
+ category: "plaintext-storage"
616
+ description: "비밀번호는 해시하여 저장해야 합니다."
617
+ enabled: true
618
+ pattern:
619
+ type: "method-analysis"
620
+ conditions:
621
+ - "password-stored-plaintext"
622
+ custom:
623
+ rule_id: "SEC-PLAIN-001"
624
+
625
+ - id: "secure-plain-002"
626
+ name: "중요정보 암호화 전송"
627
+ severity: "critical"
628
+ category: "plaintext-transmission"
629
+ description: "비밀번호, 카드번호는 암호화하여 전송해야 합니다."
630
+ enabled: true
631
+ pattern:
632
+ type: "method-analysis"
633
+ conditions:
634
+ - "sensitive-data-plaintext-transmission"
635
+ custom:
636
+ rule_id: "SEC-PLAIN-002"
637
+
638
+ - id: "secure-plain-003"
639
+ name: "쿠키 보안속성 미설정"
640
+ severity: "high"
641
+ category: "cookie-security"
642
+ description: "쿠키 생성 시 setSecure(true)를 설정해야 합니다."
643
+ enabled: true
644
+ pattern:
645
+ type: "ast-filtered-regex"
646
+ regex: "new\\s+Cookie\\s*\\([^)]+\\)(?![^;]*setSecure\\s*\\(\\s*true)"
647
+ custom:
648
+ rule_id: "SEC-PLAIN-003"
649
+
650
+ # 2.6 하드코드된 중요정보
651
+ - id: "secure-hard-001"
652
+ name: "하드코드된 비밀번호"
653
+ severity: "critical"
654
+ category: "hardcoded-credentials"
655
+ description: "비밀번호를 소스코드에 하드코딩하면 안 됩니다."
656
+ enabled: false
657
+ pattern:
658
+ type: "regex"
659
+ regex: "(password|passwd|pwd)\\s*=\\s*[\"'][^\"']+[\"']"
660
+ flags: "i"
661
+ custom:
662
+ rule_id: "SEC-HARD-001"
663
+
664
+ - id: "secure-hard-002"
665
+ name: "하드코드된 암호화 키"
666
+ severity: "critical"
667
+ category: "hardcoded-credentials"
668
+ description: "암호화 키를 소스코드에 하드코딩하면 안 됩니다."
669
+ enabled: true
670
+ pattern:
671
+ type: "ast-filtered-regex"
672
+ regex: "(secret|secretKey|SECRET_KEY|encryptKey|ENCRYPT_KEY)\\s*=\\s*[\"'][^\"']+[\"']"
673
+ custom:
674
+ rule_id: "SEC-HARD-002"
675
+
676
+ - id: "secure-hard-003"
677
+ name: "하드코드된 DB 접속정보"
678
+ severity: "critical"
679
+ category: "hardcoded-credentials"
680
+ description: "DB 접속정보를 소스코드에 하드코딩하면 안 됩니다."
681
+ enabled: true
682
+ pattern:
683
+ type: "ast-filtered-regex"
684
+ regex: "DriverManager\\.getConnection\\s*\\([^)]*[\"'][^\"']+[\"']\\s*,\\s*[\"'][^\"']+[\"']\\s*,\\s*[\"'][^\"']+[\"']"
685
+ custom:
686
+ rule_id: "SEC-HARD-003"
687
+
688
+ # 2.7 충분하지 않은 키 길이 사용
689
+ - id: "secure-key-001"
690
+ name: "RSA 키 길이 2048 이상"
691
+ severity: "critical"
692
+ category: "weak-key"
693
+ description: "RSA 키는 최소 2048비트 이상이어야 합니다."
694
+ enabled: true
695
+ pattern:
696
+ type: "ast-filtered-regex"
697
+ regex: "keyGen\\.initialize\\s*\\(\\s*(512|768|1024)\\s*\\)"
698
+ custom:
699
+ rule_id: "SEC-KEY-001"
700
+ min_length: "2048"
701
+
702
+ - id: "secure-key-002"
703
+ name: "AES 키 길이 128 이상"
704
+ severity: "critical"
705
+ category: "weak-key"
706
+ description: "AES 키는 최소 128비트 이상이어야 합니다."
707
+ enabled: true
708
+ pattern:
709
+ type: "ast-filtered-regex"
710
+ regex: "keyGen\\.init\\s*\\(\\s*(56|64)\\s*\\)"
711
+ custom:
712
+ rule_id: "SEC-KEY-002"
713
+ min_length: "128"
714
+
715
+ # 2.8 적절하지 않은 난수값 사용
716
+ - id: "secure-rand-001"
717
+ name: "세션ID 생성 시 SecureRandom 사용"
718
+ severity: "critical"
719
+ category: "weak-random"
720
+ description: "세션ID, 토큰 생성 시 SecureRandom을 사용해야 합니다."
721
+ enabled: true
722
+ pattern:
723
+ type: "ast-filtered-regex"
724
+ regex: "new\\s+Random\\s*\\(\\s*\\)[^;]*session|new\\s+Random\\s*\\(\\s*\\)[^;]*token"
725
+ custom:
726
+ rule_id: "SEC-RAND-001"
727
+ fix: "SecureRandom 사용"
728
+ flags: "i"
729
+
730
+ - id: "secure-rand-002"
731
+ name: "암호화 키 생성 시 SecureRandom 사용"
732
+ severity: "critical"
733
+ category: "weak-random"
734
+ description: "암호화 키 생성 시 Math.random()을 사용하면 안 됩니다."
735
+ enabled: true
736
+ pattern:
737
+ type: "ast-filtered-regex"
738
+ regex: "Math\\.random\\s*\\(\\s*\\)[^;]*key"
739
+ custom:
740
+ rule_id: "SEC-RAND-002"
741
+ flags: "i"
742
+
743
+ # 2.9 취약한 비밀번호 허용
744
+ - id: "secure-pwd-001"
745
+ name: "비밀번호 복잡도 검증"
746
+ severity: "high"
747
+ category: "weak-password"
748
+ description: "비밀번호 설정 시 복잡도 검증이 필요합니다."
749
+ enabled: true
750
+ pattern:
751
+ type: "method-analysis"
752
+ conditions:
753
+ - "password-set-without-complexity-check"
754
+ custom:
755
+ rule_id: "SEC-PWD-001"
756
+
757
+ - id: "secure-pwd-002"
758
+ name: "비밀번호 최소 길이"
759
+ severity: "high"
760
+ category: "weak-password"
761
+ description: "비밀번호는 최소 8자 이상이어야 합니다."
762
+ enabled: true
763
+ pattern:
764
+ type: "method-analysis"
765
+ conditions:
766
+ - "password-length-less-than-8"
767
+ custom:
768
+ rule_id: "SEC-PWD-002"
769
+
770
+ # 2.10 부적절한 전자서명 확인
771
+ - id: "secure-sign-001"
772
+ name: "JAR 서명 검증"
773
+ severity: "high"
774
+ category: "signature-verification"
775
+ description: "JarFile 사용 시 서명 검증이 필요합니다."
776
+ enabled: true
777
+ pattern:
778
+ type: "method-analysis"
779
+ conditions:
780
+ - "jarfile-without-signature-verification"
781
+ custom:
782
+ rule_id: "SEC-SIGN-001"
783
+
784
+ # 2.11 부적절한 인증서 유효성 검증
785
+ - id: "secure-cert-001"
786
+ name: "SSL 인증서 검증"
787
+ severity: "critical"
788
+ category: "certificate-validation"
789
+ description: "TrustManager에서 모든 인증서를 허용하면 안 됩니다."
790
+ enabled: true
791
+ pattern:
792
+ type: "regex"
793
+ regex: "X509TrustManager[^}]*checkServerTrusted[^}]*\\{\\s*\\}"
794
+ custom:
795
+ rule_id: "SEC-CERT-001"
796
+
797
+ - id: "secure-cert-002"
798
+ name: "호스트명 검증"
799
+ severity: "critical"
800
+ category: "certificate-validation"
801
+ description: "HostnameVerifier에서 모든 호스트를 허용하면 안 됩니다."
802
+ enabled: true
803
+ pattern:
804
+ type: "regex"
805
+ regex: "HostnameVerifier[^}]*verify[^}]*return\\s+true"
806
+ custom:
807
+ rule_id: "SEC-CERT-002"
808
+
809
+ # 2.12 쿠키 보안
810
+ - id: "secure-cookie-001"
811
+ name: "쿠키 만료시간 제한"
812
+ severity: "low"
813
+ category: "cookie-security"
814
+ description: "쿠키 만료시간은 최소한으로 설정해야 합니다."
815
+ enabled: true
816
+ pattern:
817
+ type: "ast-filtered-regex"
818
+ regex: "setMaxAge\\s*\\(\\s*\\d{5,}\\s*\\)"
819
+ custom:
820
+ rule_id: "SEC-COOKIE-001"
821
+ max_age: "3600"
822
+
823
+ - id: "secure-cookie-002"
824
+ name: "쿠키 Secure 속성"
825
+ severity: "high"
826
+ category: "cookie-security"
827
+ description: "중요 정보 쿠키는 setSecure(true)를 설정해야 합니다."
828
+ enabled: true
829
+ pattern:
830
+ type: "ast-filtered-regex"
831
+ regex: "setSecure\\s*\\(\\s*false\\s*\\)"
832
+ custom:
833
+ rule_id: "SEC-COOKIE-002"
834
+
835
+ - id: "secure-cookie-003"
836
+ name: "쿠키 HttpOnly 속성"
837
+ severity: "high"
838
+ category: "cookie-security"
839
+ description: "세션 쿠키는 setHttpOnly(true)를 설정해야 합니다."
840
+ enabled: true
841
+ pattern:
842
+ type: "ast-filtered-regex"
843
+ regex: "setHttpOnly\\s*\\(\\s*false\\s*\\)"
844
+ custom:
845
+ rule_id: "SEC-COOKIE-003"
846
+
847
+ # 2.13 주석문 안에 포함된 시스템 주요정보
848
+ - id: "secure-comment-001"
849
+ name: "주석 내 비밀번호 금지"
850
+ severity: "critical"
851
+ category: "info-exposure"
852
+ description: "주석에 비밀번호 정보를 포함하면 안 됩니다."
853
+ enabled: true
854
+ pattern:
855
+ type: "regex"
856
+ regex: "//[^\\n]*(password|pwd|비밀번호)[^\\n]*[:=][^\\n]*\\w+"
857
+ flags: "i"
858
+ custom:
859
+ rule_id: "SEC-COMMENT-001"
860
+
861
+ - id: "secure-comment-002"
862
+ name: "주석 내 계정정보 금지"
863
+ severity: "critical"
864
+ category: "info-exposure"
865
+ description: "주석에 계정정보를 포함하면 안 됩니다."
866
+ enabled: true
867
+ pattern:
868
+ type: "regex"
869
+ regex: "/\\*[^*]*\\*+([^/*][^*]*\\*+)*(admin|root)[^*]*(password|pwd)"
870
+ flags: "i"
871
+ custom:
872
+ rule_id: "SEC-COMMENT-002"
873
+
874
+ # 2.14 솔트 없이 일방향 해쉬함수 사용
875
+ - id: "secure-hash-001"
876
+ name: "비밀번호 해시 시 솔트 사용"
877
+ severity: "critical"
878
+ category: "weak-hash"
879
+ description: "비밀번호 해시 시 솔트를 사용해야 합니다."
880
+ enabled: true
881
+ pattern:
882
+ type: "method-analysis"
883
+ conditions:
884
+ - "password-hash-without-salt"
885
+ custom:
886
+ rule_id: "SEC-HASH-001"
887
+
888
+ # 2.15 무결성 검사 없는 코드 다운로드
889
+ - id: "secure-dl-001"
890
+ name: "다운로드 코드 무결성 검증"
891
+ severity: "critical"
892
+ category: "code-integrity"
893
+ description: "URLClassLoader 사용 시 체크섬 검증이 필요합니다."
894
+ enabled: true
895
+ pattern:
896
+ type: "ast-filtered-regex"
897
+ regex: "new\\s+URLClassLoader\\s*\\("
898
+ custom:
899
+ rule_id: "SEC-DL-001"
900
+
901
+ # 2.16 반복된 인증시도 제한 기능 부재
902
+ - id: "secure-brute-001"
903
+ name: "로그인 시도 횟수 제한"
904
+ severity: "critical"
905
+ category: "brute-force"
906
+ description: "로그인 실패 시 시도 횟수 제한/계정 잠금이 필요합니다."
907
+ enabled: true
908
+ pattern:
909
+ type: "method-analysis"
910
+ conditions:
911
+ - "login-without-attempt-limit"
912
+ custom:
913
+ rule_id: "SEC-BRUTE-001"
914
+
915
+ # ==================== 3. 시간 및 상태 ====================
916
+
917
+ - id: "secure-race-001"
918
+ name: "공유자원 동기화"
919
+ severity: "high"
920
+ category: "race-condition"
921
+ description: "멀티스레드 환경에서 공유자원 접근 시 synchronized가 필요합니다."
922
+ enabled: true
923
+ pattern:
924
+ type: "method-analysis"
925
+ conditions:
926
+ - "shared-resource-without-synchronization"
927
+ custom:
928
+ rule_id: "SEC-RACE-001"
929
+
930
+ # ==================== 4. 에러처리 ====================
931
+
932
+ - id: "secure-err-001"
933
+ name: "printStackTrace 금지"
934
+ severity: "high"
935
+ category: "error-handling"
936
+ description: "printStackTrace()는 시스템 정보를 노출할 수 있습니다."
937
+ enabled: true
938
+ pattern:
939
+ type: "method-analysis"
940
+ conditions:
941
+ - "print-stack-trace-prohibited"
942
+ custom:
943
+ rule_id: "SEC-ERR-001"
944
+ fix: "로거 사용"
945
+
946
+ - id: "secure-err-002"
947
+ name: "예외 메시지 노출 금지"
948
+ severity: "high"
949
+ category: "error-handling"
950
+ description: "e.getMessage()를 응답에 직접 포함하면 정보가 노출됩니다."
951
+ enabled: true
952
+ pattern:
953
+ type: "ast-filtered-regex"
954
+ regex: "(response|out)\\.[^;]*getMessage\\s*\\(\\s*\\)"
955
+ custom:
956
+ rule_id: "SEC-ERR-002"
957
+
958
+ - id: "secure-err-003"
959
+ name: "스택 트레이스 노출 금지"
960
+ severity: "high"
961
+ category: "error-handling"
962
+ description: "스택 트레이스를 응답에 포함하면 시스템 정보가 노출됩니다."
963
+ enabled: true
964
+ pattern:
965
+ type: "ast-filtered-regex"
966
+ regex: "(response|out)\\.[^;]*getStackTrace\\s*\\(\\s*\\)"
967
+ custom:
968
+ rule_id: "SEC-ERR-003"
969
+
970
+ - id: "secure-err-005"
971
+ name: "광범위한 Exception 처리 지양"
972
+ severity: "low"
973
+ category: "error-handling"
974
+ description: "Exception을 직접 catch하는 것보다 구체적인 예외를 처리하세요."
975
+ enabled: true
976
+ pattern:
977
+ type: "ast-filtered-regex"
978
+ regex: "catch\\s*\\(\\s*Exception\\s+\\w+\\s*\\)"
979
+ custom:
980
+ rule_id: "SEC-ERR-005"
981
+
982
+ # ==================== 5. 코드오류 ====================
983
+
984
+ - id: "secure-npe-001"
985
+ name: "Null 검사 필수"
986
+ severity: "high"
987
+ category: "null-pointer"
988
+ description: "request.getParameter() 결과는 null 검사가 필요합니다."
989
+ enabled: true
990
+ pattern:
991
+ type: "method-analysis"
992
+ conditions:
993
+ - "getparameter-without-null-check"
994
+ custom:
995
+ rule_id: "SEC-NPE-001"
996
+
997
+ - id: "secure-res-001"
998
+ name: "자원 해제 필수"
999
+ severity: "high"
1000
+ category: "resource-leak"
1001
+ description: "InputStream, Connection 등은 close()를 호출해야 합니다."
1002
+ enabled: true
1003
+ pattern:
1004
+ type: "method-analysis"
1005
+ conditions:
1006
+ - "resource-without-close"
1007
+ custom:
1008
+ rule_id: "SEC-RES-001"
1009
+
1010
+ - id: "secure-res-002"
1011
+ name: "try-with-resources 권장"
1012
+ severity: "low"
1013
+ category: "resource-leak"
1014
+ description: "자원 관리에 try-with-resources 사용을 권장합니다."
1015
+ enabled: true
1016
+ pattern:
1017
+ type: "method-analysis"
1018
+ conditions:
1019
+ - "resource-without-try-with-resources"
1020
+ custom:
1021
+ rule_id: "SEC-RES-002"
1022
+
1023
+ - id: "secure-deser-001"
1024
+ name: "역직렬화 검증"
1025
+ severity: "critical"
1026
+ category: "deserialization"
1027
+ description: "ObjectInputStream.readObject() 사용 시 검증이 필요합니다."
1028
+ enabled: true
1029
+ pattern:
1030
+ type: "ast-method-call"
1031
+ method: "^readObject$"
1032
+ custom:
1033
+ rule_id: "SEC-DESER-001"
1034
+
1035
+ # ==================== 6. 캡슐화 ====================
1036
+
1037
+ - id: "secure-session-001"
1038
+ name: "Controller 멤버 변수 금지"
1039
+ severity: "critical"
1040
+ category: "encapsulation"
1041
+ description: "@Controller 클래스에 멤버 필드 선언은 세션 간 데이터 공유 위험이 있습니다."
1042
+ enabled: false
1043
+ pattern:
1044
+ type: "regex"
1045
+ regex: "@Controller[^{]*\\{[^}]*private\\s+(?!static|final)[^;]+;"
1046
+ custom:
1047
+ rule_id: "SEC-SESSION-001"
1048
+
1049
+ - id: "secure-session-002"
1050
+ name: "Servlet 멤버 변수 금지"
1051
+ severity: "critical"
1052
+ category: "encapsulation"
1053
+ description: "Servlet 클래스에 멤버 필드 선언은 스레드 안전 문제를 유발합니다."
1054
+ enabled: true
1055
+ pattern:
1056
+ type: "regex"
1057
+ regex: "extends\\s+HttpServlet[^{]*\\{[^}]*private\\s+(?!static|final)[^;]+;"
1058
+ custom:
1059
+ rule_id: "SEC-SESSION-002"
1060
+
1061
+ - id: "secure-array-001"
1062
+ name: "private 배열 직접 반환 금지"
1063
+ severity: "low"
1064
+ category: "encapsulation"
1065
+ description: "private 배열을 직접 반환하면 외부에서 수정할 수 있습니다. VO/DTO 클래스는 제외됩니다."
1066
+ enabled: true
1067
+ pattern:
1068
+ type: "method-analysis"
1069
+ conditions:
1070
+ - "private-array-direct-return"
1071
+ custom:
1072
+ rule_id: "SEC-ARRAY-001"
1073
+
1074
+ - id: "secure-array-002"
1075
+ name: "외부 배열 직접 할당 금지"
1076
+ severity: "low"
1077
+ category: "encapsulation"
1078
+ description: "외부 배열을 private 필드에 직접 할당하면 외부에서 수정할 수 있습니다. VO/DTO 클래스는 제외됩니다."
1079
+ enabled: true
1080
+ pattern:
1081
+ type: "method-analysis"
1082
+ conditions:
1083
+ - "external-array-direct-assign"
1084
+ custom:
1085
+ rule_id: "SEC-ARRAY-002"
1086
+
1087
+ # ==================== 7. API 오용 ====================
1088
+
1089
+ - id: "secure-dns-001"
1090
+ name: "DNS 기반 보안결정 금지"
1091
+ severity: "high"
1092
+ category: "api-misuse"
1093
+ description: "getRemoteHost() 결과로 보안결정을 하면 DNS 스푸핑에 취약합니다."
1094
+ enabled: true
1095
+ pattern:
1096
+ type: "ast-method-call"
1097
+ method: "^getRemoteHost$"
1098
+ custom:
1099
+ rule_id: "SEC-DNS-001"
1100
+
1101
+ - id: "secure-api-001"
1102
+ name: "System.exit() 금지"
1103
+ severity: "critical"
1104
+ category: "api-misuse"
1105
+ description: "System.exit()는 웹 애플리케이션에서 사용하면 안 됩니다."
1106
+ enabled: true
1107
+ pattern:
1108
+ type: "ast-method-call"
1109
+ method: "^exit$"
1110
+ qualifier: "^System$"
1111
+ custom:
1112
+ rule_id: "SEC-API-001"
1113
+
1114
+ # ==================== CODE-RAY 갭 분석 추가 규칙 ====================
1115
+
1116
+ # ---- 신규 1: 잔존하는 디버그 코드 (CWE-489) ----
1117
+ - id: "secure-debug-001"
1118
+ name: "디버그 출력문 잔존"
1119
+ severity: "high"
1120
+ category: "debug-code"
1121
+ description: "System.out/err.print 문은 운영 환경에서 제거해야 합니다."
1122
+ enabled: true
1123
+ pattern:
1124
+ type: "ast-filtered-regex"
1125
+ regex: "System\\.(out|err)\\.(print|println|printf)\\s*\\("
1126
+ custom:
1127
+ rule_id: "SEC-DEBUG-001"
1128
+ cwe: "CWE-489"
1129
+ fix: "Logger를 사용하세요"
1130
+
1131
+ - id: "secure-debug-002"
1132
+ name: "디버그 플래그 하드코딩"
1133
+ severity: "medium"
1134
+ category: "debug-code"
1135
+ description: "디버그 모드 플래그가 true로 하드코딩되어 있습니다."
1136
+ enabled: true
1137
+ pattern:
1138
+ type: "ast-filtered-regex"
1139
+ regex: "(DEBUG|debugMode|isDebug|debug_mode)\\s*=\\s*true"
1140
+ custom:
1141
+ rule_id: "SEC-DEBUG-002"
1142
+ cwe: "CWE-489"
1143
+ fix: "운영 배포 시 false로 변경하거나 설정 파일로 분리하세요"
1144
+
1145
+ # ---- 신규 2: 취약 API 사용 확대 (CWE-676) ----
1146
+ - id: "secure-api-002"
1147
+ name: "Thread.stop() 사용 금지"
1148
+ severity: "critical"
1149
+ category: "api-misuse"
1150
+ description: "Thread.stop()은 deprecated이며 데이터 무결성을 훼손할 수 있습니다."
1151
+ enabled: true
1152
+ pattern:
1153
+ type: "ast-filtered-regex"
1154
+ regex: "\\.stop\\s*\\(\\s*\\)"
1155
+ custom:
1156
+ rule_id: "SEC-API-002"
1157
+ cwe: "CWE-676"
1158
+ fix: "interrupt()와 volatile 플래그를 사용하세요"
1159
+
1160
+ - id: "secure-api-003"
1161
+ name: "Thread.suspend/resume() 사용 금지"
1162
+ severity: "critical"
1163
+ category: "api-misuse"
1164
+ description: "Thread.suspend()/resume()은 교착상태를 유발할 수 있습니다."
1165
+ enabled: true
1166
+ pattern:
1167
+ type: "ast-filtered-regex"
1168
+ regex: "\\.(suspend|resume)\\s*\\(\\s*\\)"
1169
+ custom:
1170
+ rule_id: "SEC-API-003"
1171
+ cwe: "CWE-676"
1172
+ fix: "wait()/notify() 또는 Lock/Condition을 사용하세요"
1173
+
1174
+ - id: "secure-api-004"
1175
+ name: "Runtime.halt() 사용 금지"
1176
+ severity: "critical"
1177
+ category: "api-misuse"
1178
+ description: "Runtime.halt()는 shutdown hook을 실행하지 않으므로 위험합니다."
1179
+ enabled: true
1180
+ pattern:
1181
+ type: "ast-filtered-regex"
1182
+ regex: "Runtime\\.getRuntime\\(\\)\\.halt\\s*\\("
1183
+ custom:
1184
+ rule_id: "SEC-API-004"
1185
+ cwe: "CWE-676"
1186
+
1187
+ - id: "secure-api-005"
1188
+ name: "runFinalizersOnExit 사용 금지"
1189
+ severity: "critical"
1190
+ category: "api-misuse"
1191
+ description: "System.runFinalizersOnExit()은 deprecated이며 위험합니다."
1192
+ enabled: true
1193
+ pattern:
1194
+ type: "ast-filtered-regex"
1195
+ regex: "System\\.runFinalizersOnExit\\s*\\("
1196
+ custom:
1197
+ rule_id: "SEC-API-005"
1198
+ cwe: "CWE-676"
1199
+
1200
+ - id: "secure-api-006"
1201
+ name: "Thread.destroy() 사용 금지"
1202
+ severity: "critical"
1203
+ category: "api-misuse"
1204
+ description: "Thread.destroy()는 deprecated이며 교착상태를 유발합니다."
1205
+ enabled: true
1206
+ pattern:
1207
+ type: "ast-filtered-regex"
1208
+ regex: "\\.destroy\\s*\\(\\s*\\)"
1209
+ custom:
1210
+ rule_id: "SEC-API-006"
1211
+ cwe: "CWE-676"
1212
+
1213
+ # ---- 신규 3: 역직렬화 검증 확대 (CWE-502) ----
1214
+ - id: "secure-deser-002"
1215
+ name: "Jackson enableDefaultTyping 금지"
1216
+ severity: "critical"
1217
+ category: "deserialization"
1218
+ description: "ObjectMapper.enableDefaultTyping()은 임의 클래스 역직렬화를 허용합니다."
1219
+ enabled: true
1220
+ pattern:
1221
+ type: "ast-filtered-regex"
1222
+ regex: "\\.(enableDefaultTyping|activateDefaultTyping)\\s*\\("
1223
+ custom:
1224
+ rule_id: "SEC-DESER-002"
1225
+ cwe: "CWE-502"
1226
+ fix: "@JsonTypeInfo를 명시적으로 지정하세요"
1227
+
1228
+ - id: "secure-deser-003"
1229
+ name: "XMLDecoder 사용 금지"
1230
+ severity: "critical"
1231
+ category: "deserialization"
1232
+ description: "XMLDecoder는 임의 코드 실행이 가능하여 위험합니다."
1233
+ enabled: true
1234
+ pattern:
1235
+ type: "ast-filtered-regex"
1236
+ regex: "new\\s+XMLDecoder\\s*\\("
1237
+ custom:
1238
+ rule_id: "SEC-DESER-003"
1239
+ cwe: "CWE-502"
1240
+ fix: "JAXB 또는 Jackson XML을 사용하세요"
1241
+
1242
+ - id: "secure-deser-004"
1243
+ name: "SnakeYAML 안전하지 않은 load 사용"
1244
+ severity: "critical"
1245
+ category: "deserialization"
1246
+ description: "SnakeYAML의 yaml.load()는 임의 클래스를 인스턴스화할 수 있습니다."
1247
+ enabled: true
1248
+ pattern:
1249
+ type: "ast-filtered-regex"
1250
+ regex: "yaml\\.load\\s*\\([^)]*\\)"
1251
+ custom:
1252
+ rule_id: "SEC-DESER-004"
1253
+ cwe: "CWE-502"
1254
+ fix: "yaml.loadAs() 또는 SafeConstructor를 사용하세요"
1255
+
1256
+ # ---- 신규 4: 종료되지 않는 반복문/재귀 (CWE-835) ----
1257
+ - id: "secure-loop-001"
1258
+ name: "while(true) 무한 루프"
1259
+ severity: "high"
1260
+ category: "infinite-loop"
1261
+ description: "while(true) 사용 시 반드시 break/return 종료 조건이 필요합니다."
1262
+ enabled: true
1263
+ pattern:
1264
+ type: "method-analysis"
1265
+ conditions:
1266
+ - "while-true-without-break"
1267
+ custom:
1268
+ rule_id: "SEC-LOOP-001"
1269
+ cwe: "CWE-835"
1270
+
1271
+ - id: "secure-loop-002"
1272
+ name: "for(;;) 무한 루프"
1273
+ severity: "high"
1274
+ category: "infinite-loop"
1275
+ description: "for(;;) 사용 시 반드시 break/return 종료 조건이 필요합니다."
1276
+ enabled: true
1277
+ pattern:
1278
+ type: "ast-filtered-regex"
1279
+ regex: "for\\s*\\(\\s*;\\s*;\\s*\\)"
1280
+ custom:
1281
+ rule_id: "SEC-LOOP-002"
1282
+ cwe: "CWE-835"
1283
+
1284
+ - id: "secure-loop-003"
1285
+ name: "종료 조건 없는 재귀 호출"
1286
+ severity: "high"
1287
+ category: "infinite-loop"
1288
+ description: "재귀 호출 시 종료 조건(base case)이 필요합니다."
1289
+ enabled: true
1290
+ pattern:
1291
+ type: "method-analysis"
1292
+ conditions:
1293
+ - "recursive-without-base-case"
1294
+ custom:
1295
+ rule_id: "SEC-LOOP-003"
1296
+ cwe: "CWE-835"
1297
+
1298
+ # ---- 신규 5: 초기화되지 않은 변수 (CWE-457) ----
1299
+ - id: "secure-init-001"
1300
+ name: "지역 변수 조건부 초기화"
1301
+ severity: "medium"
1302
+ category: "uninitialized-variable"
1303
+ description: "지역 변수가 조건부 경로에서만 초기화되면 미초기화 상태로 사용될 수 있습니다."
1304
+ enabled: true
1305
+ pattern:
1306
+ type: "method-analysis"
1307
+ conditions:
1308
+ - "variable-conditional-init"
1309
+ custom:
1310
+ rule_id: "SEC-INIT-001"
1311
+ cwe: "CWE-457"
1312
+
1313
+ # ---- 신규 6: 해제된 자원 사용 (CWE-416) ----
1314
+ - id: "secure-uaf-001"
1315
+ name: "해제된 자원 사용"
1316
+ severity: "critical"
1317
+ category: "use-after-free"
1318
+ description: "close() 호출 후 자원을 재사용하면 예외가 발생합니다."
1319
+ enabled: true
1320
+ pattern:
1321
+ type: "method-analysis"
1322
+ conditions:
1323
+ - "resource-use-after-close"
1324
+ custom:
1325
+ rule_id: "SEC-UAF-001"
1326
+ cwe: "CWE-416"
1327
+
1328
+ # ---- 보강 1: 빈 catch 블록 (CWE-390) ----
1329
+ - id: "secure-err-004"
1330
+ name: "빈 catch 블록 금지"
1331
+ severity: "high"
1332
+ category: "error-handling"
1333
+ description: "catch 블록이 비어있으면 오류 상황에 대응하지 못합니다."
1334
+ enabled: true
1335
+ pattern:
1336
+ type: "ast-try-catch"
1337
+ custom:
1338
+ catch_is_empty: true
1339
+ rule_id: "SEC-ERR-004"
1340
+ cwe: "CWE-390"
1341
+ fix: "예외를 로깅하거나 적절히 처리하세요"
1342
+
1343
+ # ---- 보강 2: 주석 내 시스템 정보 노출 확대 ----
1344
+ - id: "secure-comment-003"
1345
+ name: "주석 내 IP 주소 노출"
1346
+ severity: "high"
1347
+ category: "info-exposure"
1348
+ description: "주석에 내부 IP 주소가 포함되어 있습니다."
1349
+ enabled: true
1350
+ pattern:
1351
+ type: "regex"
1352
+ regex: "//[^\\n]*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"
1353
+ custom:
1354
+ rule_id: "SEC-COMMENT-003"
1355
+ cwe: "CWE-615"
1356
+
1357
+ - id: "secure-comment-004"
1358
+ name: "주석 내 API 키 노출"
1359
+ severity: "critical"
1360
+ category: "info-exposure"
1361
+ description: "주석에 API 키 또는 토큰이 포함되어 있습니다."
1362
+ enabled: true
1363
+ pattern:
1364
+ type: "regex"
1365
+ regex: "//[^\\n]*(api[_-]?key|apikey|secret[_-]?key|access[_-]?key|token)\\s*[:=]\\s*[\"']?[A-Za-z0-9+/=_-]{10,}"
1366
+ flags: "i"
1367
+ custom:
1368
+ rule_id: "SEC-COMMENT-004"
1369
+ cwe: "CWE-615"
1370
+
1371
+ - id: "secure-comment-005"
1372
+ name: "주석 내 서버 경로 노출"
1373
+ severity: "medium"
1374
+ category: "info-exposure"
1375
+ description: "주석에 서버 파일 경로가 포함되어 있습니다."
1376
+ enabled: true
1377
+ pattern:
1378
+ type: "regex"
1379
+ regex: "//[^\\n]*(/home/[^\\s]+|/usr/[^\\s]+|/var/[^\\s]+|/opt/[^\\s]+|C:\\\\[^\\s]+|/WEB-INF/[^\\s]+)"
1380
+ custom:
1381
+ rule_id: "SEC-COMMENT-005"
1382
+ cwe: "CWE-615"
1383
+
1384
+ # ---- 보강 3: 리플렉션 코드 삽입 ----
1385
+ - id: "secure-code-004"
1386
+ name: "외부입력 Class.forName() 금지"
1387
+ severity: "critical"
1388
+ category: "code-injection"
1389
+ description: "외부 입력값으로 Class.forName()을 호출하면 임의 클래스 로딩이 가능합니다."
1390
+ enabled: true
1391
+ pattern:
1392
+ type: "ast-filtered-regex"
1393
+ regex: "Class\\.forName\\s*\\(\\s*[a-zA-Z_]\\w*\\s*\\)"
1394
+ custom:
1395
+ rule_id: "SEC-CODE-004"
1396
+ cwe: "CWE-470"
1397
+ fix: "화이트리스트로 허용 클래스를 제한하세요"
1398
+
1399
+ - id: "secure-code-005"
1400
+ name: "외부입력 Method.invoke() 금지"
1401
+ severity: "critical"
1402
+ category: "code-injection"
1403
+ description: "외부 입력값으로 Method.invoke()를 호출하면 임의 메서드 실행이 가능합니다."
1404
+ enabled: true
1405
+ pattern:
1406
+ type: "ast-filtered-regex"
1407
+ regex: "getMethod\\s*\\(\\s*[a-zA-Z_]\\w*\\s*\\)"
1408
+ custom:
1409
+ rule_id: "SEC-CODE-005"
1410
+ cwe: "CWE-470"
1411
+ fix: "화이트리스트로 허용 메서드를 제한하세요"
1412
+
1413
+ # ---- 보강 4: JWT 서명 검증 ----
1414
+ - id: "secure-sign-002"
1415
+ name: "JWT 서명 미검증"
1416
+ severity: "critical"
1417
+ category: "signature-verification"
1418
+ description: "JWT 토큰 파싱 시 서명 검증이 필요합니다."
1419
+ enabled: true
1420
+ pattern:
1421
+ type: "ast-filtered-regex"
1422
+ regex: "parseClaimsJwt\\s*\\(|Jwts\\.parser\\(\\)\\s*\\.\\s*parse\\s*\\([^)]*\\)(?![\\s\\S]*setSigningKey)"
1423
+ custom:
1424
+ rule_id: "SEC-SIGN-002"
1425
+ cwe: "CWE-347"
1426
+ fix: "setSigningKey()로 서명 검증을 포함하고 parseClaimsJws()를 사용하세요"
1427
+
1428
+ # ---- 보강 5: 정수 오버플로우 산술연산 ----
1429
+ - id: "secure-int-002"
1430
+ name: "외부입력 산술연산 오버플로우"
1431
+ severity: "high"
1432
+ category: "integer-overflow"
1433
+ description: "외부 입력값의 산술 연산 시 오버플로우 검증이 필요합니다."
1434
+ enabled: true
1435
+ pattern:
1436
+ type: "method-analysis"
1437
+ conditions:
1438
+ - "external-input-arithmetic-overflow"
1439
+ custom:
1440
+ rule_id: "SEC-INT-002"
1441
+ cwe: "CWE-190"
1442
+ fix: "Math.multiplyExact()/addExact() 또는 BigInteger를 사용하세요"
1443
+
1444
+ # ---- 보강 6: Null 검사 범위 확대 ----
1445
+ - id: "secure-npe-002"
1446
+ name: "findById 반환값 Null 검사"
1447
+ severity: "high"
1448
+ category: "null-pointer"
1449
+ description: "Repository.findById() 등의 반환값은 null 검사가 필요합니다."
1450
+ enabled: true
1451
+ pattern:
1452
+ type: "method-analysis"
1453
+ conditions:
1454
+ - "find-method-without-null-check"
1455
+ custom:
1456
+ rule_id: "SEC-NPE-002"
1457
+ cwe: "CWE-476"
1458
+
1459
+ # ---- 보강 7: 시스템 데이터 정보 노출 ----
1460
+ - id: "secure-sysinfo-001"
1461
+ name: "시스템 프로퍼티 응답 노출"
1462
+ severity: "high"
1463
+ category: "system-info-exposure"
1464
+ description: "System.getProperty()를 HTTP 응답에 포함하면 시스템 정보가 노출됩니다."
1465
+ enabled: true
1466
+ pattern:
1467
+ type: "ast-filtered-regex"
1468
+ regex: "(response|out)\\.[^;]*System\\.getProperty\\s*\\("
1469
+ custom:
1470
+ rule_id: "SEC-SYSINFO-001"
1471
+ cwe: "CWE-497"
1472
+ fix: "시스템 정보를 응답에 포함하지 마세요"
1473
+
1474
+ - id: "secure-sysinfo-002"
1475
+ name: "환경변수 응답 노출"
1476
+ severity: "critical"
1477
+ category: "system-info-exposure"
1478
+ description: "System.getenv()를 HTTP 응답에 포함하면 환경변수가 노출됩니다."
1479
+ enabled: true
1480
+ pattern:
1481
+ type: "ast-filtered-regex"
1482
+ regex: "(response|out)\\.[^;]*System\\.getenv\\s*\\("
1483
+ custom:
1484
+ rule_id: "SEC-SYSINFO-002"
1485
+ cwe: "CWE-497"
1486
+ fix: "환경변수를 응답에 포함하지 마세요"
1487
+
1488
+ # ---- spo 분석 보강: 문자열 연결 SQL Injection ----
1489
+ - id: "secure-sqli-concat-001"
1490
+ name: "문자열 연결을 통한 SQL 생성"
1491
+ severity: "critical"
1492
+ category: "sql-injection"
1493
+ description: "SQL 절에 문자열 연결(+)으로 변수를 삽입하면 SQL Injection 위험이 있습니다."
1494
+ enabled: true
1495
+ pattern:
1496
+ type: "regex"
1497
+ regex: "(WHERE|AND|OR)\\s*[^\"]*\"\\s*\\+\\s*\\w+"
1498
+ custom:
1499
+ rule_id: "SEC-SQLI-CONCAT-001"
1500
+ cwe: "CWE-89"
1501
+ fix: "PreparedStatement 또는 MyBatis #{} 바인드 변수를 사용하세요"
1502
+
1503
+ # ---- spo 분석 보강: hex 인코딩 하드코딩 키/토큰 ----
1504
+ - id: "secure-hard-hex-001"
1505
+ name: "하드코딩된 hex 인코딩 키/토큰"
1506
+ severity: "high"
1507
+ category: "hardcoded-credential"
1508
+ description: "32자 이상의 hex 인코딩 문자열이 변수에 할당되어 있습니다. 하드코딩된 키/토큰일 수 있습니다."
1509
+ enabled: true
1510
+ pattern:
1511
+ type: "regex"
1512
+ regex: "(key|token|secret|siteKey|apiKey|authKey|encKey|password|passwd|credential)\\s*=\\s*\"[0-9a-fA-F]{32,}\""
1513
+ custom:
1514
+ rule_id: "SEC-HARD-HEX-001"
1515
+ cwe: "CWE-798"
1516
+ fix: "외부 설정 파일이나 환경 변수를 사용하세요"
1517
+
1518
+ # ---- spo 분석 보강: 주석 처리된 보안 코드 ----
1519
+ - id: "secure-commented-auth-001"
1520
+ name: "주석 처리된 인증/인가 코드"
1521
+ severity: "high"
1522
+ category: "authentication"
1523
+ description: "인증/인가 관련 코드가 주석 처리되어 있습니다. 보안 검증이 우회될 수 있습니다."
1524
+ enabled: true
1525
+ pattern:
1526
+ type: "regex"
1527
+ regex: "//\\s*(if\\s*\\(\\s*(loginVO|session|userInfo|loginInfo|authInfo)\\s*(==|!=)\\s*null)"
1528
+ custom:
1529
+ rule_id: "SEC-COMMENT-AUTH-001"
1530
+ cwe: "CWE-287"
1531
+ fix: "주석 처리된 인증/인가 코드를 확인하고, 필요하면 복원하세요"
1532
+
1533
+ # ---- spo 분석 보강: 인증 없는 API 엔드포인트 ----
1534
+ - id: "secure-api-noauth-001"
1535
+ name: "인증 없는 API 엔드포인트"
1536
+ severity: "medium"
1537
+ category: "authentication"
1538
+ description: "@ResponseBody API 엔드포인트에 인증/세션 검증이 없습니다. (프론트 분리 환경에서 중요)"
1539
+ enabled: true
1540
+ pattern:
1541
+ type: "method-analysis"
1542
+ conditions:
1543
+ - "api-endpoint-without-auth"
1544
+ custom:
1545
+ rule_id: "SEC-API-NOAUTH-001"
1546
+ cwe: "CWE-306"
1547
+ fix: "API 엔드포인트에 인증/인가 검증을 추가하세요"
1548
+
1549
+ # ==================== 8. CWE Top 25 갭 보완 ====================
1550
+
1551
+ # 8.1 잘못된 권한 검증 - IDOR (CWE-863, CWE-639)
1552
+ - id: "secure-idor-001"
1553
+ name: "PathVariable ID 직접 DB 조회"
1554
+ severity: "medium"
1555
+ category: "authorization"
1556
+ description: "@PathVariable로 받은 ID를 findById/selectById로 직접 조회하면 타 사용자 리소스에 접근할 수 있습니다. 세션 사용자와 리소스 소유자 일치 여부를 검증하세요."
1557
+ enabled: true
1558
+ tags:
1559
+ - "CWE-639"
1560
+ - "idor"
1561
+ pattern:
1562
+ type: "regex-multiline"
1563
+ regex: "@PathVariable[^)]*\\)\\s+\\w+\\s+(\\w+)[\\s\\S]{0,500}(findById|selectById|getById)\\s*\\(\\s*\\1\\s*\\)"
1564
+ custom:
1565
+ rule_id: "SEC-IDOR-001"
1566
+ cwe: "CWE-639"
1567
+ fix: "리소스 조회 후 세션 사용자와 소유자 일치 여부를 반드시 검증하세요"
1568
+
1569
+ # 8.2 민감정보 노출 (CWE-200)
1570
+ - id: "secure-info-001"
1571
+ name: "예외 응답에 내부 정보 노출"
1572
+ severity: "medium"
1573
+ category: "information-exposure"
1574
+ description: "@ExceptionHandler에서 예외 메시지(e.getMessage(), e.toString())를 클라이언트 응답에 직접 포함하면 내부 구조가 노출됩니다."
1575
+ enabled: true
1576
+ tags:
1577
+ - "CWE-200"
1578
+ - "information-exposure"
1579
+ pattern:
1580
+ type: "regex-multiline"
1581
+ regex: "@ExceptionHandler[\\s\\S]{0,500}(e\\.getMessage\\(\\)|e\\.toString\\(\\)|exception\\.getMessage\\(\\)|ex\\.getMessage\\(\\))"
1582
+ custom:
1583
+ rule_id: "SEC-INFO-001"
1584
+ cwe: "CWE-200"
1585
+ fix: "클라이언트에는 일반적인 에러 메시지만 반환하고, 상세 예외는 서버 로그에만 기록하세요"
1586
+
1587
+ - id: "secure-info-002"
1588
+ name: "Spring Boot 에러 상세 노출 설정"
1589
+ severity: "medium"
1590
+ category: "information-exposure"
1591
+ description: "server.error.include-stacktrace=always 또는 include-message=always 설정은 에러 응답에 스택트레이스/메시지를 포함시켜 내부 정보가 노출됩니다."
1592
+ enabled: true
1593
+ tags:
1594
+ - "CWE-200"
1595
+ - "information-exposure"
1596
+ pattern:
1597
+ type: "regex"
1598
+ regex: "server\\.error\\.include-(stacktrace|message|binding-errors)\\s*=\\s*always"
1599
+ custom:
1600
+ rule_id: "SEC-INFO-002"
1601
+ cwe: "CWE-200"
1602
+ fix: "운영 환경에서는 server.error.include-stacktrace=never로 설정하세요"
1603
+
1604
+ # 8.3 리소스 제한 없음 (CWE-770)
1605
+ - id: "secure-dos-001"
1606
+ name: "파일 업로드 크기 검증 필요"
1607
+ severity: "low"
1608
+ category: "resource-exhaustion"
1609
+ description: "MultipartFile을 파라미터로 받는 경우 파일 크기 검증이 필요합니다. getSize()로 크기를 확인하거나 spring.servlet.multipart.max-file-size를 설정하세요."
1610
+ enabled: true
1611
+ tags:
1612
+ - "CWE-770"
1613
+ - "dos"
1614
+ pattern:
1615
+ type: "ast-filtered-regex"
1616
+ regex: "MultipartFile\\b"
1617
+ exclude:
1618
+ - "^\\s*import\\s"
1619
+ - "\\bget[A-Z]\\w*\\s*\\("
1620
+ - "\\bset[A-Z]\\w*\\s*\\("
1621
+ custom:
1622
+ rule_id: "SEC-DOS-001"
1623
+ cwe: "CWE-770"
1624
+ fix: "file.getSize()로 크기를 검증하거나 spring.servlet.multipart.max-file-size를 설정하세요"
1625
+
1626
+ - language: javascript
1627
+ rules:
1628
+ # jQuery DOM 조작 XSS
1629
+ - id: "secure-xss-jquery-001"
1630
+ name: "jQuery DOM 조작 XSS 위험"
1631
+ severity: "high"
1632
+ category: "xss"
1633
+ description: ".html(), .append() 등 jQuery DOM 조작에 사용자 입력값을 삽입하면 XSS에 취약합니다."
1634
+ enabled: true
1635
+ pattern:
1636
+ type: "regex"
1637
+ regex: "\\.(html|append|prepend|after|before|replaceWith)\\s*\\([^)]*[+]"
1638
+ custom:
1639
+ rule_id: "SEC-XSS-JQUERY-001"
1640
+ fix: ".text()를 사용하거나, 입력값을 이스케이핑 후 삽입하세요"
1641
+
1642
+ - id: "secure-xss-jquery-002"
1643
+ name: "jQuery .html() 직접 변수 삽입"
1644
+ severity: "high"
1645
+ category: "xss"
1646
+ description: ".html()에 변수를 직접 삽입하면 XSS에 취약합니다."
1647
+ enabled: true
1648
+ pattern:
1649
+ type: "regex"
1650
+ regex: "\\$\\s*\\([^)]+\\)\\s*\\.\\s*html\\s*\\(\\s*[a-zA-Z_$]"
1651
+ custom:
1652
+ rule_id: "SEC-XSS-JQUERY-002"
1653
+ fix: ".text()를 사용하거나, DOMPurify 등으로 sanitize하세요"
1654
+
1655
+ - language: html
1656
+ rules:
1657
+ # 인라인 이벤트 핸들러 (onclick 등)
1658
+ - id: "secure-xss-inline-001"
1659
+ name: "인라인 이벤트 핸들러 사용"
1660
+ severity: "medium"
1661
+ category: "xss"
1662
+ description: "onclick 등 인라인 이벤트 핸들러는 CSP 위반이며 XSS 위험이 있습니다."
1663
+ enabled: true
1664
+ pattern:
1665
+ type: "regex"
1666
+ regex: "\\s+on(click|change|submit|load|error|mouseover|keyup|keydown|focus|blur)\\s*="
1667
+ custom:
1668
+ rule_id: "SEC-XSS-INLINE-001"
1669
+ fix: "addEventListener()를 사용하세요"
1670
+
1671
+ - language: properties
1672
+ rules:
1673
+ # Spring Boot 에러 상세 노출 설정 (CWE-200)
1674
+ - id: "secure-info-props-001"
1675
+ name: "에러 상세 노출 설정"
1676
+ severity: "medium"
1677
+ category: "information-exposure"
1678
+ description: "server.error.include-stacktrace=always 설정은 운영 환경에서 내부 정보를 노출합니다."
1679
+ enabled: true
1680
+ tags:
1681
+ - "CWE-200"
1682
+ pattern:
1683
+ type: "regex"
1684
+ regex: "server\\.error\\.include-(stacktrace|message|binding-errors)\\s*=\\s*always"
1685
+ custom:
1686
+ rule_id: "SEC-INFO-PROPS-001"
1687
+ cwe: "CWE-200"
1688
+ fix: "운영 환경에서는 never로 설정하세요"