Skip to content

Commit 901e584

Browse files
committed
Scrub names from messages in logging/telemetry
1 parent e676cea commit 901e584

File tree

4 files changed

+154
-8
lines changed

4 files changed

+154
-8
lines changed

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCache.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import software.aws.toolkits.core.utils.touch
3232
import software.aws.toolkits.core.utils.tryDirOp
3333
import software.aws.toolkits.core.utils.tryFileOp
3434
import software.aws.toolkits.core.utils.tryOrNull
35+
import software.aws.toolkits.jetbrains.services.telemetry.scrubNames
3536
import software.aws.toolkits.telemetry.AuthTelemetry
3637
import software.aws.toolkits.telemetry.Result
3738
import java.io.InputStream
@@ -111,7 +112,7 @@ class DiskCache(
111112
source = "loadClientRegistration",
112113
result = Result.Failed,
113114
reason = "Failed to load Client Registration",
114-
reasonDesc = "Load Step:$stage failed. Unable to load file"
115+
reasonDesc = "Load Step:$stage failed. Cache file does not exist"
115116
)
116117
return null
117118
}
@@ -136,7 +137,7 @@ class DiskCache(
136137
source = "invalidateClientRegistration",
137138
result = Result.Failed,
138139
reason = "Failed to invalidate Client Registration",
139-
reasonDesc = e.message ?: e::class.java.name
140+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
140141
)
141142
throw e
142143
}
@@ -152,7 +153,7 @@ class DiskCache(
152153
source = "invalidateAccessToken",
153154
result = Result.Failed,
154155
reason = "Failed to invalidate Access Token",
155-
reasonDesc = e.message ?: e::class.java.name
156+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
156157
)
157158
throw e
158159
}
@@ -186,7 +187,7 @@ class DiskCache(
186187
source = "invalidateAccessToken",
187188
result = Result.Failed,
188189
reason = "Failed to invalidate Access Token",
189-
reasonDesc = e.message ?: e::class.java.name
190+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
190191
)
191192
throw e
192193
}
@@ -282,7 +283,7 @@ class DiskCache(
282283
source = "writeKey",
283284
result = Result.Failed,
284285
reason = "Failed to write to cache",
285-
reasonDesc = e.message ?: e::class.java.name
286+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
286287
)
287288
throw e
288289
}

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package software.aws.toolkits.jetbrains.core.credentials.sso
55

6+
import com.intellij.openapi.application.ApplicationInfo
67
import com.intellij.openapi.components.service
78
import com.intellij.openapi.progress.EmptyProgressIndicator
89
import com.intellij.openapi.progress.ProcessCanceledException
@@ -23,6 +24,7 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
2324
import software.aws.toolkits.jetbrains.core.credentials.sso.pkce.PKCE_CLIENT_NAME
2425
import software.aws.toolkits.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService
2526
import software.aws.toolkits.jetbrains.core.webview.getAuthType
27+
import software.aws.toolkits.jetbrains.services.telemetry.scrubNames
2628
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
2729
import software.aws.toolkits.jetbrains.utils.sleepWithCancellation
2830
import software.aws.toolkits.resources.AwsCoreBundle
@@ -182,7 +184,7 @@ class SsoAccessTokenProvider(
182184
source = "accessToken",
183185
result = Result.Failed,
184186
reason = "Failed to write AccessToken to cache",
185-
reasonDesc = e.message ?: e::class.java.name,
187+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name,
186188
)
187189
throw e
188190
}
@@ -224,7 +226,7 @@ class SsoAccessTokenProvider(
224226
source = "registerDAGClient",
225227
result = Result.Failed,
226228
reason = "Failed to write DeviceAuthorizationClientRegistration to cache",
227-
reasonDesc = e.message ?: e::class.java.name
229+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
228230
)
229231
throw e
230232
}
@@ -276,7 +278,7 @@ class SsoAccessTokenProvider(
276278
source = "registerPkceClient",
277279
result = Result.Failed,
278280
reason = "Failed to write PKCEClientRegistration to cache",
279-
reasonDesc = e.message ?: e::class.java.name
281+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
280282
)
281283
throw e
282284
}
@@ -345,6 +347,7 @@ class SsoAccessTokenProvider(
345347

346348
onPendingToken.tokenRetrieved()
347349
_authorization.set(null)
350+
ApplicationInfo.getInstance().build.baselineVersion
348351

349352
return tokenResponse.toDAGAccessToken(authorization.createdAt)
350353
} catch (e: SlowDownException) {

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/TelemetryUtils.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,66 @@ val ALLOWED_CODE_EXTENSIONS = setOf(
200200
"yml",
201201
"zig"
202202
)
203+
204+
fun scrubNames(messageToBeScrubbed: String, username: String? = null): String {
205+
var scrubbedMessage = ""
206+
val fileExtRegex = Regex("""\.[^./]+$""")
207+
val slashdot = Regex("""^[~.]*[/\\]*""")
208+
209+
/** Allowlisted filepath segments. */
210+
val keep = setOf(
211+
"~", ".", "..", ".aws", "aws", "sso", "cache", "credentials", "config",
212+
"Users", "users", "home", "tmp", "aws-toolkit-jetbrains"
213+
)
214+
215+
var processedMessage = messageToBeScrubbed
216+
if (!username.isNullOrEmpty() && username.length > 2) {
217+
processedMessage = processedMessage.replace(username, "x")
218+
}
219+
220+
// Replace contiguous whitespace with 1 space.
221+
processedMessage = processedMessage.replace(Regex("""\s+"""), " ")
222+
223+
// 1. split on whitespace.
224+
// 2. scrub words that match username or look like filepaths.
225+
val words = processedMessage.split(Regex("""\s+"""))
226+
for (word in words) {
227+
val pathSegments = word.split(Regex("""[/\\]""")).filter { it != "" }
228+
if (pathSegments.size < 2) {
229+
// Not a filepath.
230+
scrubbedMessage += " $word"
231+
continue
232+
}
233+
234+
// Replace all (non-allowlisted) ASCII filepath segments with "x".
235+
// "/foo/bar/aws/sso/" => "/x/x/aws/sso/"
236+
var scrubbed = ""
237+
// Get the frontmatter ("/", "../", "~/", or "./").
238+
val start = slashdot.find(word.trimStart())?.value ?: ""
239+
val firstVal = pathSegments[0].trimStart().replace(slashdot, "")
240+
241+
val ps = pathSegments.filterIndexed { i, _ -> i != 0 }.toMutableList()
242+
ps.add(0, firstVal)
243+
val driveLetterRegex = Regex("""^[a-zA-Z]:""")
244+
for (seg in ps) {
245+
when {
246+
driveLetterRegex.matches(seg) -> scrubbed += seg
247+
keep.contains(seg) -> scrubbed += "/$seg"
248+
else -> {
249+
// Save the first non-ASCII (unicode) char, if any.
250+
val nonAscii = Regex("""[^\p{ASCII}]""").find(seg)?.value ?: ""
251+
// Replace all chars (except [^…]) with "x" .
252+
val ascii = seg.replace(Regex("""[^$\[\](){}:;'" ]+"""), "x")
253+
scrubbed += "/${ascii}$nonAscii"
254+
}
255+
}
256+
}
257+
258+
// includes leading '.', eg: '.json'
259+
val fileExt = fileExtRegex.find(pathSegments.last())?.value ?: ""
260+
val newString = " ${start.replace(Regex("""\\"""), "/")}${scrubbed.removePrefix("//").removePrefix("/").removePrefix("\\")}$fileExt"
261+
scrubbedMessage += newString
262+
}
263+
264+
return scrubbedMessage.trim()
265+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.telemetry
5+
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.Test
8+
9+
class TelemetryUtilsTest {
10+
11+
@Test
12+
fun testScrubNames() {
13+
val fakeUser = "jdoe123"
14+
15+
assertEquals("", scrubNames("", fakeUser))
16+
assertEquals("a ./ b", scrubNames("a ./ b", fakeUser))
17+
assertEquals("a ../ b", scrubNames("a ../ b", fakeUser))
18+
assertEquals("a /.. b", scrubNames("a /.. b", fakeUser))
19+
assertEquals("a //..// b", scrubNames("a //..// b", fakeUser))
20+
assertEquals("a / b", scrubNames("a / b", fakeUser))
21+
assertEquals("a ~/ b", scrubNames("a ~/ b", fakeUser))
22+
assertEquals("a //// b", scrubNames("a //// b", fakeUser))
23+
assertEquals("a .. b", scrubNames("a .. b", fakeUser))
24+
assertEquals("a . b", scrubNames("a . b", fakeUser))
25+
assertEquals("lots of x", scrubNames(" lots of space ", "space"))
26+
assertEquals(
27+
"Failed to save c:/xß/xï/xó/x∑/xπ/xö/x/xö/x.txt no permissions (error!)",
28+
scrubNames(
29+
"Failed to save c:/fooß/aïböcß/aób∑c/∑ö/ππ¨p/ö/a/bar123öabc/baz.txt no permissions (error!)",
30+
fakeUser
31+
)
32+
)
33+
assertEquals(
34+
"user: x file: C:/Users/x/.aws/sso/cache/x.json (regex: /x/)",
35+
scrubNames("user: jdoe123 file: C:/Users/user1/.aws/sso/cache/abc123.json (regex: /foo/)", fakeUser)
36+
)
37+
assertEquals("/Users/x/x.jso", scrubNames("/Users/user1/foo.jso", fakeUser))
38+
assertEquals("/Users/x/x.js", scrubNames("/Users/user1/foo.js", fakeUser))
39+
assertEquals("/Users/x/x", scrubNames("/Users/user1/noFileExtension", fakeUser))
40+
assertEquals("/Users/x/x.a", scrubNames("/Users/user1/minExtLength.a", fakeUser))
41+
assertEquals("/Users/x/x.123456", scrubNames("/Users/user1/extIsNum.123456", fakeUser))
42+
assertEquals(
43+
"/Users/x/x.looooooooongextension",
44+
scrubNames("/Users/user1/foo.looooooooongextension", fakeUser)
45+
)
46+
assertEquals("/Users/x/x.ext3", scrubNames("/Users/user1/multipleExts.ext1.ext2.ext3", fakeUser))
47+
48+
assertEquals("c:/xß/x/x.txt", scrubNames("c:\\fooß\\bar\\baz.txt", fakeUser))
49+
// assertEquals(
50+
// "uhh c:/x x/ spaces /x hmm...",
51+
// scrubNames("uhh c:\\path with\\ spaces \\baz.. hmm...", fakeUser)
52+
// )
53+
assertEquals(
54+
"unc path: //x$/x/x END",
55+
scrubNames("unc path: \\\\server$\\pipename\\etc END", fakeUser)
56+
)
57+
assertEquals(
58+
"c:/Users/x/.aws/sso/cache/x.json x abc",
59+
scrubNames("c:\\Users\\user1\\.aws\\sso\\cache\\abc123.json jdoe123 abc", fakeUser)
60+
)
61+
assertEquals(
62+
"unix /home/x/.aws/config failed",
63+
scrubNames("unix /home/jdoe123/.aws/config failed", fakeUser)
64+
)
65+
assertEquals(
66+
"unix ~x/.aws/config failed",
67+
scrubNames("unix ~jdoe123/.aws/config failed", fakeUser)
68+
)
69+
assertEquals(
70+
"unix ../../.aws/config failed",
71+
scrubNames("unix ../../.aws/config failed", fakeUser)
72+
)
73+
assertEquals("unix ~/.aws/config failed", scrubNames("unix ~/.aws/config failed", fakeUser))
74+
assertEquals(
75+
"/Users/x/.aws/sso/cache/x.json no space",
76+
scrubNames("/Users/user1/.aws/sso/cache/abc123.json no space", fakeUser)
77+
)
78+
}
79+
}

0 commit comments

Comments
 (0)