agent-messenger 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.env.template +35 -17
- package/README.md +7 -7
- package/bun.lock +31 -7
- package/dist/package.json +5 -3
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
- package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +57 -49
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/ensure-auth.js +3 -3
- package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +167 -14
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +2 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +2 -2
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +107 -14
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.js +57 -11
- package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
- package/dist/src/platforms/instagram/index.d.ts +1 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +1 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/instagram/token-extractor.js +407 -0
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +2 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/client.js +36 -9
- package/dist/src/platforms/line/client.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +6 -5
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +11 -10
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +300 -23
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +9 -8
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +2 -1
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +161 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +25 -7
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.js +6 -5
- package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/client.d.ts +12 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +168 -1
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts +4 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +50 -4
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/credential-manager.js +1 -1
- package/dist/src/platforms/webex/credential-manager.js.map +1 -1
- package/dist/src/platforms/webex/encryption.d.ts +10 -0
- package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
- package/dist/src/platforms/webex/encryption.js +49 -0
- package/dist/src/platforms/webex/encryption.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +25 -5
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
- package/dist/src/platforms/webex/index.d.ts +2 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/index.js +1 -0
- package/dist/src/platforms/webex/index.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +29 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +393 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +8 -1
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +4 -1
- package/dist/src/platforms/webex/types.js.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +6 -2
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.d.ts +1 -1
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.js +3 -2
- package/dist/src/shared/utils/error-handler.js.map +1 -1
- package/dist/src/shared/utils/stderr.d.ts +5 -0
- package/dist/src/shared/utils/stderr.d.ts.map +1 -0
- package/dist/src/shared/utils/stderr.js +18 -0
- package/dist/src/shared/utils/stderr.js.map +1 -0
- package/docs/content/docs/cli/channeltalk.mdx +7 -7
- package/docs/content/docs/cli/discord.mdx +3 -3
- package/docs/content/docs/cli/instagram.mdx +28 -6
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/teams.mdx +6 -4
- package/docs/content/docs/cli/webex.mdx +32 -11
- package/e2e/README.md +132 -8
- package/e2e/channeltalk.e2e.test.ts +2 -7
- package/e2e/channeltalkbot.e2e.test.ts +2 -6
- package/e2e/config.ts +172 -10
- package/e2e/helpers.ts +7 -0
- package/e2e/instagram.e2e.test.ts +97 -0
- package/e2e/kakaotalk.e2e.test.ts +74 -0
- package/e2e/line.e2e.test.ts +92 -0
- package/e2e/teams.e2e.test.ts +46 -1
- package/e2e/telegram.e2e.test.ts +84 -0
- package/e2e/webex.e2e.test.ts +190 -0
- package/e2e/whatsapp.e2e.test.ts +90 -0
- package/e2e/whatsappbot.e2e.test.ts +78 -0
- package/package.json +5 -3
- package/skills/agent-channeltalk/SKILL.md +9 -9
- package/skills/agent-channeltalk/references/authentication.md +21 -18
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -5
- package/skills/agent-discord/references/authentication.md +8 -8
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +51 -9
- package/skills/agent-instagram/references/authentication.md +35 -3
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -5
- package/skills/agent-slack/references/authentication.md +8 -8
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +6 -6
- package/skills/agent-teams/references/authentication.md +8 -8
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +35 -15
- package/skills/agent-webex/references/authentication.md +63 -9
- package/skills/agent-webex/references/common-patterns.md +6 -3
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
- package/src/platforms/channeltalk/commands/auth.ts +38 -32
- package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
- package/src/platforms/channeltalk/ensure-auth.ts +6 -6
- package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
- package/src/platforms/channeltalk/token-extractor.ts +344 -30
- package/src/platforms/discord/commands/auth.test.ts +3 -3
- package/src/platforms/discord/commands/auth.ts +58 -54
- package/src/platforms/discord/ensure-auth.test.ts +3 -3
- package/src/platforms/discord/ensure-auth.ts +3 -3
- package/src/platforms/discord/token-extractor.test.ts +199 -27
- package/src/platforms/discord/token-extractor.ts +190 -17
- package/src/platforms/instagram/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.ts +133 -14
- package/src/platforms/instagram/ensure-auth.ts +63 -12
- package/src/platforms/instagram/index.ts +1 -0
- package/src/platforms/instagram/token-extractor.test.ts +424 -0
- package/src/platforms/instagram/token-extractor.ts +478 -0
- package/src/platforms/kakaotalk/client.ts +3 -1
- package/src/platforms/kakaotalk/commands/auth.ts +14 -13
- package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
- package/src/platforms/line/client.ts +39 -14
- package/src/platforms/line/commands/auth.ts +7 -6
- package/src/platforms/slack/cli.test.ts +6 -5
- package/src/platforms/slack/commands/auth.test.ts +11 -7
- package/src/platforms/slack/commands/auth.ts +11 -10
- package/src/platforms/slack/token-extractor.test.ts +98 -1
- package/src/platforms/slack/token-extractor.ts +338 -26
- package/src/platforms/teams/commands/auth.ts +9 -8
- package/src/platforms/teams/ensure-auth.ts +3 -1
- package/src/platforms/teams/token-extractor.test.ts +136 -17
- package/src/platforms/teams/token-extractor.ts +182 -31
- package/src/platforms/telegram/client.test.ts +134 -0
- package/src/platforms/telegram/client.ts +27 -6
- package/src/platforms/telegram/commands/auth.ts +6 -5
- package/src/platforms/webex/client.test.ts +314 -0
- package/src/platforms/webex/client.ts +231 -1
- package/src/platforms/webex/commands/auth.ts +71 -4
- package/src/platforms/webex/commands/member.test.ts +10 -1
- package/src/platforms/webex/commands/message.test.ts +9 -5
- package/src/platforms/webex/commands/snapshot.test.ts +13 -4
- package/src/platforms/webex/commands/space.test.ts +12 -2
- package/src/platforms/webex/credential-manager.ts +1 -1
- package/src/platforms/webex/encryption.ts +53 -0
- package/src/platforms/webex/ensure-auth.test.ts +4 -0
- package/src/platforms/webex/ensure-auth.ts +27 -4
- package/src/platforms/webex/index.ts +2 -0
- package/src/platforms/webex/token-extractor.test.ts +327 -0
- package/src/platforms/webex/token-extractor.ts +460 -0
- package/src/platforms/webex/types.ts +8 -2
- package/src/platforms/webex/typings/node-jose.d.ts +27 -0
- package/src/platforms/whatsapp/client.ts +11 -7
- package/src/shared/utils/derived-key-cache.ts +1 -1
- package/src/shared/utils/error-handler.ts +4 -2
- package/src/shared/utils/stderr.ts +22 -0
|
@@ -8,6 +8,7 @@ import QRCode from 'qrcode'
|
|
|
8
8
|
|
|
9
9
|
import { handleError } from '@/shared/utils/error-handler'
|
|
10
10
|
import { formatOutput } from '@/shared/utils/output'
|
|
11
|
+
import { info } from '@/shared/utils/stderr'
|
|
11
12
|
|
|
12
13
|
import { LineClient } from '../client'
|
|
13
14
|
import { LineCredentialManager } from '../credential-manager'
|
|
@@ -85,9 +86,9 @@ async function loginAction(options: {
|
|
|
85
86
|
email: options.email,
|
|
86
87
|
password: options.password,
|
|
87
88
|
device,
|
|
88
|
-
|
|
89
|
+
onPincode: (pin) => {
|
|
89
90
|
if (interactive) {
|
|
90
|
-
|
|
91
|
+
info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
|
|
91
92
|
}
|
|
92
93
|
},
|
|
93
94
|
})
|
|
@@ -104,14 +105,14 @@ async function loginAction(options: {
|
|
|
104
105
|
await openQRInBrowser(url).catch(() => {})
|
|
105
106
|
try {
|
|
106
107
|
const qrAscii = await QRCode.toString(url, { type: 'terminal', small: true })
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
info('\nScan this QR code with the LINE mobile app:\n')
|
|
109
|
+
info(qrAscii)
|
|
109
110
|
} catch {
|
|
110
|
-
|
|
111
|
+
info(`\nOpen the QR code in the browser window, or scan this URL:\n${url}\n`)
|
|
111
112
|
}
|
|
112
113
|
},
|
|
113
114
|
onPincode: (pin) => {
|
|
114
|
-
|
|
115
|
+
info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
|
|
115
116
|
},
|
|
116
117
|
})
|
|
117
118
|
console.log(formatOutput(result, options.pretty))
|
|
@@ -39,12 +39,13 @@ describe('CLI Framework', () => {
|
|
|
39
39
|
describe('handleError utility', () => {
|
|
40
40
|
test('logs error as JSON and exits', () => {
|
|
41
41
|
const originalExit = process.exit
|
|
42
|
-
const
|
|
42
|
+
const originalWrite = process.stderr.write
|
|
43
43
|
let capturedOutput = ''
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
capturedOutput
|
|
47
|
-
|
|
45
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
46
|
+
capturedOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
|
|
47
|
+
return true
|
|
48
|
+
}) as typeof process.stderr.write
|
|
48
49
|
process.exit = (() => {
|
|
49
50
|
throw new Error('EXIT_CALLED')
|
|
50
51
|
}) as never
|
|
@@ -58,7 +59,7 @@ describe('CLI Framework', () => {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
process.stderr.write = originalWrite
|
|
62
63
|
process.exit = originalExit
|
|
63
64
|
})
|
|
64
65
|
})
|
|
@@ -75,13 +75,16 @@ describe('TokenExtractor', () => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
describe('extract', () => {
|
|
78
|
-
test('
|
|
79
|
-
//
|
|
78
|
+
test('returns empty array when Slack directory does not exist (falls back to browser)', async () => {
|
|
79
|
+
// given
|
|
80
80
|
const nonExistentPath = `/tmp/nonexistent-slack-${Date.now()}-${Math.random()}`
|
|
81
81
|
extractor = new TokenExtractor('darwin', nonExistentPath)
|
|
82
82
|
|
|
83
|
-
//
|
|
84
|
-
await
|
|
83
|
+
// when
|
|
84
|
+
const result = await extractor.extract()
|
|
85
|
+
|
|
86
|
+
// then
|
|
87
|
+
expect(result).toEqual([])
|
|
85
88
|
})
|
|
86
89
|
|
|
87
90
|
test('returns empty array when no tokens found', async () => {
|
|
@@ -430,12 +433,13 @@ describe('Error Handling', () => {
|
|
|
430
433
|
})
|
|
431
434
|
|
|
432
435
|
test('handles missing Slack installation gracefully', async () => {
|
|
433
|
-
//
|
|
436
|
+
// given — Slack is not installed
|
|
434
437
|
const nonExistentPath = `/tmp/nonexistent-slack-${Date.now()}-${Math.random()}`
|
|
435
438
|
const extractor = new TokenExtractor('darwin', nonExistentPath)
|
|
436
439
|
|
|
437
|
-
//
|
|
438
|
-
await
|
|
440
|
+
// when/then — falls back to browser profiles, returns empty array
|
|
441
|
+
const result = await extractor.extract()
|
|
442
|
+
expect(result).toEqual([])
|
|
439
443
|
})
|
|
440
444
|
|
|
441
445
|
test('handles empty Slack directory gracefully', async () => {
|
|
@@ -2,6 +2,7 @@ import { Command } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import { handleError } from '@/shared/utils/error-handler'
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
|
+
import { debug } from '@/shared/utils/stderr'
|
|
5
6
|
|
|
6
7
|
import { SlackClient, SlackError } from '../client'
|
|
7
8
|
import { CredentialManager } from '../credential-manager'
|
|
@@ -23,7 +24,7 @@ async function extractAction(options: {
|
|
|
23
24
|
if (options.unsafelyShowSecrets) {
|
|
24
25
|
options.debug = true
|
|
25
26
|
}
|
|
26
|
-
const debugLog = options.debug ? (msg: string) =>
|
|
27
|
+
const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
|
|
27
28
|
const extractor = new TokenExtractor(undefined, undefined, undefined, debugLog)
|
|
28
29
|
|
|
29
30
|
if (process.platform === 'darwin') {
|
|
@@ -44,15 +45,15 @@ async function extractAction(options: {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
if (options.debug) {
|
|
47
|
-
|
|
48
|
+
debug(`[debug] Slack directory: ${extractor.getSlackDir()}`)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const workspaces = await extractor.extract()
|
|
51
52
|
|
|
52
53
|
if (options.debug) {
|
|
53
|
-
|
|
54
|
+
debug(`[debug] Found ${workspaces.length} workspace(s)`)
|
|
54
55
|
for (const ws of workspaces) {
|
|
55
|
-
|
|
56
|
+
debug(`[debug] - ${formatCredentialDebug(ws, options.unsafelyShowSecrets)}`)
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -77,7 +78,7 @@ async function extractAction(options: {
|
|
|
77
78
|
const failureReasons: string[] = []
|
|
78
79
|
for (const ws of workspaces) {
|
|
79
80
|
if (options.debug) {
|
|
80
|
-
|
|
81
|
+
debug(`[debug] Testing credentials for ${ws.workspace_id}...`)
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
try {
|
|
@@ -89,7 +90,7 @@ async function extractAction(options: {
|
|
|
89
90
|
await credManager.setWorkspace(ws)
|
|
90
91
|
|
|
91
92
|
if (options.debug) {
|
|
92
|
-
|
|
93
|
+
debug(`[debug] ✓ Valid: ${authInfo.team} (${authInfo.user})`)
|
|
93
94
|
}
|
|
94
95
|
} catch (error) {
|
|
95
96
|
const code = error instanceof SlackError ? error.code : undefined
|
|
@@ -97,12 +98,12 @@ async function extractAction(options: {
|
|
|
97
98
|
failureReasons.push(code)
|
|
98
99
|
}
|
|
99
100
|
if (options.debug) {
|
|
100
|
-
|
|
101
|
+
debug(`[debug] ✗ Invalid: ${(error as Error).message}`)
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
if (options.debug) {
|
|
104
105
|
const domain = workspaceDomains[ws.workspace_id]
|
|
105
|
-
|
|
106
|
+
debug(
|
|
106
107
|
`[debug] Attempting web token refresh for ${ws.workspace_id}${domain ? ` (${domain}.slack.com)` : ''}...`,
|
|
107
108
|
)
|
|
108
109
|
}
|
|
@@ -114,10 +115,10 @@ async function extractAction(options: {
|
|
|
114
115
|
await credManager.setWorkspace(ws)
|
|
115
116
|
|
|
116
117
|
if (options.debug) {
|
|
117
|
-
|
|
118
|
+
debug(`[debug] ✓ Web refresh succeeded: ${refreshed.workspace_name}`)
|
|
118
119
|
}
|
|
119
120
|
} else if (options.debug) {
|
|
120
|
-
|
|
121
|
+
debug('[debug] ✗ Web refresh failed')
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
|
@@ -6,7 +6,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
|
6
6
|
import { tmpdir } from 'node:os'
|
|
7
7
|
import { join } from 'node:path'
|
|
8
8
|
|
|
9
|
-
import { TokenExtractor } from './token-extractor'
|
|
9
|
+
import { ExtractedWorkspace, TokenExtractor } from './token-extractor'
|
|
10
10
|
|
|
11
11
|
const tempDirs: string[] = []
|
|
12
12
|
|
|
@@ -790,3 +790,100 @@ describe('TokenExtractor getWorkspaceDomains', () => {
|
|
|
790
790
|
expect(domains).toEqual({ T111: 'acme-corp' })
|
|
791
791
|
})
|
|
792
792
|
})
|
|
793
|
+
|
|
794
|
+
describe('TokenExtractor browser fallback', () => {
|
|
795
|
+
test('extractFromBrowsers returns empty array when no browser profiles have tokens', async () => {
|
|
796
|
+
const slackDir = mkdtempSync(join(tmpdir(), 'slack-nonexistent-'))
|
|
797
|
+
tempDirs.push(slackDir)
|
|
798
|
+
rmSync(slackDir, { recursive: true, force: true })
|
|
799
|
+
|
|
800
|
+
const extractor = new TokenExtractor('darwin', slackDir)
|
|
801
|
+
const result = await extractor.extractFromBrowsers()
|
|
802
|
+
expect(result).toEqual([])
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
test('extract tries desktop before browser profiles', async () => {
|
|
806
|
+
// given — slackDir with LevelDB token data
|
|
807
|
+
const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-desktop-'))
|
|
808
|
+
tempDirs.push(slackDir)
|
|
809
|
+
|
|
810
|
+
const hex64 = 'a'.repeat(64)
|
|
811
|
+
const token = `xoxc-1111111111-2222222222-3333333333-${hex64}`
|
|
812
|
+
const leveldbDir = join(slackDir, 'Local Storage', 'leveldb')
|
|
813
|
+
mkdirSync(leveldbDir, { recursive: true })
|
|
814
|
+
writeFileSync(join(leveldbDir, '000001.log'), `"${token}"T12345678"name":"desktop-workspace"`)
|
|
815
|
+
|
|
816
|
+
const extractFromBrowsersSpy = spyOn(
|
|
817
|
+
TokenExtractor.prototype as any,
|
|
818
|
+
'extractFromBrowsers',
|
|
819
|
+
).mockResolvedValue([])
|
|
820
|
+
|
|
821
|
+
// when
|
|
822
|
+
const extractor = new TokenExtractor('darwin', slackDir)
|
|
823
|
+
const result = await extractor.extract()
|
|
824
|
+
|
|
825
|
+
// then — desktop extraction succeeded, browser not called
|
|
826
|
+
expect(result.length).toBeGreaterThan(0)
|
|
827
|
+
expect(extractFromBrowsersSpy).not.toHaveBeenCalled()
|
|
828
|
+
|
|
829
|
+
extractFromBrowsersSpy.mockRestore()
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
test('extract falls back to browser profiles when desktop has no tokens', async () => {
|
|
833
|
+
// given — empty slackDir (no tokens)
|
|
834
|
+
const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-browser-'))
|
|
835
|
+
tempDirs.push(slackDir)
|
|
836
|
+
|
|
837
|
+
const hex64 = 'b'.repeat(64)
|
|
838
|
+
const browserToken = `xoxc-9999999999-8888888888-7777777777-${hex64}`
|
|
839
|
+
const browserWorkspace: ExtractedWorkspace = {
|
|
840
|
+
workspace_id: 'T99999999',
|
|
841
|
+
workspace_name: 'browser-workspace',
|
|
842
|
+
token: browserToken,
|
|
843
|
+
cookie: '',
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const extractFromBrowsersSpy = spyOn(
|
|
847
|
+
TokenExtractor.prototype as any,
|
|
848
|
+
'extractFromBrowsers',
|
|
849
|
+
).mockResolvedValue([browserWorkspace])
|
|
850
|
+
|
|
851
|
+
// when
|
|
852
|
+
const extractor = new TokenExtractor('darwin', slackDir)
|
|
853
|
+
const result = await extractor.extract()
|
|
854
|
+
|
|
855
|
+
// then — browser fallback used
|
|
856
|
+
expect(extractFromBrowsersSpy).toHaveBeenCalled()
|
|
857
|
+
expect(result).toEqual([browserWorkspace])
|
|
858
|
+
|
|
859
|
+
extractFromBrowsersSpy.mockRestore()
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
test('extract falls back to browser when slackDir does not exist', async () => {
|
|
863
|
+
// given — non-existent slackDir
|
|
864
|
+
const slackDir = '/nonexistent/slack/dir'
|
|
865
|
+
const hex64 = 'c'.repeat(64)
|
|
866
|
+
const browserToken = `xoxc-1234567890-0987654321-1122334455-${hex64}`
|
|
867
|
+
const browserWorkspace: ExtractedWorkspace = {
|
|
868
|
+
workspace_id: 'T11111111',
|
|
869
|
+
workspace_name: 'browser-ws',
|
|
870
|
+
token: browserToken,
|
|
871
|
+
cookie: 'xoxd-browser-cookie',
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const extractFromBrowsersSpy = spyOn(
|
|
875
|
+
TokenExtractor.prototype as any,
|
|
876
|
+
'extractFromBrowsers',
|
|
877
|
+
).mockResolvedValue([browserWorkspace])
|
|
878
|
+
|
|
879
|
+
// when
|
|
880
|
+
const extractor = new TokenExtractor('darwin', slackDir)
|
|
881
|
+
const result = await extractor.extract()
|
|
882
|
+
|
|
883
|
+
// then
|
|
884
|
+
expect(extractFromBrowsersSpy).toHaveBeenCalled()
|
|
885
|
+
expect(result).toEqual([browserWorkspace])
|
|
886
|
+
|
|
887
|
+
extractFromBrowsersSpy.mockRestore()
|
|
888
|
+
})
|
|
889
|
+
})
|
|
@@ -10,6 +10,58 @@ import { ClassicLevel } from 'classic-level'
|
|
|
10
10
|
import { DerivedKeyCache } from '@/shared/utils/derived-key-cache'
|
|
11
11
|
import { lookupLinuxKeyringPassword } from '@/shared/utils/linux-keyring'
|
|
12
12
|
|
|
13
|
+
interface BrowserConfig {
|
|
14
|
+
name: string
|
|
15
|
+
darwin: string
|
|
16
|
+
linux: string
|
|
17
|
+
win32: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const BROWSERS: BrowserConfig[] = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Chrome',
|
|
23
|
+
darwin: join('Google', 'Chrome'),
|
|
24
|
+
linux: 'google-chrome',
|
|
25
|
+
win32: join('Google', 'Chrome', 'User Data'),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Chrome Canary',
|
|
29
|
+
darwin: join('Google', 'Chrome Canary'),
|
|
30
|
+
linux: 'google-chrome-unstable',
|
|
31
|
+
win32: join('Google', 'Chrome SxS', 'User Data'),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Edge',
|
|
35
|
+
darwin: 'Microsoft Edge',
|
|
36
|
+
linux: 'microsoft-edge',
|
|
37
|
+
win32: join('Microsoft', 'Edge', 'User Data'),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Arc',
|
|
41
|
+
darwin: join('Arc', 'User Data'),
|
|
42
|
+
linux: '',
|
|
43
|
+
win32: join('Arc', 'User Data'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Brave',
|
|
47
|
+
darwin: join('BraveSoftware', 'Brave-Browser'),
|
|
48
|
+
linux: join('BraveSoftware', 'Brave-Browser'),
|
|
49
|
+
win32: join('BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Vivaldi',
|
|
53
|
+
darwin: 'Vivaldi',
|
|
54
|
+
linux: 'vivaldi',
|
|
55
|
+
win32: join('Vivaldi', 'User Data'),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Chromium',
|
|
59
|
+
darwin: 'Chromium',
|
|
60
|
+
linux: 'chromium',
|
|
61
|
+
win32: join('Chromium', 'User Data'),
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
|
|
13
65
|
const require = createRequire(import.meta.url)
|
|
14
66
|
|
|
15
67
|
export interface ExtractedWorkspace {
|
|
@@ -177,40 +229,300 @@ export class TokenExtractor {
|
|
|
177
229
|
}
|
|
178
230
|
|
|
179
231
|
async extract(): Promise<ExtractedWorkspace[]> {
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
232
|
+
if (existsSync(this.slackDir)) {
|
|
233
|
+
await this.getDerivedKeyAsync()
|
|
234
|
+
|
|
235
|
+
const tokens = await this.extractTokensFromLevelDB()
|
|
236
|
+
if (tokens.length > 0) {
|
|
237
|
+
const cookie = await this.extractCookieFromSQLite()
|
|
238
|
+
|
|
239
|
+
if (!cookie && this.usedCachedKey) {
|
|
240
|
+
await this.clearKeyCache()
|
|
241
|
+
this.cachedKey = this.getDerivedKeyFromKeychain()
|
|
242
|
+
if (this.cachedKey) {
|
|
243
|
+
await this.keyCache.set('slack', this.cachedKey)
|
|
244
|
+
const retryCookie = await this.extractCookieFromSQLite()
|
|
245
|
+
return tokens.map((t) => ({
|
|
246
|
+
workspace_id: t.teamId,
|
|
247
|
+
workspace_name: t.teamName,
|
|
248
|
+
token: t.token,
|
|
249
|
+
cookie: retryCookie,
|
|
250
|
+
}))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
192
253
|
|
|
193
|
-
if (!cookie && this.usedCachedKey) {
|
|
194
|
-
await this.clearKeyCache()
|
|
195
|
-
this.cachedKey = this.getDerivedKeyFromKeychain()
|
|
196
|
-
if (this.cachedKey) {
|
|
197
|
-
await this.keyCache.set('slack', this.cachedKey)
|
|
198
|
-
const retryCookie = await this.extractCookieFromSQLite()
|
|
199
254
|
return tokens.map((t) => ({
|
|
200
255
|
workspace_id: t.teamId,
|
|
201
256
|
workspace_name: t.teamName,
|
|
202
257
|
token: t.token,
|
|
203
|
-
cookie:
|
|
258
|
+
cookie: cookie,
|
|
204
259
|
}))
|
|
205
260
|
}
|
|
206
261
|
}
|
|
207
262
|
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
263
|
+
return this.extractFromBrowsers()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async extractFromBrowsers(): Promise<ExtractedWorkspace[]> {
|
|
267
|
+
const results: ExtractedWorkspace[] = []
|
|
268
|
+
const seenTokens = new Set<string>()
|
|
269
|
+
|
|
270
|
+
for (const browser of BROWSERS) {
|
|
271
|
+
const browserBase = this.getBrowserBasePath(browser)
|
|
272
|
+
if (!browserBase) continue
|
|
273
|
+
|
|
274
|
+
const profileDirs = this.discoverBrowserProfileDirs(browserBase)
|
|
275
|
+
for (const profileDir of profileDirs) {
|
|
276
|
+
const leveldbDir = join(profileDir, 'Local Storage', 'leveldb')
|
|
277
|
+
if (!existsSync(leveldbDir)) continue
|
|
278
|
+
|
|
279
|
+
let tokenInfos: TokenInfo[]
|
|
280
|
+
try {
|
|
281
|
+
tokenInfos = await this.extractFromLevelDB(leveldbDir, 'local-storage')
|
|
282
|
+
} catch {
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (tokenInfos.length === 0) continue
|
|
287
|
+
|
|
288
|
+
const cookie = await this.extractCookieFromBrowserProfile(profileDir, browserBase)
|
|
289
|
+
|
|
290
|
+
for (const t of tokenInfos) {
|
|
291
|
+
if (seenTokens.has(t.token)) continue
|
|
292
|
+
seenTokens.add(t.token)
|
|
293
|
+
results.push({
|
|
294
|
+
workspace_id: t.teamId,
|
|
295
|
+
workspace_name: t.teamName,
|
|
296
|
+
token: t.token,
|
|
297
|
+
cookie,
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return results
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private getBrowserBasePath(browser: BrowserConfig): string | null {
|
|
307
|
+
let relative: string
|
|
308
|
+
|
|
309
|
+
switch (this.platform) {
|
|
310
|
+
case 'darwin':
|
|
311
|
+
relative = browser.darwin
|
|
312
|
+
if (!relative) return null
|
|
313
|
+
return join(homedir(), 'Library', 'Application Support', relative)
|
|
314
|
+
case 'linux':
|
|
315
|
+
relative = browser.linux
|
|
316
|
+
if (!relative) return null
|
|
317
|
+
return join(homedir(), '.config', relative)
|
|
318
|
+
case 'win32':
|
|
319
|
+
relative = browser.win32
|
|
320
|
+
if (!relative) return null
|
|
321
|
+
return join(
|
|
322
|
+
process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'),
|
|
323
|
+
relative,
|
|
324
|
+
)
|
|
325
|
+
default:
|
|
326
|
+
return null
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private discoverBrowserProfileDirs(browserBase: string): string[] {
|
|
331
|
+
const dirs: string[] = []
|
|
332
|
+
|
|
333
|
+
dirs.push(join(browserBase, 'Default'))
|
|
334
|
+
|
|
335
|
+
if (!existsSync(browserBase)) return dirs
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const entries = readdirSync(browserBase, { withFileTypes: true })
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
if (!entry.isDirectory()) continue
|
|
341
|
+
if (!/^Profile \d+$/i.test(entry.name)) continue
|
|
342
|
+
dirs.push(join(browserBase, entry.name))
|
|
343
|
+
}
|
|
344
|
+
} catch {}
|
|
345
|
+
|
|
346
|
+
return dirs
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private async extractCookieFromBrowserProfile(profileDir: string, browserBase: string): Promise<string> {
|
|
350
|
+
const cookiePaths = [
|
|
351
|
+
join(profileDir, 'Cookies'),
|
|
352
|
+
join(profileDir, 'Network', 'Cookies'),
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
for (const cookiePath of cookiePaths) {
|
|
356
|
+
if (!existsSync(cookiePath)) continue
|
|
357
|
+
|
|
358
|
+
const cookie = await this.extractCookieFromBrowserSQLite(cookiePath, browserBase)
|
|
359
|
+
if (cookie) return cookie
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return ''
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async extractCookieFromBrowserSQLite(cookiesPath: string, browserBase: string): Promise<string> {
|
|
366
|
+
const tempDbPath = join(tmpdir(), `slack-browser-cookies-${Date.now()}-${Math.random().toString(36).slice(2)}.db`)
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
copyFileSync(cookiesPath, tempDbPath)
|
|
370
|
+
} catch {
|
|
371
|
+
return ''
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const sql = `SELECT value, encrypted_value
|
|
376
|
+
FROM cookies
|
|
377
|
+
WHERE name = 'd' AND host_key LIKE '%slack.com%'
|
|
378
|
+
ORDER BY last_access_utc DESC
|
|
379
|
+
LIMIT 1`
|
|
380
|
+
|
|
381
|
+
type CookieRow = {
|
|
382
|
+
value?: string
|
|
383
|
+
encrypted_value?: Uint8Array | Buffer
|
|
384
|
+
} | null
|
|
385
|
+
|
|
386
|
+
let row: CookieRow
|
|
387
|
+
if (typeof globalThis.Bun !== 'undefined') {
|
|
388
|
+
const { Database } = require('bun:sqlite')
|
|
389
|
+
const db = new Database(tempDbPath, { readonly: true })
|
|
390
|
+
row = db.query(sql).get() as CookieRow
|
|
391
|
+
db.close()
|
|
392
|
+
} else {
|
|
393
|
+
const Database = require('better-sqlite3')
|
|
394
|
+
const db = new Database(tempDbPath, { readonly: true })
|
|
395
|
+
row = db.prepare(sql).get() as CookieRow
|
|
396
|
+
db.close()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!row) return ''
|
|
400
|
+
|
|
401
|
+
if (row.value?.startsWith('xoxd-')) return row.value
|
|
402
|
+
|
|
403
|
+
if (row.encrypted_value && row.encrypted_value.length > 0) {
|
|
404
|
+
return this.decryptBrowserCookieForSlack(Buffer.from(row.encrypted_value), browserBase) ?? ''
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return ''
|
|
408
|
+
} catch {
|
|
409
|
+
return ''
|
|
410
|
+
} finally {
|
|
411
|
+
try {
|
|
412
|
+
rmSync(tempDbPath, { force: true })
|
|
413
|
+
} catch {}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private decryptBrowserCookieForSlack(encrypted: Buffer, browserBase: string): string | null {
|
|
418
|
+
const str = encrypted.toString('utf8')
|
|
419
|
+
if (str.startsWith('xoxd-')) return str
|
|
420
|
+
|
|
421
|
+
const prefix = encrypted.length > 3 ? encrypted.subarray(0, 3).toString() : ''
|
|
422
|
+
|
|
423
|
+
if (prefix === 'v10') {
|
|
424
|
+
if (this.platform === 'win32') {
|
|
425
|
+
return this.decryptBrowserV10CookieWindows(encrypted, browserBase)
|
|
426
|
+
}
|
|
427
|
+
if (this.platform === 'linux') {
|
|
428
|
+
return this.decryptV10CookieLinux(encrypted)
|
|
429
|
+
}
|
|
430
|
+
return this.decryptBrowserV10CookieMac(encrypted)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (prefix === 'v11') {
|
|
434
|
+
if (this.platform === 'linux') {
|
|
435
|
+
return this.decryptV11CookieLinux(encrypted)
|
|
436
|
+
}
|
|
437
|
+
if (this.platform === 'darwin') {
|
|
438
|
+
return this.decryptBrowserV10CookieMac(encrypted)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (this.platform === 'win32' && encrypted.length > 0) {
|
|
443
|
+
const decrypted = this.decryptDPAPI(encrypted)
|
|
444
|
+
if (decrypted) {
|
|
445
|
+
const text = decrypted.toString('utf8')
|
|
446
|
+
const match = text.match(/xoxd-[A-Za-z0-9%]+/)
|
|
447
|
+
return match ? match[0] : null
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private decryptBrowserV10CookieMac(encrypted: Buffer): string | null {
|
|
455
|
+
const keychainVariants = [
|
|
456
|
+
{ service: 'Chrome Safe Storage', account: 'Chrome' },
|
|
457
|
+
{ service: 'Chrome Canary Safe Storage', account: 'Chrome Canary' },
|
|
458
|
+
{ service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' },
|
|
459
|
+
{ service: 'Arc Safe Storage', account: 'Arc' },
|
|
460
|
+
{ service: 'Brave Safe Storage', account: 'Brave' },
|
|
461
|
+
{ service: 'Vivaldi Safe Storage', account: 'Vivaldi' },
|
|
462
|
+
{ service: 'Chromium Safe Storage', account: 'Chromium' },
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
for (const variant of keychainVariants) {
|
|
466
|
+
try {
|
|
467
|
+
const password = execSync(
|
|
468
|
+
`security find-generic-password -s "${variant.service}" -a "${variant.account}" -w 2>/dev/null`,
|
|
469
|
+
{ encoding: 'utf8' },
|
|
470
|
+
).trim()
|
|
471
|
+
|
|
472
|
+
const key = pbkdf2Sync(password, 'saltysalt', 1003, 16, 'sha1')
|
|
473
|
+
const result = this.decryptLinuxCookieWithKey(encrypted, key)
|
|
474
|
+
if (result) return result
|
|
475
|
+
} catch {}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return null
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private decryptBrowserV10CookieWindows(encrypted: Buffer, browserBase: string): string | null {
|
|
482
|
+
try {
|
|
483
|
+
const masterKey = this.getBrowserWindowsMasterKey(browserBase)
|
|
484
|
+
if (!masterKey) {
|
|
485
|
+
const decrypted = this.decryptDPAPI(encrypted.subarray(3))
|
|
486
|
+
if (!decrypted) return null
|
|
487
|
+
const text = decrypted.toString('utf8')
|
|
488
|
+
const match = text.match(/xoxd-[A-Za-z0-9%]+/)
|
|
489
|
+
return match ? match[0] : null
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const nonce = encrypted.subarray(3, 3 + 12)
|
|
493
|
+
const ciphertextWithTag = encrypted.subarray(3 + 12)
|
|
494
|
+
const tag = ciphertextWithTag.subarray(ciphertextWithTag.length - 16)
|
|
495
|
+
const ciphertext = ciphertextWithTag.subarray(0, ciphertextWithTag.length - 16)
|
|
496
|
+
|
|
497
|
+
const decipher = createDecipheriv('aes-256-gcm', masterKey, nonce)
|
|
498
|
+
decipher.setAuthTag(tag)
|
|
499
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8')
|
|
500
|
+
|
|
501
|
+
const match = decrypted.match(/xoxd-[A-Za-z0-9%]+/)
|
|
502
|
+
return match ? match[0] : null
|
|
503
|
+
} catch {
|
|
504
|
+
return null
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private getBrowserWindowsMasterKey(browserBase: string): Buffer | null {
|
|
509
|
+
try {
|
|
510
|
+
const localStatePath = join(browserBase, 'Local State')
|
|
511
|
+
if (!existsSync(localStatePath)) return null
|
|
512
|
+
|
|
513
|
+
const localState = JSON.parse(readFileSync(localStatePath, 'utf8')) as {
|
|
514
|
+
os_crypt?: { encrypted_key?: string }
|
|
515
|
+
}
|
|
516
|
+
const encryptedKeyB64 = localState?.os_crypt?.encrypted_key
|
|
517
|
+
if (!encryptedKeyB64) return null
|
|
518
|
+
|
|
519
|
+
const encryptedKey = Buffer.from(encryptedKeyB64, 'base64')
|
|
520
|
+
if (encryptedKey.subarray(0, 5).toString() !== 'DPAPI') return null
|
|
521
|
+
|
|
522
|
+
return this.decryptDPAPI(encryptedKey.subarray(5))
|
|
523
|
+
} catch {
|
|
524
|
+
return null
|
|
525
|
+
}
|
|
214
526
|
}
|
|
215
527
|
|
|
216
528
|
private async extractTokensFromLevelDB(): Promise<TokenInfo[]> {
|
|
@@ -845,7 +1157,7 @@ export class TokenExtractor {
|
|
|
845
1157
|
}
|
|
846
1158
|
|
|
847
1159
|
private decryptV11CookieLinux(encrypted: Buffer): string | null {
|
|
848
|
-
const appNames = ['Slack', 'slack']
|
|
1160
|
+
const appNames = ['Slack', 'slack', 'Chrome', 'chrome', 'Chromium', 'chromium', 'Microsoft Edge', 'Brave', 'Vivaldi']
|
|
849
1161
|
for (const appName of appNames) {
|
|
850
1162
|
try {
|
|
851
1163
|
const keyringPassword = this.getLinuxKeyringPassword(appName)
|