Skip to content

Commit e1b0b23

Browse files
authored
Send file name and path only if isSendDefaultPii is true (#3919)
* file span description is now masked if isSendDefaultPii is false * file path on Android is sent only if isSendDefaultPii is true
1 parent 159a367 commit e1b0b23

File tree

6 files changed

+214
-34
lines changed

6 files changed

+214
-34
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
- Our `UncaughtExceptionHandlerIntegration` waited for the full flush timeout duration (default 15s) when rate limited.
1414
- Do not replace `op` with auto generated content for OpenTelemetry spans with span kind `INTERNAL` ([#3906](https://github.com/getsentry/sentry-java/pull/3906))
1515

16+
### Behavioural Changes
17+
18+
- Send file name and path only if isSendDefaultPii is true ([#3919](https://github.com/getsentry/sentry-java/pull/3919))
19+
1620
## 8.0.0-beta.2
1721

1822
### Breaking Changes

sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ private void finishSpan() {
9393
if (currentSpan != null) {
9494
final String byteCountToString = StringUtils.byteCountToString(byteCount);
9595
if (file != null) {
96-
final String description = file.getName() + " " + "(" + byteCountToString + ")";
96+
final String description = getDescription(file);
9797
currentSpan.setDescription(description);
98-
if (Platform.isAndroid() || options.isSendDefaultPii()) {
98+
if (options.isSendDefaultPii()) {
9999
currentSpan.setData("file.path", file.getAbsolutePath());
100100
}
101101
} else {
@@ -112,6 +112,22 @@ private void finishSpan() {
112112
}
113113
}
114114

115+
private @NotNull String getDescription(final @NotNull File file) {
116+
final String byteCountToString = StringUtils.byteCountToString(byteCount);
117+
// if we send PII, we can send the file name directly
118+
if (options.isSendDefaultPii()) {
119+
return file.getName() + " (" + byteCountToString + ")";
120+
}
121+
final int lastDotIndex = file.getName().lastIndexOf('.');
122+
// if the file has an extension, show it in the description, even without sending PII
123+
if (lastDotIndex > 0 && lastDotIndex < file.getName().length() - 1) {
124+
final String fileExtension = file.getName().substring(lastDotIndex);
125+
return "***" + fileExtension + " (" + byteCountToString + ")";
126+
} else {
127+
return "***" + " (" + byteCountToString + ")";
128+
}
129+
}
130+
115131
/**
116132
* A task that returns a result and may throw an IOException. Implementors define a single method
117133
* with no arguments called {@code call}.

sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import kotlin.concurrent.thread
2323
import kotlin.test.Test
2424
import kotlin.test.assertEquals
2525
import kotlin.test.assertFalse
26+
import kotlin.test.assertNotNull
2627
import kotlin.test.assertNull
2728
import kotlin.test.assertTrue
2829

@@ -80,6 +81,8 @@ class SentryFileInputStreamTest {
8081

8182
private val tmpFile: File get() = tmpDir.newFile("test.txt")
8283

84+
private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test")
85+
8386
@Test
8487
fun `when no active transaction does not capture a span`() {
8588
fixture.getSut(tmpFile, activeTransaction = false)
@@ -104,13 +107,42 @@ class SentryFileInputStreamTest {
104107

105108
assertEquals(fixture.sentryTracer.children.size, 1)
106109
val fileIOSpan = fixture.sentryTracer.children.first()
107-
assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)")
108110
assertEquals(fileIOSpan.data["file.size"], 0L)
109111
assertEquals(fileIOSpan.throwable, null)
110112
assertEquals(fileIOSpan.isFinished, true)
111113
assertEquals(fileIOSpan.status, SpanStatus.OK)
112114
}
113115

116+
@Test
117+
fun `captures file name in description and file path when isSendDefaultPii is true`() {
118+
val fis = fixture.getSut(tmpFile, sendDefaultPii = true)
119+
fis.close()
120+
121+
val fileIOSpan = fixture.sentryTracer.children.first()
122+
assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)")
123+
assertNotNull(fileIOSpan.data["file.path"])
124+
}
125+
126+
@Test
127+
fun `captures only file extension in description when isSendDefaultPii is false`() {
128+
val fis = fixture.getSut(tmpFile, sendDefaultPii = false)
129+
fis.close()
130+
131+
val fileIOSpan = fixture.sentryTracer.children.first()
132+
assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)")
133+
assertNull(fileIOSpan.data["file.path"])
134+
}
135+
136+
@Test
137+
fun `captures only file size if no extension is available when isSendDefaultPii is false`() {
138+
val fis = fixture.getSut(tmpFileWithoutExtension, sendDefaultPii = false)
139+
fis.close()
140+
141+
val fileIOSpan = fixture.sentryTracer.children.first()
142+
assertEquals(fileIOSpan.spanContext.description, "*** (0 B)")
143+
assertNull(fileIOSpan.data["file.path"])
144+
}
145+
114146
@Test
115147
fun `when stream is closed, releases file descriptor`() {
116148
val fis = fixture.getSut(tmpFile)
@@ -123,7 +155,7 @@ class SentryFileInputStreamTest {
123155
fixture.getSut(tmpFile).use { it.read() }
124156

125157
val fileIOSpan = fixture.sentryTracer.children.first()
126-
assertEquals(fileIOSpan.spanContext.description, "test.txt (1 B)")
158+
assertEquals(fileIOSpan.spanContext.description, "***.txt (1 B)")
127159
assertEquals(fileIOSpan.data["file.size"], 1L)
128160
}
129161

@@ -132,7 +164,7 @@ class SentryFileInputStreamTest {
132164
fixture.getSut(tmpFile).use { it.read(ByteArray(10)) }
133165

134166
val fileIOSpan = fixture.sentryTracer.children.first()
135-
assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)")
167+
assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)")
136168
assertEquals(fileIOSpan.data["file.size"], 4L)
137169
}
138170

@@ -141,7 +173,7 @@ class SentryFileInputStreamTest {
141173
fixture.getSut(tmpFile).use { it.read(ByteArray(10), 1, 3) }
142174

143175
val fileIOSpan = fixture.sentryTracer.children.first()
144-
assertEquals(fileIOSpan.spanContext.description, "test.txt (3 B)")
176+
assertEquals(fileIOSpan.spanContext.description, "***.txt (3 B)")
145177
assertEquals(fileIOSpan.data["file.size"], 3L)
146178
}
147179

@@ -150,7 +182,7 @@ class SentryFileInputStreamTest {
150182
fixture.getSut(tmpFile).use { it.skip(10) }
151183

152184
val fileIOSpan = fixture.sentryTracer.children.first()
153-
assertEquals(fileIOSpan.spanContext.description, "test.txt (10 B)")
185+
assertEquals(fileIOSpan.spanContext.description, "***.txt (10 B)")
154186
assertEquals(fileIOSpan.data["file.size"], 10L)
155187
}
156188

@@ -159,7 +191,7 @@ class SentryFileInputStreamTest {
159191
fixture.getSut(tmpFile).use { it.reader().readText() }
160192

161193
val fileIOSpan = fixture.sentryTracer.children.first()
162-
assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)")
194+
assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)")
163195
assertEquals(fileIOSpan.data["file.size"], 4L)
164196
}
165197

sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import kotlin.concurrent.thread
1919
import kotlin.test.Test
2020
import kotlin.test.assertEquals
2121
import kotlin.test.assertFalse
22+
import kotlin.test.assertNotNull
2223
import kotlin.test.assertNull
2324
import kotlin.test.assertTrue
2425

@@ -30,14 +31,15 @@ class SentryFileOutputStreamTest {
3031
internal fun getSut(
3132
tmpFile: File? = null,
3233
activeTransaction: Boolean = true,
33-
append: Boolean = false
34+
append: Boolean = false,
35+
optionsConfiguration: (SentryOptions) -> Unit = {}
3436
): SentryFileOutputStream {
35-
whenever(scopes.options).thenReturn(
36-
SentryOptions().apply {
37-
threadChecker = ThreadChecker.getInstance()
38-
addInAppInclude("org.junit")
39-
}
40-
)
37+
val options = SentryOptions().apply {
38+
threadChecker = ThreadChecker.getInstance()
39+
addInAppInclude("org.junit")
40+
optionsConfiguration(this)
41+
}
42+
whenever(scopes.options).thenReturn(options)
4143
sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes)
4244
if (activeTransaction) {
4345
whenever(scopes.span).thenReturn(sentryTracer)
@@ -53,6 +55,8 @@ class SentryFileOutputStreamTest {
5355

5456
private val tmpFile: File get() = tmpDir.newFile("test.txt")
5557

58+
private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test")
59+
5660
@Test
5761
fun `when no active transaction does not capture a span`() {
5862
fixture.getSut(tmpFile, activeTransaction = false)
@@ -77,13 +81,49 @@ class SentryFileOutputStreamTest {
7781

7882
assertEquals(fixture.sentryTracer.children.size, 1)
7983
val fileIOSpan = fixture.sentryTracer.children.first()
80-
assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)")
84+
assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)")
8185
assertEquals(fileIOSpan.data["file.size"], 0L)
8286
assertEquals(fileIOSpan.throwable, null)
8387
assertEquals(fileIOSpan.isFinished, true)
8488
assertEquals(fileIOSpan.status, SpanStatus.OK)
8589
}
8690

91+
@Test
92+
fun `captures file name in description and file path when isSendDefaultPii is true`() {
93+
val fos = fixture.getSut(tmpFile) {
94+
it.isSendDefaultPii = true
95+
}
96+
fos.close()
97+
98+
val fileIOSpan = fixture.sentryTracer.children.first()
99+
assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)")
100+
assertNotNull(fileIOSpan.data["file.path"])
101+
}
102+
103+
@Test
104+
fun `captures only file extension in description when isSendDefaultPii is false`() {
105+
val fos = fixture.getSut(tmpFile) {
106+
it.isSendDefaultPii = false
107+
}
108+
fos.close()
109+
110+
val fileIOSpan = fixture.sentryTracer.children.first()
111+
assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)")
112+
assertNull(fileIOSpan.data["file.path"])
113+
}
114+
115+
@Test
116+
fun `captures only file size if no extension is available when isSendDefaultPii is false`() {
117+
val fos = fixture.getSut(tmpFileWithoutExtension) {
118+
it.isSendDefaultPii = false
119+
}
120+
fos.close()
121+
122+
val fileIOSpan = fixture.sentryTracer.children.first()
123+
assertEquals(fileIOSpan.spanContext.description, "*** (0 B)")
124+
assertNull(fileIOSpan.data["file.path"])
125+
}
126+
87127
@Test
88128
fun `when stream is closed file descriptor is also closed`() {
89129
val fos = fixture.getSut(tmpFile)
@@ -96,7 +136,7 @@ class SentryFileOutputStreamTest {
96136
fixture.getSut(tmpFile).use { it.write(29) }
97137

98138
val fileIOSpan = fixture.sentryTracer.children.first()
99-
assertEquals(fileIOSpan.spanContext.description, "test.txt (1 B)")
139+
assertEquals(fileIOSpan.spanContext.description, "***.txt (1 B)")
100140
assertEquals(fileIOSpan.data["file.size"], 1L)
101141
}
102142

@@ -105,7 +145,7 @@ class SentryFileOutputStreamTest {
105145
fixture.getSut(tmpFile).use { it.write(ByteArray(10)) }
106146

107147
val fileIOSpan = fixture.sentryTracer.children.first()
108-
assertEquals(fileIOSpan.spanContext.description, "test.txt (10 B)")
148+
assertEquals(fileIOSpan.spanContext.description, "***.txt (10 B)")
109149
assertEquals(fileIOSpan.data["file.size"], 10L)
110150
}
111151

@@ -114,7 +154,7 @@ class SentryFileOutputStreamTest {
114154
fixture.getSut(tmpFile).use { it.write(ByteArray(10), 1, 3) }
115155

116156
val fileIOSpan = fixture.sentryTracer.children.first()
117-
assertEquals(fileIOSpan.spanContext.description, "test.txt (3 B)")
157+
assertEquals(fileIOSpan.spanContext.description, "***.txt (3 B)")
118158
assertEquals(fileIOSpan.data["file.size"], 3L)
119159
}
120160

@@ -123,7 +163,7 @@ class SentryFileOutputStreamTest {
123163
fixture.getSut(tmpFile).use { it.write("Text".toByteArray()) }
124164

125165
val fileIOSpan = fixture.sentryTracer.children.first()
126-
assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)")
166+
assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)")
127167
assertEquals(fileIOSpan.data["file.size"], 4L)
128168
}
129169

sentry/src/test/java/io/sentry/instrumentation/file/SentryFileReaderTest.kt

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.mockito.kotlin.whenever
1414
import java.io.File
1515
import kotlin.test.Test
1616
import kotlin.test.assertEquals
17+
import kotlin.test.assertNotNull
18+
import kotlin.test.assertNull
1719

1820
class SentryFileReaderTest {
1921
class Fixture {
@@ -22,14 +24,15 @@ class SentryFileReaderTest {
2224

2325
internal fun getSut(
2426
tmpFile: File,
25-
activeTransaction: Boolean = true
27+
activeTransaction: Boolean = true,
28+
optionsConfiguration: (SentryOptions) -> Unit = {}
2629
): SentryFileReader {
2730
tmpFile.writeText("TEXT")
28-
whenever(scopes.options).thenReturn(
29-
SentryOptions().apply {
30-
threadChecker = ThreadChecker.getInstance()
31-
}
32-
)
31+
val options = SentryOptions().apply {
32+
threadChecker = ThreadChecker.getInstance()
33+
optionsConfiguration(this)
34+
}
35+
whenever(scopes.options).thenReturn(options)
3336
sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes)
3437
if (activeTransaction) {
3538
whenever(scopes.span).thenReturn(sentryTracer)
@@ -45,6 +48,8 @@ class SentryFileReaderTest {
4548

4649
private val tmpFile: File get() = tmpDir.newFile("test.txt")
4750

51+
private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test")
52+
4853
@Test
4954
fun `captures a span`() {
5055
val reader = fixture.getSut(tmpFile)
@@ -54,11 +59,50 @@ class SentryFileReaderTest {
5459
assertEquals(fixture.sentryTracer.children.size, 1)
5560
val fileIOSpan = fixture.sentryTracer.children.first()
5661
assertEquals(fileIOSpan.spanContext.operation, "file.read")
57-
assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)")
62+
assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)")
5863
assertEquals(fileIOSpan.data["file.size"], 4L)
5964
assertEquals(fileIOSpan.throwable, null)
6065
assertEquals(fileIOSpan.isFinished, true)
6166
assertEquals(fileIOSpan.data[SpanDataConvention.BLOCKED_MAIN_THREAD_KEY], true)
6267
assertEquals(fileIOSpan.status, OK)
6368
}
69+
70+
@Test
71+
fun `captures file name in description and file path when isSendDefaultPii is true`() {
72+
val reader = fixture.getSut(tmpFile) {
73+
it.isSendDefaultPii = true
74+
}
75+
reader.readText()
76+
reader.close()
77+
78+
val fileIOSpan = fixture.sentryTracer.children.first()
79+
assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)")
80+
assertNotNull(fileIOSpan.data["file.path"])
81+
}
82+
83+
@Test
84+
fun `captures only file extension in description when isSendDefaultPii is false`() {
85+
val reader = fixture.getSut(tmpFile) {
86+
it.isSendDefaultPii = false
87+
}
88+
reader.readText()
89+
reader.close()
90+
91+
val fileIOSpan = fixture.sentryTracer.children.first()
92+
assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)")
93+
assertNull(fileIOSpan.data["file.path"])
94+
}
95+
96+
@Test
97+
fun `captures only file size if no extension is available when isSendDefaultPii is false`() {
98+
val reader = fixture.getSut(tmpFileWithoutExtension) {
99+
it.isSendDefaultPii = false
100+
}
101+
reader.readText()
102+
reader.close()
103+
104+
val fileIOSpan = fixture.sentryTracer.children.first()
105+
assertEquals(fileIOSpan.spanContext.description, "*** (4 B)")
106+
assertNull(fileIOSpan.data["file.path"])
107+
}
64108
}

0 commit comments

Comments
 (0)