mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-09-14 00:35:08 +00:00
fix(L3): redact key material from crash reports before persistence (GHSA-8844-q5vh-9j8f)
ReportAssembler previously appended e.toString()/cause.toString() and the full stack trace verbatim. Parser/crypto exceptions can embed attacker- controlled request data (hex keys, npub/nsec bech32, base64 ciphertexts). Add a redaction step that replaces 64-char hex keys, Nostr bech32 prefixes and long base64 blobs with placeholders before the report is written to internal storage by UnexpectedCrashSaver. Adds ReportAssemblerTest covering hex/bech32/base64/cause-chain redaction and non-sensitive content preservation.
This commit is contained in:
+28
-4
@@ -71,22 +71,46 @@ class ReportAssembler {
|
||||
appendLine(" |")
|
||||
appendLine()
|
||||
|
||||
// Defense-in-depth (GHSA-8844-q5vh-9j8f, L3): exception messages and
|
||||
// stack traces can embed attacker-controlled request data (parser/
|
||||
// crypto exceptions quoting bad input). Redact Nostr key material
|
||||
// (bech32 npub/nsec/etc, 64-char hex keys) and long base64 blobs
|
||||
// before persistence so the crash report cannot leak secrets.
|
||||
appendLine("```")
|
||||
appendLine(e.toString())
|
||||
appendLine(redactSensitive(e.toString()))
|
||||
e.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine(it.toString())
|
||||
appendLine(redactSensitive(it.toString()))
|
||||
}
|
||||
val cause = e.cause
|
||||
if (cause != null) {
|
||||
appendLine("\n\nCause:")
|
||||
append(" ")
|
||||
appendLine(cause.toString())
|
||||
appendLine(redactSensitive(cause.toString()))
|
||||
cause.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine(it.toString())
|
||||
appendLine(redactSensitive(it.toString()))
|
||||
}
|
||||
}
|
||||
appendLine("```")
|
||||
}
|
||||
|
||||
private fun redactSensitive(input: String): String = input
|
||||
.replace(BECH32_REDACT, "<bech32>")
|
||||
.replace(HEXKEY_REDACT, "<hexkey>")
|
||||
.replace(BASE64_REDACT, "<base64>")
|
||||
|
||||
private companion object {
|
||||
// bech32 prefixes used by Nostr (npub, nsec, note, nprofile, nevent,
|
||||
// naddr, nrelay, ncryptsec). Match the hrp + data part; bech32 uses
|
||||
// the alphabet 023456789acdefghjklmnpqrstuvwxyz (no b, i, o, 1).
|
||||
private val BECH32_REDACT =
|
||||
Regex("\\b(npub|nsec|note|nprofile|nevent|naddr|nrelay|ncryptsec)1[023456789acdefghjklmnpqrstuvwxyz]{6,}\\b")
|
||||
|
||||
// 64-char lowercase hex keys (pubkeys/event ids).
|
||||
private val HEXKEY_REDACT = Regex("\\b[0-9a-f]{64}\\b")
|
||||
|
||||
// Long base64 runs (>=32 chars): NIP-44/44v3 ciphertexts, payloads.
|
||||
private val BASE64_REDACT = Regex("\\b[A-Za-z0-9+/]{32,}={0,2}\\b")
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.greenart7c3.nostrsigner.service.crashreports
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ReportAssemblerTest {
|
||||
private val npubHex = "3bf0c63fcb93463407af97a5e5ee64b88f1f2c73b1a21c4f33ac8f0c7c6a6e0b"
|
||||
|
||||
@Test
|
||||
fun `redacts 64-char hex keys from exception messages and stack traces`() {
|
||||
val e = IllegalStateException("bad key $npubHex in input")
|
||||
|
||||
val report = ReportAssembler().buildReport(e)
|
||||
|
||||
assertFalse("hex key must be redacted", report.contains(npubHex))
|
||||
assertTrue("redaction marker present", report.contains("<hexkey>"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redacts npub bech32 from exception messages`() {
|
||||
// npub1... derived from the hex key above
|
||||
val npub = "npub10qp7qrzdrqmax40ayhxm2vp6d8yurfr3lwxyh4df0sf5r8cjnrms2cv7aj"
|
||||
val e = IllegalArgumentException("could not parse $npub")
|
||||
|
||||
val report = ReportAssembler().buildReport(e)
|
||||
|
||||
assertFalse("npub must be redacted", report.contains(npub))
|
||||
assertTrue("redaction marker present", report.contains("<bech32>"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redacts long base64 blobs from exception messages`() {
|
||||
val base64 = "kL9/abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
|
||||
val e = RuntimeException("decryption failed for payload=$base64")
|
||||
|
||||
val report = ReportAssembler().buildReport(e)
|
||||
|
||||
assertFalse("base64 must be redacted", report.contains(base64))
|
||||
assertTrue("redaction marker present", report.contains("<base64>"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preserves non-sensitive message content`() {
|
||||
val e = IllegalStateException("null pointer in feature X")
|
||||
|
||||
val report = ReportAssembler().buildReport(e)
|
||||
|
||||
assertTrue("non-sensitive content preserved", report.contains("null pointer in feature X"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redacts cause chain too`() {
|
||||
val cause = IllegalArgumentException("bad hex $npubHex")
|
||||
val e = RuntimeException("wrapper", cause)
|
||||
|
||||
val report = ReportAssembler().buildReport(e)
|
||||
|
||||
assertFalse("cause hex must be redacted", report.contains(npubHex))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user