Skip to content

Commit e589936

Browse files
committed
feat(security): Add sensitive data sanitization for log export
- Remove app list logging from MainActivity startup - Add sanitizeLogContent() to mask sensitive data before export: - URLs completely masked - Profile/config names masked - Task descriptions masked - Model thinking/action masked - App names/package names masked - Add logging reference documentation - Prevent agent reinitialization during active tasks Bump version to 0.0.4
1 parent be40883 commit e589936

File tree

5 files changed

+660
-43
lines changed

5 files changed

+660
-43
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId = "com.kevinluo.autoglm"
1313
minSdk = 24
1414
targetSdk = 34
15-
versionCode = 3
16-
versionName = "0.0.3"
15+
versionCode = 4
16+
versionName = "0.0.4"
1717

1818
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1919
}

app/src/main/java/com/kevinluo/autoglm/ComponentManager.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,25 @@ class ComponentManager private constructor(private val context: Context) {
235235
/**
236236
* Reinitializes the PhoneAgent with updated configuration.
237237
* Call this after settings have been changed.
238+
*
239+
* Note: This will NOT reinitialize if a task is currently running or paused,
240+
* to prevent accidentally cancelling user tasks.
238241
*/
239242
fun reinitializeAgent() {
240243
if (userService == null) {
241244
Logger.w(TAG, "Cannot reinitialize agent: UserService not connected")
242245
return
243246
}
244247

245-
// Cancel any running task
248+
// Safety check: don't reinitialize while a task is active
249+
_phoneAgent?.let { agent ->
250+
if (agent.isRunning() || agent.isPaused()) {
251+
Logger.w(TAG, "Cannot reinitialize agent: task is currently active (state: ${agent.getState()})")
252+
return
253+
}
254+
}
255+
256+
// Cancel any running task (should be IDLE at this point, but just in case)
246257
_phoneAgent?.cancel()
247258

248259
// Recreate model client with new config

app/src/main/java/com/kevinluo/autoglm/MainActivity.kt

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,6 @@ class MainActivity : AppCompatActivity(), PhoneAgentListener {
175175
componentManager = ComponentManager.getInstance(this)
176176
Logger.i(TAG, "ComponentManager initialized")
177177

178-
// Log all launchable apps at startup
179-
logAllLaunchableApps()
180-
181178
initViews()
182179
setupListeners()
183180
setupShizukuListeners()
@@ -188,38 +185,6 @@ class MainActivity : AppCompatActivity(), PhoneAgentListener {
188185
updateTaskStatus(TaskStatus.IDLE)
189186
}
190187

191-
/**
192-
* Logs all launchable apps for debugging purposes.
193-
*
194-
* Queries the package manager for all apps with launcher activities
195-
* and logs them for debugging. Only logs the first 20 apps to avoid
196-
* excessive log output.
197-
*/
198-
private fun logAllLaunchableApps() {
199-
// Query apps without loading icons to avoid excessive logging
200-
val intent = android.content.Intent(android.content.Intent.ACTION_MAIN).apply {
201-
addCategory(android.content.Intent.CATEGORY_LAUNCHER)
202-
}
203-
val resolveInfoList = packageManager.queryIntentActivities(intent, 0)
204-
205-
val apps = resolveInfoList.mapNotNull { resolveInfo ->
206-
val activityInfo = resolveInfo.activityInfo ?: return@mapNotNull null
207-
val displayName = resolveInfo.loadLabel(packageManager)?.toString() ?: return@mapNotNull null
208-
val packageName = activityInfo.packageName ?: return@mapNotNull null
209-
displayName to packageName
210-
}.distinctBy { it.second }
211-
212-
Logger.i(TAG, "=== All Launchable Apps: ${apps.size} total ===")
213-
// Only log first 20 apps to avoid log quota
214-
apps.take(20).forEach { (name, pkg) ->
215-
Logger.i(TAG, " $name -> $pkg")
216-
}
217-
if (apps.size > 20) {
218-
Logger.i(TAG, " ... and ${apps.size - 20} more apps")
219-
}
220-
Logger.i(TAG, "=== End of App List ===")
221-
}
222-
223188
/**
224189
* Updates the overlay permission status display.
225190
*
@@ -299,7 +264,12 @@ class MainActivity : AppCompatActivity(), PhoneAgentListener {
299264
// Only reinitialize if service is connected and we need to refresh
300265
if (componentManager.isServiceConnected) {
301266
// Check if settings actually changed before reinitializing
302-
if (componentManager.settingsManager.hasConfigChanged()) {
267+
// But NEVER reinitialize while a task is running or paused - this would cancel the task!
268+
val isTaskActive = componentManager.phoneAgent?.let {
269+
it.isRunning() || it.isPaused()
270+
} ?: false
271+
272+
if (!isTaskActive && componentManager.settingsManager.hasConfigChanged()) {
303273
componentManager.reinitializeAgent()
304274
}
305275
componentManager.setPhoneAgentListener(this)

app/src/main/java/com/kevinluo/autoglm/util/LogFileManager.kt

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)