From 417456ad49928fe67f04178ec1046dc3164d8b27 Mon Sep 17 00:00:00 2001 From: Cobb Date: Thu, 6 Aug 2026 09:48:32 -0700 Subject: [PATCH] diag: keep channel/playlist ids in the dogfood scrub profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dogfood profile already skips the channel/playlist/video-id replacements, but the always-on LONG_TOKEN pass (20+ chars w/ a digit) then redacted the 24-char channel ids and long playlist ids to — so channel_info logs showed "[channel ]" instead of the real id. LONG_TOKEN on the dogfood profile now keeps a token that is exactly a channel (UC…) or playlist (PL/UU/LL/RD/OLAK5uy_…) id, while every other high-entropy run (visitorData, hashes) still redacts. Full profile unchanged. +2 unit tests. --- .../kotlin/com/sulkta/straw/util/LogDump.kt | 21 +++++++++++++++--- .../com/sulkta/straw/util/LogDumpScrubTest.kt | 22 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/util/LogDump.kt b/strawApp/src/main/kotlin/com/sulkta/straw/util/LogDump.kt index a34d1ccf4..cacb6e3f9 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/util/LogDump.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/util/LogDump.kt @@ -211,9 +211,24 @@ object LogDump { } } // Long high-entropy runs (hashes, visitor data, unlabeled - // tokens): 20+ [A-Za-z0-9_-] chars containing a digit. Scrubbed - // in BOTH profiles — visitorData & friends are creds, not signal. - s = LONG_TOKEN_RE.replace(s, "") + // tokens): 20+ [A-Za-z0-9_-] chars containing a digit. Scrubbed in + // BOTH profiles — visitorData & friends are creds, not signal. On the + // dogfood profile a token that is exactly a channel/playlist id + // (UC…, PL…/UU…/LL…/RD…/OLAK5uy_…) is KEPT — those ride past the + // skipped id passes above but LONG_TOKEN would otherwise redact them, + // and they're the same triage signal as the video id we keep. Every + // other high-entropy run (visitorData, hashes) still redacts. + s = if (keepIdentifiers) { + LONG_TOKEN_RE.replace(s) { m -> + if (CHANNEL_ID_RE.matches(m.value) || PLAYLIST_ID_RE.matches(m.value)) { + m.value + } else { + "" + } + } + } else { + LONG_TOKEN_RE.replace(s, "") + } // IP addresses (v4 + v6 — v6 patterns are shaped so threadtime // HH:MM:SS timestamps can never match). s = IPV4_RE.replace(s, "") diff --git a/strawApp/src/test/kotlin/com/sulkta/straw/util/LogDumpScrubTest.kt b/strawApp/src/test/kotlin/com/sulkta/straw/util/LogDumpScrubTest.kt index 1a3b7d0b1..a59a667d8 100644 --- a/strawApp/src/test/kotlin/com/sulkta/straw/util/LogDumpScrubTest.kt +++ b/strawApp/src/test/kotlin/com/sulkta/straw/util/LogDumpScrubTest.kt @@ -72,6 +72,28 @@ class LogDumpScrubTest { assertFalse(LogDump.scrubLine(line).contains("dQw4w9WgXcQ")) } + @Test + fun dogfoodKeepsChannelAndPlaylistIdFullScrubs() { + // These are ≥20 chars so LONG_TOKEN would otherwise redact them even + // on the dogfood profile; the channel/playlist exemption keeps them. + val channelId = "UCuAXFkgsw1L7xaCfnd5JJOw" // UC + 22, has digits + val playlistId = "PLbpi6ZahtOH6Blw3RGYpWkSByi_T7Rygb" + assertEquals(channelId, LogDump.scrubLineDogfood(channelId)) + assertEquals(playlistId, LogDump.scrubLineDogfood(playlistId)) + // Full profile redacts them to their labelled placeholders. + assertEquals("", LogDump.scrubLine(channelId)) + assertEquals("", LogDump.scrubLine(playlistId)) + } + + @Test + fun dogfoodStillScrubsGenericHighEntropyToken() { + // visitorData-shaped: ≥20 chars with a digit, NOT a channel/playlist + // prefix → still `` on the dogfood profile (it's a cred). + val visitorData = "CgtVQzEyMzQ1Njc4OTBhYg" + assertEquals("", LogDump.scrubLineDogfood(visitorData)) + assertEquals("", LogDump.scrubLine(visitorData)) + } + // ---- B2: dogfood profile STILL scrubs real credentials / PII --------- @Test