Skip to content

Commit a834c96

Browse files
authored
Scrub names from messages in logging/telemetry (#5053)
* Scrub names from messages in logging/telemetry * removing commented code * feedback * detekt
1 parent 404329f commit a834c96

File tree

4 files changed

+142
-8
lines changed

4 files changed

+142
-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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
2424
import software.aws.toolkits.jetbrains.core.credentials.sso.pkce.PKCE_CLIENT_NAME
2525
import software.aws.toolkits.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService
2626
import software.aws.toolkits.jetbrains.core.webview.getAuthType
27+
import software.aws.toolkits.jetbrains.services.telemetry.scrubNames
2728
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
2829
import software.aws.toolkits.jetbrains.utils.sleepWithCancellation
2930
import software.aws.toolkits.resources.AwsCoreBundle
@@ -183,7 +184,7 @@ class SsoAccessTokenProvider(
183184
source = "accessToken",
184185
result = Result.Failed,
185186
reason = "Failed to write AccessToken to cache",
186-
reasonDesc = e.message ?: e::class.java.name,
187+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name,
187188
)
188189
throw e
189190
}
@@ -225,7 +226,7 @@ class SsoAccessTokenProvider(
225226
source = "registerDAGClient",
226227
result = Result.Failed,
227228
reason = "Failed to write DeviceAuthorizationClientRegistration to cache",
228-
reasonDesc = e.message ?: e::class.java.name
229+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
229230
)
230231
throw e
231232
}
@@ -277,7 +278,7 @@ class SsoAccessTokenProvider(
277278
source = "registerPkceClient",
278279
result = Result.Failed,
279280
reason = "Failed to write PKCEClientRegistration to cache",
280-
reasonDesc = e.message ?: e::class.java.name
281+
reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name
281282
)
282283
throw e
283284
}

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

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

0 commit comments

Comments
 (0)