Skip to content

Commit d39db38

Browse files
authored
Merge pull request #1113 from supabase-community/mask-sensitive
Mask sensitive information in `RestException`
2 parents 99268f8 + 49e2be1 commit d39db38

File tree

5 files changed

+95
-10
lines changed

5 files changed

+95
-10
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.jan.supabase
2+
3+
import io.ktor.http.Headers
4+
import io.ktor.http.Url
5+
import io.ktor.util.toMap
6+
7+
internal fun maskString(value: String, visibleCharacters: Int = 2, showLength: Boolean = false): String {
8+
if(value.isBlank()) return value;
9+
return value.take(visibleCharacters) + "..." + if(showLength) " (len=${value.length})" else ""
10+
}
11+
12+
internal fun maskUrl(value: Url, visibleCharacters: Int = 2): String {
13+
return buildUrl(value) {
14+
host = "${host.take(visibleCharacters)}..."
15+
}
16+
}
17+
18+
private val SENSITIVE_HEADERS = listOf("apikey", "Authorization")
19+
20+
internal fun maskHeaders(headers: Headers): String = headers.toMap().mapValues { (key, value) ->
21+
if(key in SENSITIVE_HEADERS) {
22+
value.firstOrNull()?.let {
23+
listOf(if(key == "Authorization") "Bearer ${maskString(it.drop(7), showLength = true)}" else maskString(it, showLength = true))
24+
}
25+
} else value
26+
}.toString()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.github.jan.supabase
2+
3+
import io.github.jan.supabase.annotations.SupabaseInternal
4+
import io.ktor.http.URLBuilder
5+
import io.ktor.http.Url
6+
7+
@SupabaseInternal
8+
inline fun buildUrl(baseUrl: String, init: URLBuilder.() -> Unit): String {
9+
val builder = URLBuilder(baseUrl)
10+
builder.init()
11+
return builder.buildString()
12+
}
13+
14+
@SupabaseInternal
15+
inline fun buildUrl(baseUrl: Url, init: URLBuilder.() -> Unit): String {
16+
val builder = URLBuilder(baseUrl)
17+
builder.init()
18+
return builder.buildString()
19+
}

Supabase/src/commonMain/kotlin/io/github/jan/supabase/Utils.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import io.github.jan.supabase.exceptions.SupabaseEncodingException
55
import io.github.jan.supabase.logging.i
66
import io.ktor.client.statement.HttpResponse
77
import io.ktor.client.statement.bodyAsText
8-
import io.ktor.http.URLBuilder
98
import kotlinx.serialization.ExperimentalSerializationApi
109
import kotlinx.serialization.MissingFieldException
1110
import kotlinx.serialization.SerializationException
@@ -32,13 +31,6 @@ suspend inline fun <reified T> HttpResponse.safeBody(context: String? = null): T
3231
}
3332
}
3433

35-
@SupabaseInternal
36-
inline fun buildUrl(baseUrl: String, init: URLBuilder.() -> Unit): String {
37-
val builder = URLBuilder(baseUrl)
38-
builder.init()
39-
return builder.buildString()
40-
}
41-
4234
@SupabaseInternal
4335
fun String.toJsonObject(): JsonObject = supabaseJson.decodeFromString(this)
4436

Supabase/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.jan.supabase.exceptions
22

3+
import io.github.jan.supabase.maskHeaders
4+
import io.github.jan.supabase.maskUrl
35
import io.ktor.client.statement.HttpResponse
46
import io.ktor.client.statement.request
57

@@ -17,8 +19,8 @@ import io.ktor.client.statement.request
1719
*/
1820
open class RestException(val error: String, val description: String?, val response: HttpResponse): Exception("""
1921
$error${description?.let { "\n$it" } ?: ""}
20-
URL: ${response.request.url}
21-
Headers: ${response.request.headers.entries()}
22+
URL: ${maskUrl(response.request.url)}
23+
Headers: ${maskHeaders(response.request.headers)}
2224
Http Method: ${response.request.method.value}
2325
""".trimIndent()) {
2426

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import io.github.jan.supabase.maskHeaders
2+
import io.github.jan.supabase.maskString
3+
import io.github.jan.supabase.maskUrl
4+
import io.ktor.http.Url
5+
import io.ktor.http.headers
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
9+
class StringMaskingTest {
10+
11+
@Test
12+
fun testEmptyString() {
13+
assertEquals("", maskString(""))
14+
}
15+
16+
@Test
17+
fun testString() {
18+
assertEquals("ab...", maskString("abcdefghi"))
19+
}
20+
21+
@Test
22+
fun testStringWithLength() {
23+
assertEquals("ab... (len=5)", maskString("abcde", showLength = true))
24+
}
25+
26+
@Test
27+
fun testUrl() {
28+
assertEquals("https://ab.../test?parameter=true", maskUrl(Url("https://abcdefg.supabase.co/test?parameter=true")))
29+
}
30+
31+
@Test
32+
fun testHeaderMasking() {
33+
val headers = headers {
34+
set("aa", "bb")
35+
set("another", "one")
36+
set("apikey", "areallylongkey")
37+
set("Authorization", "Bearer thisisasecretkey")
38+
}
39+
val maskedString = maskHeaders(headers)
40+
assertEquals(
41+
"{aa=[bb], another=[one], apikey=[ar... (len=14)], Authorization=[Bearer th... (len=16)]}",
42+
maskedString
43+
)
44+
}
45+
46+
}

0 commit comments

Comments
 (0)