slkcli 0.1.2 → 0.1.3
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/README.md +5 -4
- package/package.json +1 -1
- package/src/auth.js +40 -7
package/README.md
CHANGED
|
@@ -145,9 +145,9 @@ On first run, macOS will show a Keychain dialog asking whether to allow access t
|
|
|
145
145
|
|
|
146
146
|
### How it works
|
|
147
147
|
|
|
148
|
-
1. **Cookie decryption** — Reads the encrypted `d` cookie from Slack's SQLite cookie store (
|
|
148
|
+
1. **Cookie decryption** — Reads the encrypted `d` cookie from Slack's SQLite cookie store (`Cookies` file). Decrypts it using the "Slack Safe Storage" key from the macOS Keychain via PBKDF2 + AES-128-CBC. Supports both direct-download and Mac App Store keychain account names.
|
|
149
149
|
|
|
150
|
-
2. **Token extraction** — Scans Slack's LevelDB storage (
|
|
150
|
+
2. **Token extraction** — Scans Slack's LevelDB storage (`Local Storage/leveldb/`) for `xoxc-` session tokens. Uses both direct regex scanning and a Python fallback for Snappy-compressed entries. The Slack data directory is auto-detected (direct download or App Store sandbox).
|
|
151
151
|
|
|
152
152
|
3. **Validation** — Tests each candidate token against `auth.test` with the decrypted cookie. The first valid pair is used.
|
|
153
153
|
|
|
@@ -177,8 +177,8 @@ Validated tokens are cached to avoid re-extracting on every invocation:
|
|
|
177
177
|
| Data | Source | Purpose |
|
|
178
178
|
|------|--------|---------|
|
|
179
179
|
| Keychain password | `security find-generic-password -s "Slack Safe Storage"` | Derive AES key for cookie decryption |
|
|
180
|
-
| Encrypted cookie |
|
|
181
|
-
| Session token |
|
|
180
|
+
| Encrypted cookie | `<slack-data-dir>/Cookies` (SQLite) | Decrypt the `d` session cookie (`xoxd-`) |
|
|
181
|
+
| Session token | `<slack-data-dir>/Local Storage/leveldb/` | Extract `xoxc-` token |
|
|
182
182
|
|
|
183
183
|
## Agent usage patterns
|
|
184
184
|
|
|
@@ -241,6 +241,7 @@ npm link # symlink globally for development
|
|
|
241
241
|
## Notes
|
|
242
242
|
|
|
243
243
|
- **macOS only** — uses Keychain and Electron storage paths specific to macOS.
|
|
244
|
+
- **Both Slack variants supported** — works with the direct download (`~/Library/Application Support/Slack/`) and the Mac App Store version (`~/Library/Containers/com.tinyspeck.slackmacgap/.../Slack/`). The correct path is auto-detected at runtime.
|
|
244
245
|
- **Slack desktop app required** — must be installed and logged in. The app does not need to be running for cached tokens.
|
|
245
246
|
- **Zero dependencies** — uses only Node.js built-in modules (`crypto`, `fs`, `child_process`, `fetch`).
|
|
246
247
|
- **Session-based** — uses `xoxc-` tokens (user session), not bot tokens. This means you act as yourself.
|
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -14,7 +14,27 @@ import { pbkdf2Sync } from "crypto";
|
|
|
14
14
|
|
|
15
15
|
import { existsSync, mkdirSync } from "fs";
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const SLACK_DIR_DIRECT = join(homedir(), "Library", "Application Support", "Slack");
|
|
18
|
+
const SLACK_DIR_APPSTORE = join(
|
|
19
|
+
homedir(),
|
|
20
|
+
"Library", "Containers", "com.tinyspeck.slackmacgap",
|
|
21
|
+
"Data", "Library", "Application Support", "Slack"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
function resolveSlackDir() {
|
|
25
|
+
if (existsSync(SLACK_DIR_DIRECT)) return SLACK_DIR_DIRECT;
|
|
26
|
+
if (existsSync(SLACK_DIR_APPSTORE)) return SLACK_DIR_APPSTORE;
|
|
27
|
+
console.error(
|
|
28
|
+
"Could not find Slack data directory.\n" +
|
|
29
|
+
"Checked:\n" +
|
|
30
|
+
` ${SLACK_DIR_DIRECT}\n` +
|
|
31
|
+
` ${SLACK_DIR_APPSTORE}\n` +
|
|
32
|
+
"Is Slack installed?"
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const SLACK_DIR = resolveSlackDir();
|
|
18
38
|
const LEVELDB_DIR = join(SLACK_DIR, "Local Storage", "leveldb");
|
|
19
39
|
const COOKIES_DB = join(SLACK_DIR, "Cookies");
|
|
20
40
|
const CACHE_DIR = join(homedir(), ".local", "slk");
|
|
@@ -23,11 +43,24 @@ const TOKEN_CACHE = join(CACHE_DIR, "token-cache.json");
|
|
|
23
43
|
let cachedCreds = null;
|
|
24
44
|
|
|
25
45
|
function getKeychainKey() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
// Mac App Store Slack uses account "Slack App Store Key", direct download uses "Slack"
|
|
47
|
+
const accounts = SLACK_DIR === SLACK_DIR_APPSTORE
|
|
48
|
+
? ["Slack App Store Key", "Slack"]
|
|
49
|
+
: ["Slack", "Slack App Store Key"];
|
|
50
|
+
|
|
51
|
+
for (const account of accounts) {
|
|
52
|
+
try {
|
|
53
|
+
return Buffer.from(
|
|
54
|
+
execSync(
|
|
55
|
+
`security find-generic-password -s "Slack Safe Storage" -a "${account}" -w`,
|
|
56
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
57
|
+
).trim()
|
|
58
|
+
);
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.error("Could not find Slack Safe Storage key in Keychain.");
|
|
63
|
+
process.exit(1);
|
|
31
64
|
}
|
|
32
65
|
|
|
33
66
|
function decryptCookie() {
|
|
@@ -111,7 +144,7 @@ function extractToken() {
|
|
|
111
144
|
try {
|
|
112
145
|
const pyResult = spawnSync("python3", ["-c", `
|
|
113
146
|
import os, re
|
|
114
|
-
path =
|
|
147
|
+
path = ${JSON.stringify(LEVELDB_DIR)}
|
|
115
148
|
for f in os.listdir(path):
|
|
116
149
|
if not (f.endswith(".ldb") or f.endswith(".log")): continue
|
|
117
150
|
data = open(os.path.join(path, f), "rb").read()
|