From 4f402cc5d50e74bce6dae9936a2e8a5cd25fdf19 Mon Sep 17 00:00:00 2001 From: Jeon Suyeol Date: Thu, 5 Mar 2026 21:31:19 +0900 Subject: [PATCH 1/2] Add debug logging to Slack cookie extraction pipeline The cookie extraction has many silent failure paths (file not found, SQL query empty, decryption failed, Keychain denied) that all return empty string with no indication of what went wrong. Add an optional debugLog callback that logs at each decision point. --- src/platforms/slack/token-extractor.test.ts | 31 +++++++++++++++++++ src/platforms/slack/token-extractor.ts | 33 +++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/platforms/slack/token-extractor.test.ts b/src/platforms/slack/token-extractor.test.ts index 8d113ae..ec5f819 100644 --- a/src/platforms/slack/token-extractor.test.ts +++ b/src/platforms/slack/token-extractor.test.ts @@ -155,6 +155,37 @@ describe('TokenExtractor Linux cookie decryption', () => { }) }) +describe('TokenExtractor debug logging', () => { + test('calls debugLog callback during extraction', async () => { + // given + const slackDir = mkdtempSync(join(tmpdir(), 'slack-debug-')) + tempDirs.push(slackDir) + mkdirSync(join(slackDir, 'storage'), { recursive: true }) + + const messages: string[] = [] + const debugLog = (msg: string) => messages.push(msg) + + // when + const extractor = new TokenExtractor('darwin', slackDir, undefined, debugLog) + await extractor.extract() + + // then — should have emitted debug messages + expect(messages.length).toBeGreaterThan(0) + }) + + test('does not throw when debugLog is not provided', async () => { + // given + const slackDir = mkdtempSync(join(tmpdir(), 'slack-no-debug-')) + tempDirs.push(slackDir) + mkdirSync(join(slackDir, 'storage'), { recursive: true }) + + // when — then — should not throw + const extractor = new TokenExtractor('darwin', slackDir) + const result = await extractor.extract() + expect(result).toEqual([]) + }) +}) + describe('TokenExtractor Windows DPAPI', () => { test('decryptDPAPI returns null on non-win32 platform', () => { const extractor = new TokenExtractor('darwin', '/tmp/slack-test') diff --git a/src/platforms/slack/token-extractor.ts b/src/platforms/slack/token-extractor.ts index 4c4774b..ee07bfa 100644 --- a/src/platforms/slack/token-extractor.ts +++ b/src/platforms/slack/token-extractor.ts @@ -26,8 +26,14 @@ export class TokenExtractor { private platform: NodeJS.Platform private slackDir: string private keyCache: DerivedKeyCache - - constructor(platform?: NodeJS.Platform, slackDir?: string, keyCache?: DerivedKeyCache) { + private debugLog: ((message: string) => void) | null + + constructor( + platform?: NodeJS.Platform, + slackDir?: string, + keyCache?: DerivedKeyCache, + debugLog?: (message: string) => void, + ) { this.platform = platform ?? process.platform if (!['darwin', 'linux', 'win32'].includes(this.platform)) { @@ -36,6 +42,11 @@ export class TokenExtractor { this.slackDir = slackDir ?? this.getSlackDir() this.keyCache = keyCache ?? new DerivedKeyCache() + this.debugLog = debugLog ?? null + } + + private debug(message: string): void { + this.debugLog?.(message) } getSlackDir(): string { @@ -406,10 +417,13 @@ export class TokenExtractor { if (!existsSync(cookiesPath)) { const networkCookiesPath = join(this.slackDir, 'Network', 'Cookies') if (!existsSync(networkCookiesPath)) { + this.debug(`Cookie file not found at ${cookiesPath} or ${networkCookiesPath}`) return '' } + this.debug(`Using Network cookies path: ${networkCookiesPath}`) return this.readCookieFromDB(networkCookiesPath) } + this.debug(`Using cookies path: ${cookiesPath}`) return this.readCookieFromDB(cookiesPath) } @@ -427,6 +441,7 @@ export class TokenExtractor { 'Quit the Slack app completely and try again.', ) } + this.debug(`Failed to copy cookie DB: ${(error as Error).message}`) return '' } @@ -453,22 +468,29 @@ export class TokenExtractor { } if (!row) { + this.debug('No cookie row found in database') return '' } if (row.value?.startsWith('xoxd-')) { + this.debug('Found plaintext cookie') return row.value } if (row.encrypted_value && row.encrypted_value.length > 0) { + this.debug(`Found encrypted cookie (${row.encrypted_value.length} bytes)`) const decrypted = this.tryDecryptCookie(Buffer.from(row.encrypted_value)) if (decrypted) { + this.debug('Cookie decrypted successfully') return decrypted } + this.debug('Cookie decryption failed') } + this.debug('No usable cookie value in row') return '' - } catch { + } catch (error) { + this.debug(`Cookie DB query failed: ${(error as Error).message}`) return '' } finally { try { @@ -629,6 +651,7 @@ export class TokenExtractor { private async getDerivedKeyAsync(): Promise { if (this.platform !== 'darwin') { + this.debug(`Skipping Keychain key derivation (platform: ${this.platform})`) return null } @@ -636,6 +659,7 @@ export class TokenExtractor { if (cached) { this.cachedKey = cached this.usedCachedKey = true + this.debug('Using cached derived key') return cached } @@ -644,6 +668,9 @@ export class TokenExtractor { this.cachedKey = key await this.keyCache.set('slack', key) this.usedCachedKey = false + this.debug('Derived key from Keychain') + } else { + this.debug('Failed to derive key from Keychain') } return key } From f5f5d6679d0e832e50e6a99a9175cf6197cb0716 Mon Sep 17 00:00:00 2001 From: Jeon Suyeol Date: Thu, 5 Mar 2026 21:31:26 +0900 Subject: [PATCH 2/2] Wire debug logging from auth extract --debug to TokenExtractor Pass a debugLog callback when --debug is set so cookie extraction failure details appear in the debug output. --- src/platforms/slack/commands/auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platforms/slack/commands/auth.ts b/src/platforms/slack/commands/auth.ts index 6ae099b..3379b4c 100644 --- a/src/platforms/slack/commands/auth.ts +++ b/src/platforms/slack/commands/auth.ts @@ -7,7 +7,8 @@ import { TokenExtractor } from '../token-extractor' async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise { try { - const extractor = new TokenExtractor() + const debugLog = options.debug ? (msg: string) => console.error(`[debug] ${msg}`) : undefined + const extractor = new TokenExtractor(undefined, undefined, undefined, debugLog) if (process.platform === 'darwin') { console.log('')