@@ -128,6 +128,11 @@ object LogFileManager {
128128
129129 /* *
130130 * Exports all logs as a zip file and returns a share intent.
131+ *
132+ * Sensitive data is sanitized before export:
133+ * - URLs are partially masked
134+ * - Profile names are masked
135+ * - App lists are removed
131136 *
132137 * @param context Context for file operations
133138 * @return Share intent for the exported zip file, or null if export failed
@@ -147,12 +152,11 @@ object LogFileManager {
147152 zipOut.write(deviceInfo.toByteArray())
148153 zipOut.closeEntry()
149154
150- // Add log files
155+ // Add log files with sanitization
151156 getLogFiles().forEach { logFile ->
152157 zipOut.putNextEntry(ZipEntry (logFile.name))
153- logFile.inputStream().use { input ->
154- input.copyTo(zipOut)
155- }
158+ val sanitizedContent = sanitizeLogContent(logFile.readText())
159+ zipOut.write(sanitizedContent.toByteArray())
156160 zipOut.closeEntry()
157161 }
158162 }
@@ -175,6 +179,169 @@ object LogFileManager {
175179 null
176180 }
177181 }
182+
183+ /* *
184+ * Sanitizes log content by masking sensitive data.
185+ *
186+ * Masked data includes:
187+ * - URLs (keeps protocol and TLD only)
188+ * - Profile names
189+ * - Task descriptions
190+ * - App names and package names
191+ * - Model thinking and action content
192+ * - App list entries (removed entirely)
193+ *
194+ * @param content Raw log content
195+ * @return Sanitized log content
196+ */
197+ private fun sanitizeLogContent (content : String ): String {
198+ var sanitized = content
199+
200+ // ==================== Remove app list entries ====================
201+ // Match lines like " 信息 -> com.android.mms" or " ... and 118 more apps"
202+ sanitized = sanitized.replace(
203+ Regex (""" (?m)^.*\[INFO\] AutoGLM/MainActivity:\s{2,}.*(?:->|more apps).*$\n?""" ),
204+ " "
205+ )
206+ sanitized = sanitized.replace(
207+ Regex (""" (?m)^.*=== All Launchable Apps:.*$\n?""" ),
208+ " "
209+ )
210+ sanitized = sanitized.replace(
211+ Regex (""" (?m)^.*=== End of App List ===.*$\n?""" ),
212+ " "
213+ )
214+
215+ // ==================== Mask URLs ====================
216+ // Completely mask all URLs
217+ sanitized = sanitized.replace(
218+ Regex (""" https?://[^\s]+""" , RegexOption .IGNORE_CASE )
219+ ) { " ***" }
220+
221+ // ==================== Mask profile/config names ====================
222+ sanitized = sanitized.replace(
223+ Regex (""" (Saving profile: id=\S+, name=)([^\n]+)""" )
224+ ) { " ${it.groupValues[1 ]} ***" }
225+
226+ sanitized = sanitized.replace(
227+ Regex (""" (Saving current configuration as profile: )([^\n]+)""" )
228+ ) { " ${it.groupValues[1 ]} ***" }
229+
230+ sanitized = sanitized.replace(
231+ Regex (""" (Saving model configuration: baseUrl=)([^,]+)(, modelName=)([^\n]+)""" )
232+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} ***" }
233+
234+ sanitized = sanitized.replace(
235+ Regex (""" (Testing connection to: )([^\n]+)""" )
236+ ) { " ${it.groupValues[1 ]} ***" }
237+
238+ sanitized = sanitized.replace(
239+ Regex (""" (Imported dev profile: )([^\n]+)""" )
240+ ) { " ${it.groupValues[1 ]} ***" }
241+
242+ sanitized = sanitized.replace(
243+ Regex (""" (Saving task template: id=\S+, name=)([^\n]+)""" )
244+ ) { " ${it.groupValues[1 ]} ***" }
245+
246+ // ==================== Mask task descriptions ====================
247+ // PhoneAgent: "Task started: xxx"
248+ sanitized = sanitized.replace(
249+ Regex (""" (Task started: )([^\n]+)""" )
250+ ) { " ${it.groupValues[1 ]} ***" }
251+
252+ // PhoneAgent: "Step N: xxx"
253+ sanitized = sanitized.replace(
254+ Regex (""" (Step \d+: )([^\n]+)""" )
255+ ) { " ${it.groupValues[1 ]} ***" }
256+
257+ // MainActivity: "Starting task: xxx" or "Starting task from floating window: xxx"
258+ sanitized = sanitized.replace(
259+ Regex (""" (Starting task(?:\s+from floating window)?: )([^\n]+)""" )
260+ ) { " ${it.groupValues[1 ]} ***" }
261+
262+ // ==================== Mask model thinking and action ====================
263+ sanitized = sanitized.replace(
264+ Regex (""" (Thinking: )([^\n]+)""" )
265+ ) { " ${it.groupValues[1 ]} ***" }
266+
267+ sanitized = sanitized.replace(
268+ Regex (""" (Action: )([^\n]+)""" )
269+ ) { " ${it.groupValues[1 ]} ***" }
270+
271+ sanitized = sanitized.replace(
272+ Regex (""" (Parsing response: )([^\n]+)""" )
273+ ) { " ${it.groupValues[1 ]} ***" }
274+
275+ sanitized = sanitized.replace(
276+ Regex (""" (Unknown action format: )([^\n]+)""" )
277+ ) { " ${it.groupValues[1 ]} ***" }
278+
279+ sanitized = sanitized.replace(
280+ Regex (""" (Parsing error: [^,]+, input: )([^\n]+)""" )
281+ ) { " ${it.groupValues[1 ]} ***" }
282+
283+ // ==================== Mask app names and package names ====================
284+ // AppResolver logs
285+ sanitized = sanitized.replace(
286+ Regex (""" (resolvePackageName called with: ')([^']+)(')""" )
287+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
288+
289+ sanitized = sanitized.replace(
290+ Regex (""" (Normalized query: ')([^']+)(')""" )
291+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
292+
293+ sanitized = sanitized.replace(
294+ Regex (""" (Found as package name: )([^\n]+)""" )
295+ ) { " ${it.groupValues[1 ]} ***" }
296+
297+ sanitized = sanitized.replace(
298+ Regex (""" (App: ')([^']+)(' -> )([^\n]+)""" )
299+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} ***" }
300+
301+ sanitized = sanitized.replace(
302+ Regex (""" (Similarity ')([^']+)(':.*)""" )
303+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
304+
305+ sanitized = sanitized.replace(
306+ Regex (""" (Best match: ')([^']+)(' \()([^)]+)(\).*)""" )
307+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} ***${it.groupValues[5 ]} " }
308+
309+ sanitized = sanitized.replace(
310+ Regex (""" (No match found for ')([^']+)(')""" )
311+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
312+
313+ // ActionHandler logs
314+ sanitized = sanitized.replace(
315+ Regex (""" (Launching app: )([^\n]+)""" )
316+ ) { " ${it.groupValues[1 ]} ***" }
317+
318+ sanitized = sanitized.replace(
319+ Regex (""" (Using package name directly: )([^\n]+)""" )
320+ ) { " ${it.groupValues[1 ]} ***" }
321+
322+ sanitized = sanitized.replace(
323+ Regex (""" (Resolving app name: )([^\n]+)""" )
324+ ) { " ${it.groupValues[1 ]} ***" }
325+
326+ sanitized = sanitized.replace(
327+ Regex (""" (Launching package: )([^\n]+)""" )
328+ ) { " ${it.groupValues[1 ]} ***" }
329+
330+ sanitized = sanitized.replace(
331+ Regex (""" (Launch failed for )([^:]+)(:.*)""" )
332+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
333+
334+ sanitized = sanitized.replace(
335+ Regex (""" (Package not found for ')([^']+)('.*)""" )
336+ ) { " ${it.groupValues[1 ]} ***${it.groupValues[3 ]} " }
337+
338+ // ErrorHandler: App not found
339+ sanitized = sanitized.replace(
340+ Regex (""" (App not found: )([^\n]+)""" )
341+ ) { " ${it.groupValues[1 ]} ***" }
342+
343+ return sanitized
344+ }
178345
179346 /* *
180347 * Cleans up log files older than the specified number of days.
0 commit comments