bitwarden-cli-bio 1.0.0 → 1.1.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/README.md +4 -1
- package/dist/index.js +59 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,10 @@ Everything else triggers biometric unlock if the vault is locked.
|
|
|
79
79
|
| Variable | Description |
|
|
80
80
|
|----------|-------------|
|
|
81
81
|
| `BW_SESSION` | Already set? `bwbio` passes through to `bw` directly |
|
|
82
|
-
| `
|
|
82
|
+
| `BW_QUIET` | Set to `true` to suppress all biometric-related messages |
|
|
83
|
+
| `BW_NOINTERACTION` | Set to `true` to skip biometric unlock (requires user interaction) |
|
|
84
|
+
| `BWBIO_VERBOSE` | Set to `true` for verbose logging |
|
|
85
|
+
| `BWBIO_DEBUG` | Set to `true` for raw IPC message dumps |
|
|
83
86
|
| `BWBIO_IPC_SOCKET_PATH` | Override the IPC socket path (advanced) |
|
|
84
87
|
|
|
85
88
|
## Platforms
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import * as fs from "fs";
|
|
|
12
12
|
import * as net from "net";
|
|
13
13
|
import * as os from "os";
|
|
14
14
|
import * as path from "path";
|
|
15
|
-
var DEBUG = process.env.BWBIO_DEBUG === "
|
|
15
|
+
var DEBUG = process.env.BWBIO_DEBUG === "true";
|
|
16
16
|
var IpcSocketService = class {
|
|
17
17
|
socket = null;
|
|
18
18
|
messageBuffer = Buffer.alloc(0);
|
|
@@ -220,7 +220,7 @@ import * as crypto2 from "crypto";
|
|
|
220
220
|
var MESSAGE_VALID_TIMEOUT = 10 * 1e3;
|
|
221
221
|
var DEFAULT_TIMEOUT = 10 * 1e3;
|
|
222
222
|
var USER_INTERACTION_TIMEOUT = 60 * 1e3;
|
|
223
|
-
var DEBUG2 = process.env.BWBIO_DEBUG === "
|
|
223
|
+
var DEBUG2 = process.env.BWBIO_DEBUG === "true";
|
|
224
224
|
var BiometricsCommands = {
|
|
225
225
|
AuthenticateWithBiometrics: "authenticateWithBiometrics",
|
|
226
226
|
GetBiometricsStatus: "getBiometricsStatus",
|
|
@@ -676,6 +676,18 @@ var NativeMessagingClient = class {
|
|
|
676
676
|
}
|
|
677
677
|
};
|
|
678
678
|
|
|
679
|
+
// src/log.ts
|
|
680
|
+
function log(message) {
|
|
681
|
+
if (process.env.BW_QUIET !== "true") {
|
|
682
|
+
console.error(message);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function logVerbose(message) {
|
|
686
|
+
if (process.env.BWBIO_VERBOSE === "true" && process.env.BW_QUIET !== "true") {
|
|
687
|
+
console.error(message);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
679
691
|
// src/session-storage.ts
|
|
680
692
|
import * as crypto3 from "crypto";
|
|
681
693
|
import * as fs2 from "fs";
|
|
@@ -754,51 +766,53 @@ function storeUserKeyForSession(userKeyB64, userId, sessionKey) {
|
|
|
754
766
|
}
|
|
755
767
|
|
|
756
768
|
// src/biometrics.ts
|
|
769
|
+
function getBiometricMethodName() {
|
|
770
|
+
switch (process.platform) {
|
|
771
|
+
case "darwin":
|
|
772
|
+
return "Touch ID";
|
|
773
|
+
case "win32":
|
|
774
|
+
return "Windows Hello";
|
|
775
|
+
default:
|
|
776
|
+
return "Polkit";
|
|
777
|
+
}
|
|
778
|
+
}
|
|
757
779
|
function generateAppId() {
|
|
758
780
|
return `bwbio-${crypto4.randomUUID()}`;
|
|
759
781
|
}
|
|
760
782
|
async function attemptBiometricUnlock(options = {}) {
|
|
761
783
|
const userId = options.userId || getActiveUserId();
|
|
762
784
|
if (!userId) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
};
|
|
785
|
+
logVerbose(
|
|
786
|
+
"Biometric unlock unavailable: No user ID available - please log in first"
|
|
787
|
+
);
|
|
788
|
+
return { success: false };
|
|
767
789
|
}
|
|
768
790
|
const appId = generateAppId();
|
|
769
791
|
const client = new NativeMessagingClient(appId, userId);
|
|
770
792
|
try {
|
|
771
793
|
const available = await client.isDesktopAppAvailable();
|
|
772
794
|
if (!available) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
if (options.verbose) {
|
|
779
|
-
console.error("Connecting to Bitwarden Desktop...");
|
|
795
|
+
logVerbose(
|
|
796
|
+
"Biometric unlock unavailable: Bitwarden Desktop app is not running"
|
|
797
|
+
);
|
|
798
|
+
return { success: false };
|
|
780
799
|
}
|
|
800
|
+
logVerbose("Connecting to Bitwarden Desktop...");
|
|
781
801
|
await client.connect();
|
|
782
|
-
|
|
783
|
-
console.error("Checking biometrics status...");
|
|
784
|
-
}
|
|
802
|
+
logVerbose("Checking biometrics status...");
|
|
785
803
|
const userStatus = await client.getBiometricsStatusForUser(userId);
|
|
786
804
|
if (userStatus !== 0 /* Available */) {
|
|
787
805
|
const statusName = BiometricsStatus[userStatus] || `Unknown(${userStatus})`;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
error: `Biometrics not available: ${statusName}`
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
if (options.verbose) {
|
|
794
|
-
console.error("Requesting biometric authentication...");
|
|
806
|
+
logVerbose(`Biometric unlock unavailable: ${statusName}`);
|
|
807
|
+
return { success: false };
|
|
795
808
|
}
|
|
809
|
+
log(
|
|
810
|
+
`Authenticate with ${getBiometricMethodName()} on Desktop app to continue...`
|
|
811
|
+
);
|
|
796
812
|
const userKey = await client.unlockWithBiometricsForUser(userId);
|
|
797
813
|
if (!userKey) {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
error: "Biometric unlock was denied or failed"
|
|
801
|
-
};
|
|
814
|
+
log("Biometric unlock was denied or failed");
|
|
815
|
+
return { success: false };
|
|
802
816
|
}
|
|
803
817
|
return {
|
|
804
818
|
success: true,
|
|
@@ -807,10 +821,8 @@ async function attemptBiometricUnlock(options = {}) {
|
|
|
807
821
|
};
|
|
808
822
|
} catch (err) {
|
|
809
823
|
const error = err instanceof Error ? err.message : String(err);
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
error
|
|
813
|
-
};
|
|
824
|
+
log(`Biometric unlock failed: ${error}`);
|
|
825
|
+
return { success: false };
|
|
814
826
|
} finally {
|
|
815
827
|
client.disconnect();
|
|
816
828
|
}
|
|
@@ -843,6 +855,12 @@ function isPassthroughCommand(args2) {
|
|
|
843
855
|
}
|
|
844
856
|
|
|
845
857
|
// src/main.ts
|
|
858
|
+
function writeLn(s) {
|
|
859
|
+
if (process.env.BW_QUIET !== "true") {
|
|
860
|
+
process.stdout.write(`${s}
|
|
861
|
+
`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
846
864
|
function getBwPath() {
|
|
847
865
|
return "bw";
|
|
848
866
|
}
|
|
@@ -870,24 +888,24 @@ async function executeBw(args2, sessionKey) {
|
|
|
870
888
|
async function handleUnlock(args2, sessionKey) {
|
|
871
889
|
const isRaw = args2.includes("--raw");
|
|
872
890
|
if (isRaw) {
|
|
873
|
-
|
|
891
|
+
writeLn(sessionKey);
|
|
874
892
|
} else {
|
|
875
|
-
|
|
876
|
-
|
|
893
|
+
writeLn(`export BW_SESSION="${sessionKey}"`);
|
|
894
|
+
writeLn(`# Run this command to set the session: eval $(bwbio unlock)`);
|
|
877
895
|
}
|
|
878
896
|
return 0;
|
|
879
897
|
}
|
|
880
898
|
async function main(args2) {
|
|
881
|
-
if (
|
|
882
|
-
|
|
899
|
+
if (args2.includes("--quiet")) {
|
|
900
|
+
process.env.BW_QUIET = "true";
|
|
901
|
+
}
|
|
902
|
+
if (args2.includes("--nointeraction")) {
|
|
903
|
+
process.env.BW_NOINTERACTION = "true";
|
|
883
904
|
}
|
|
884
|
-
if (isPassthroughCommand(args2)) {
|
|
905
|
+
if (process.env.BW_SESSION || process.env.BW_NOINTERACTION === "true" || isPassthroughCommand(args2)) {
|
|
885
906
|
return executeBw(args2);
|
|
886
907
|
}
|
|
887
|
-
|
|
888
|
-
const result = await attemptBiometricUnlock({
|
|
889
|
-
verbose: process.env.BWBIO_VERBOSE === "1"
|
|
890
|
-
});
|
|
908
|
+
const result = await attemptBiometricUnlock();
|
|
891
909
|
if (result.success) {
|
|
892
910
|
const sessionKey = generateSessionKey();
|
|
893
911
|
storeUserKeyForSession(result.userKeyB64, result.userId, sessionKey);
|
|
@@ -896,9 +914,6 @@ async function main(args2) {
|
|
|
896
914
|
}
|
|
897
915
|
return executeBw(args2, sessionKey);
|
|
898
916
|
}
|
|
899
|
-
console.error(
|
|
900
|
-
`Biometric unlock unavailable: ${result.error}. Falling back to CLI...`
|
|
901
|
-
);
|
|
902
917
|
return executeBw(args2);
|
|
903
918
|
}
|
|
904
919
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts","../src/biometrics.ts","../src/ipc/ipc-socket.service.ts","../src/ipc/native-messaging-client.ts","../src/session-storage.ts","../src/passthrough.ts","../src/index.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { attemptBiometricUnlock } from \"./biometrics\";\nimport { isPassthroughCommand } from \"./passthrough\";\nimport { generateSessionKey, storeUserKeyForSession } from \"./session-storage\";\n\n/**\n * Get the path to the official bw CLI.\n *\n * Shell aliases don't apply when spawning via child_process,\n * so aliasing bwbio as bw won't cause a conflict here.\n */\nfunction getBwPath(): string {\n return \"bw\";\n}\n\n/**\n * Execute the official bw CLI with the given arguments.\n *\n * @param args - Command line arguments to pass to bw\n * @param sessionKey - Optional BW_SESSION value to set\n * @returns Exit code from bw\n */\nasync function executeBw(args: string[], sessionKey?: string): Promise<number> {\n return new Promise((resolve) => {\n const env = { ...process.env };\n\n if (sessionKey) {\n env.BW_SESSION = sessionKey;\n }\n\n const child = spawn(getBwPath(), args, {\n stdio: \"inherit\",\n env,\n // On Windows, spawn needs shell to resolve .cmd wrappers (e.g. bw.cmd)\n shell: process.platform === \"win32\",\n });\n\n child.on(\"error\", (err) => {\n console.error(`Failed to execute bw: ${err.message}`);\n resolve(1);\n });\n\n child.on(\"close\", (code) => {\n resolve(code ?? 0);\n });\n });\n}\n\n/**\n * Handle the 'unlock' command specially to output BW_SESSION export.\n */\nasync function handleUnlock(\n args: string[],\n sessionKey: string,\n): Promise<number> {\n // Check if --raw flag is present\n const isRaw = args.includes(\"--raw\");\n\n if (isRaw) {\n // Just output the session key\n console.log(sessionKey);\n } else {\n // Output in a format suitable for eval\n console.log(`export BW_SESSION=\"${sessionKey}\"`);\n console.log(`# Run this command to set the session: eval $(bwbio unlock)`);\n }\n\n return 0;\n}\n\n/**\n * Main entry point for the CLI wrapper.\n *\n * Decision flow:\n * 1. If BW_SESSION is already set, pass through to bw\n * 2. If command is passthrough, delegate to bw directly\n * 3. Otherwise, attempt biometric unlock and delegate with session\n */\nexport async function main(args: string[]): Promise<number> {\n // 1. Check if BW_SESSION is already set\n if (process.env.BW_SESSION) {\n return executeBw(args);\n }\n\n // 2. Check if this is a passthrough command\n if (isPassthroughCommand(args)) {\n return executeBw(args);\n }\n\n // 3. Attempt biometric unlock\n console.error(\"Attempting biometric unlock...\");\n\n const result = await attemptBiometricUnlock({\n verbose: process.env.BWBIO_VERBOSE === \"1\",\n });\n\n if (result.success) {\n // Generate a new session key and store the user key\n const sessionKey = generateSessionKey();\n storeUserKeyForSession(result.userKeyB64, result.userId, sessionKey);\n\n // Check if this is an explicit 'unlock' command\n if (args[0] === \"unlock\") {\n return handleUnlock(args, sessionKey);\n }\n\n // Execute the requested command with the session\n return executeBw(args, sessionKey);\n }\n\n // Biometric unlock failed - always fall back to regular bw CLI\n console.error(\n `Biometric unlock unavailable: ${result.error}. Falling back to CLI...`,\n );\n return executeBw(args);\n}\n","import * as crypto from \"node:crypto\";\nimport { BiometricsStatus, NativeMessagingClient } from \"./ipc\";\nimport { getActiveUserId } from \"./session-storage\";\n\n/**\n * Result of a biometric unlock attempt.\n */\nexport type BiometricUnlockResult =\n | {\n success: true;\n /** The user's encryption key (base64 encoded) - NOT the session key */\n userKeyB64: string;\n userId: string;\n }\n | {\n success: false;\n error: string;\n };\n\n/**\n * Options for biometric unlock.\n */\nexport interface BiometricUnlockOptions {\n userId?: string;\n verbose?: boolean;\n}\n\n/**\n * Generate a unique app ID for this CLI instance.\n */\nfunction generateAppId(): string {\n return `bwbio-${crypto.randomUUID()}`;\n}\n\n/**\n * Attempt to unlock the vault using biometrics via the Desktop app.\n */\nexport async function attemptBiometricUnlock(\n options: BiometricUnlockOptions = {},\n): Promise<BiometricUnlockResult> {\n // Get the user ID from CLI data - this is required for the desktop app\n const userId = options.userId || getActiveUserId();\n if (!userId) {\n return {\n success: false,\n error: \"No user ID available - please log in first\",\n };\n }\n\n const appId = generateAppId();\n const client = new NativeMessagingClient(appId, userId);\n\n try {\n // Check if desktop app is available\n const available = await client.isDesktopAppAvailable();\n if (!available) {\n return {\n success: false,\n error: \"Bitwarden Desktop app is not running\",\n };\n }\n\n if (options.verbose) {\n console.error(\"Connecting to Bitwarden Desktop...\");\n }\n\n await client.connect();\n\n // Get user-specific biometrics status\n if (options.verbose) {\n console.error(\"Checking biometrics status...\");\n }\n\n const userStatus = await client.getBiometricsStatusForUser(userId);\n\n // BiometricsStatus is an enum - Available (0) means biometrics can be used\n if (userStatus !== BiometricsStatus.Available) {\n const statusName =\n BiometricsStatus[userStatus] || `Unknown(${userStatus})`;\n return {\n success: false,\n error: `Biometrics not available: ${statusName}`,\n };\n }\n\n // Request biometric unlock\n if (options.verbose) {\n console.error(\"Requesting biometric authentication...\");\n }\n\n const userKey = await client.unlockWithBiometricsForUser(userId);\n\n if (!userKey) {\n return {\n success: false,\n error: \"Biometric unlock was denied or failed\",\n };\n }\n\n return {\n success: true,\n userKeyB64: userKey,\n userId,\n };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n\n return {\n success: false,\n error,\n };\n } finally {\n client.disconnect();\n }\n}\n\n/**\n * Check biometrics availability without attempting unlock.\n */\nexport async function checkBiometricsAvailable(): Promise<boolean> {\n const userId = getActiveUserId();\n if (!userId) {\n return false;\n }\n\n const appId = generateAppId();\n const client = new NativeMessagingClient(appId, userId);\n\n try {\n const available = await client.isDesktopAppAvailable();\n if (!available) {\n return false;\n }\n\n await client.connect();\n const status = await client.getBiometricsStatusForUser(userId);\n return status === BiometricsStatus.Available;\n } catch {\n return false;\n } finally {\n client.disconnect();\n }\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as net from \"node:net\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst DEBUG = process.env.BWBIO_DEBUG === \"1\";\n\n/**\n * Platform-specific IPC socket service for connecting to the Bitwarden desktop app.\n *\n * The desktop app listens on a Unix domain socket (macOS/Linux) or named pipe (Windows).\n * This service provides a platform-agnostic way to connect and communicate with it.\n */\nexport class IpcSocketService {\n private socket: net.Socket | null = null;\n private messageBuffer: Buffer = Buffer.alloc(0);\n private messageHandler: ((message: unknown) => void) | null = null;\n private disconnectHandler: (() => void) | null = null;\n\n /**\n * Get the IPC socket path for the current platform.\n * This mirrors the logic in desktop_native/core/src/ipc/mod.rs\n */\n getSocketPath(): string {\n if (process.env.BWBIO_IPC_SOCKET_PATH) {\n return process.env.BWBIO_IPC_SOCKET_PATH;\n }\n\n const platform = os.platform();\n\n if (platform === \"win32\") {\n return this.getWindowsSocketPath();\n }\n\n if (platform === \"darwin\") {\n return this.getMacSocketPath();\n }\n\n // Linux: use XDG cache directory or fallback\n return this.getLinuxSocketPath();\n }\n\n /**\n * Windows named pipe path - uses hash of home directory.\n */\n private getWindowsSocketPath(): string {\n const homeDir = os.homedir();\n const hash = crypto.createHash(\"sha256\").update(homeDir).digest();\n // Use URL-safe base64 without padding (like Rust's URL_SAFE_NO_PAD)\n const hashB64 = hash\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n return `\\\\\\\\.\\\\pipe\\\\${hashB64}.s.bw`;\n }\n\n /**\n * Get the socket path on macOS.\n * The Desktop app can be sandboxed (Mac App Store) or non-sandboxed.\n * We check both paths and return the one that exists.\n */\n private getMacSocketPath(): string {\n const homeDir = os.homedir();\n\n // Path for sandboxed Desktop app (Mac App Store version)\n const sandboxedPath = path.join(\n homeDir,\n \"Library\",\n \"Group Containers\",\n \"LTZ2PFU5D6.com.bitwarden.desktop\",\n \"s.bw\",\n );\n\n // Path for non-sandboxed Desktop app\n const nonSandboxedPath = path.join(\n homeDir,\n \"Library\",\n \"Caches\",\n \"com.bitwarden.desktop\",\n \"s.bw\",\n );\n\n // Check sandboxed path first (most common for Mac App Store users)\n try {\n fs.accessSync(sandboxedPath);\n return sandboxedPath;\n } catch {\n // Socket not found at sandboxed path\n }\n\n // Check non-sandboxed path\n try {\n fs.accessSync(nonSandboxedPath);\n return nonSandboxedPath;\n } catch {\n // Socket not found at non-sandboxed path either\n }\n\n // Default to sandboxed path\n return sandboxedPath;\n }\n\n /**\n * Linux socket path - uses XDG_CACHE_HOME or ~/.cache.\n */\n private getLinuxSocketPath(): string {\n const cacheDir =\n process.env.XDG_CACHE_HOME != null\n ? process.env.XDG_CACHE_HOME\n : path.join(os.homedir(), \".cache\");\n return path.join(cacheDir, \"com.bitwarden.desktop\", \"s.bw\");\n }\n\n /**\n * Check if the desktop app socket exists (quick availability check).\n */\n async isSocketAvailable(): Promise<boolean> {\n const socketPath = this.getSocketPath();\n try {\n await fs.promises.access(socketPath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Connect to the desktop app's IPC socket.\n */\n async connect(): Promise<void> {\n if (this.socket != null) {\n return;\n }\n\n const socketPath = this.getSocketPath();\n\n if (DEBUG) {\n console.error(`[DEBUG] Connecting to socket: ${socketPath}`);\n }\n\n return new Promise((resolve, reject) => {\n const socket = net.createConnection(socketPath);\n\n socket.on(\"connect\", () => {\n if (DEBUG) {\n console.error(`[DEBUG] Socket connected`);\n }\n this.socket = socket;\n resolve();\n });\n\n socket.on(\"data\", (data: Buffer) => {\n if (DEBUG) {\n console.error(`[DEBUG] Received raw data: ${data.length} bytes`);\n }\n this.processIncomingData(data);\n });\n\n socket.on(\"error\", (err) => {\n if (this.socket == null) {\n reject(new Error(`Failed to connect to desktop app: ${err.message}`));\n }\n });\n\n socket.on(\"close\", () => {\n this.socket = null;\n this.messageBuffer = Buffer.alloc(0);\n if (this.disconnectHandler) {\n this.disconnectHandler();\n }\n });\n\n // Timeout for initial connection\n socket.setTimeout(5000, () => {\n if (this.socket == null) {\n socket.destroy();\n reject(new Error(\"Connection to desktop app timed out\"));\n }\n });\n });\n }\n\n /**\n * Disconnect from the socket.\n */\n disconnect(): void {\n if (this.socket != null) {\n this.socket.destroy();\n this.socket = null;\n }\n this.messageBuffer = Buffer.alloc(0);\n }\n\n /**\n * Check if currently connected.\n */\n isConnected(): boolean {\n return this.socket != null && !this.socket.destroyed;\n }\n\n /**\n * Set the handler for incoming messages.\n */\n onMessage(handler: (message: unknown) => void): void {\n this.messageHandler = handler;\n }\n\n /**\n * Set the handler for disconnect events.\n */\n onDisconnect(handler: () => void): void {\n this.disconnectHandler = handler;\n }\n\n /**\n * Send a message to the desktop app.\n * Uses length-delimited protocol: 4-byte little-endian length prefix + JSON payload.\n */\n sendMessage(message: unknown): void {\n if (this.socket == null || this.socket.destroyed) {\n throw new Error(\"Not connected to desktop app\");\n }\n\n const messageStr = JSON.stringify(message);\n const messageBytes = Buffer.from(messageStr, \"utf8\");\n\n // Create buffer with 4-byte length prefix (little-endian)\n const buffer = Buffer.alloc(4 + messageBytes.length);\n buffer.writeUInt32LE(messageBytes.length, 0);\n messageBytes.copy(buffer, 4);\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Sending ${buffer.length} bytes (message: ${messageBytes.length} bytes)`,\n );\n }\n\n this.socket.write(buffer);\n }\n\n /**\n * Process incoming data from the socket.\n * Messages are length-delimited: 4-byte LE length + JSON payload.\n */\n private processIncomingData(data: Buffer): void {\n this.messageBuffer = Buffer.concat([this.messageBuffer, data]);\n\n // Process all complete messages in the buffer\n while (this.messageBuffer.length >= 4) {\n const messageLength = this.messageBuffer.readUInt32LE(0);\n\n // Check if we have the full message\n if (this.messageBuffer.length < 4 + messageLength) {\n break;\n }\n\n // Extract and parse the message\n const messageBytes = this.messageBuffer.subarray(4, 4 + messageLength);\n const messageStr = messageBytes.toString(\"utf8\");\n\n // Update buffer to remove processed message\n this.messageBuffer = this.messageBuffer.subarray(4 + messageLength);\n\n try {\n const message = JSON.parse(messageStr);\n if (this.messageHandler) {\n this.messageHandler(message);\n }\n } catch {\n // Failed to parse message\n }\n }\n }\n}\n","import * as crypto from \"node:crypto\";\nimport { IpcSocketService } from \"./ipc-socket.service\";\n\nconst MESSAGE_VALID_TIMEOUT = 10 * 1000; // 10 seconds\nconst DEFAULT_TIMEOUT = 10 * 1000; // 10 seconds for protocol messages\nconst USER_INTERACTION_TIMEOUT = 60 * 1000; // 60 seconds for biometric prompts\n\nconst DEBUG = process.env.BWBIO_DEBUG === \"1\";\n\n/**\n * Biometrics commands matching the desktop app's expected commands.\n */\nexport const BiometricsCommands = {\n AuthenticateWithBiometrics: \"authenticateWithBiometrics\",\n GetBiometricsStatus: \"getBiometricsStatus\",\n UnlockWithBiometricsForUser: \"unlockWithBiometricsForUser\",\n GetBiometricsStatusForUser: \"getBiometricsStatusForUser\",\n CanEnableBiometricUnlock: \"canEnableBiometricUnlock\",\n} as const;\n\n/**\n * Biometrics status enum matching the desktop app's BiometricsStatus.\n */\nexport enum BiometricsStatus {\n Available = 0,\n UnlockNeeded = 1,\n HardwareUnavailable = 2,\n AutoSetupNeeded = 3,\n ManualSetupNeeded = 4,\n PlatformUnsupported = 5,\n DesktopDisconnected = 6,\n NotEnabledLocally = 7,\n NotEnabledInConnectedDesktopApp = 8,\n NativeMessagingPermissionMissing = 9,\n}\n\ntype Message = {\n command: string;\n messageId?: number;\n userId?: string;\n timestamp?: number;\n publicKey?: string;\n};\n\ntype OuterMessage = {\n message: Message | EncryptedMessage;\n appId: string;\n};\n\ntype EncryptedMessage = {\n encryptedString: string;\n encryptionType: number;\n data: string;\n iv: string;\n mac: string;\n};\n\ntype ReceivedMessage = {\n timestamp: number;\n command: string;\n messageId: number;\n response?: unknown;\n userKeyB64?: string;\n};\n\ntype ReceivedMessageOuter = {\n command: string;\n appId: string;\n messageId?: number;\n message?: ReceivedMessage | EncryptedMessage;\n sharedSecret?: string;\n};\n\ntype Callback = {\n resolver: (value: ReceivedMessage) => void;\n rejecter: (reason?: unknown) => void;\n timeout: ReturnType<typeof setTimeout>;\n};\n\ntype SecureChannel = {\n privateKey: crypto.KeyObject;\n publicKey: crypto.KeyObject;\n sharedSecret?: Buffer;\n setupResolve?: () => void;\n setupReject?: (reason?: unknown) => void;\n};\n\n/**\n * Native messaging client for communicating with the Bitwarden desktop app.\n *\n * This implements the same IPC protocol used by the browser extension:\n * 1. Connect to the desktop app via Unix socket / named pipe\n * 2. Set up encrypted communication using RSA key exchange\n * 3. Send/receive encrypted commands (biometric unlock, status checks, etc.)\n */\nexport class NativeMessagingClient {\n private connected = false;\n private connecting = false;\n private appId: string;\n\n private secureChannel: SecureChannel | null = null;\n private messageId = 0;\n private callbacks = new Map<number, Callback>();\n\n private ipcSocket: IpcSocketService;\n private userId: string | null = null;\n\n constructor(appId: string, userId?: string) {\n this.appId = appId;\n this.userId = userId ?? null;\n this.ipcSocket = new IpcSocketService();\n }\n\n /**\n * Check if the desktop app is available (socket exists).\n */\n async isDesktopAppAvailable(): Promise<boolean> {\n return this.ipcSocket.isSocketAvailable();\n }\n\n /**\n * Connect to the desktop app.\n */\n async connect(): Promise<void> {\n if (this.connected || this.connecting) {\n return;\n }\n\n this.connecting = true;\n\n try {\n await this.ipcSocket.connect();\n\n // Set up message handler\n this.ipcSocket.onMessage((message) => {\n this.handleMessage(message as ReceivedMessageOuter);\n });\n\n this.ipcSocket.onDisconnect(() => {\n this.connected = false;\n this.secureChannel = null;\n\n // Clear timeouts and reject all pending callbacks\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(new Error(\"Disconnected from Desktop app\"));\n }\n this.callbacks.clear();\n });\n\n this.connected = true;\n this.connecting = false;\n } catch (e) {\n this.connecting = false;\n throw e;\n }\n }\n\n /**\n * Disconnect from the desktop app.\n */\n disconnect(): void {\n this.ipcSocket.disconnect();\n this.connected = false;\n this.secureChannel = null;\n }\n\n /**\n * Send a command to the desktop app and wait for a response.\n */\n async callCommand(\n message: Message,\n timeoutMs: number = DEFAULT_TIMEOUT,\n ): Promise<ReceivedMessage> {\n const messageId = this.messageId++;\n\n const callback = new Promise<ReceivedMessage>((resolver, rejecter) => {\n const timeout = setTimeout(() => {\n if (this.callbacks.has(messageId)) {\n this.callbacks.delete(messageId);\n rejecter(\n new Error(\"Message timed out waiting for Desktop app response\"),\n );\n }\n }, timeoutMs);\n\n this.callbacks.set(messageId, { resolver, rejecter, timeout });\n });\n\n message.messageId = messageId;\n\n try {\n await this.send(message);\n } catch (e) {\n const cb = this.callbacks.get(messageId);\n if (cb) {\n clearTimeout(cb.timeout);\n this.callbacks.delete(messageId);\n cb.rejecter(e instanceof Error ? e : new Error(String(e)));\n }\n }\n\n return callback;\n }\n\n /**\n * Get biometrics status from the desktop app.\n */\n async getBiometricsStatus(): Promise<BiometricsStatus> {\n const response = await this.callCommand({\n command: BiometricsCommands.GetBiometricsStatus,\n });\n return response.response as BiometricsStatus;\n }\n\n /**\n * Get biometrics status for a specific user.\n */\n async getBiometricsStatusForUser(userId: string): Promise<BiometricsStatus> {\n const response = await this.callCommand({\n command: BiometricsCommands.GetBiometricsStatusForUser,\n userId: userId,\n });\n return response.response as BiometricsStatus;\n }\n\n /**\n * Unlock with biometrics for a specific user.\n * Returns the user key if successful.\n */\n async unlockWithBiometricsForUser(userId: string): Promise<string | null> {\n const response = await this.callCommand(\n {\n command: BiometricsCommands.UnlockWithBiometricsForUser,\n userId: userId,\n },\n USER_INTERACTION_TIMEOUT,\n );\n\n if (response.response) {\n return response.userKeyB64 ?? null;\n }\n\n return null;\n }\n\n /**\n * Send a message to the desktop app (encrypted if secure channel is established).\n */\n private async send(message: Message): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n\n message.userId = this.userId ?? undefined;\n message.timestamp = Date.now();\n\n this.postMessage({\n appId: this.appId,\n message: await this.encryptMessage(message),\n });\n }\n\n /**\n * Encrypt a message using the secure channel's shared secret.\n */\n private async encryptMessage(\n message: Message,\n ): Promise<EncryptedMessage | Message> {\n if (this.secureChannel?.sharedSecret == null) {\n await this.secureCommunication();\n }\n\n // biome-ignore lint/style/noNonNullAssertion: guaranteed by secureCommunication() above\n const sharedSecret = this.secureChannel!.sharedSecret!;\n const messageJson = JSON.stringify(message);\n\n // Use AES-256-CBC encryption (matching Bitwarden's EncryptionType.AesCbc256_HmacSha256_B64 = 2)\n const iv = crypto.randomBytes(16);\n const cipher = crypto.createCipheriv(\n \"aes-256-cbc\",\n sharedSecret.subarray(0, 32),\n iv,\n );\n const encrypted = Buffer.concat([\n cipher.update(messageJson, \"utf8\"),\n cipher.final(),\n ]);\n\n // Create HMAC using the second half of the key\n const macKey = sharedSecret.subarray(32, 64);\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(encrypted);\n const mac = hmac.digest();\n\n return {\n encryptionType: 2, // AesCbc256_HmacSha256_B64\n encryptedString: `2.${iv.toString(\"base64\")}|${encrypted.toString(\"base64\")}|${mac.toString(\"base64\")}`,\n iv: iv.toString(\"base64\"),\n data: encrypted.toString(\"base64\"),\n mac: mac.toString(\"base64\"),\n };\n }\n\n /**\n * Post a message to the IPC socket.\n */\n private postMessage(message: OuterMessage): void {\n try {\n this.ipcSocket.sendMessage(message);\n } catch (e) {\n this.secureChannel = null;\n this.connected = false;\n throw e;\n }\n }\n\n /**\n * Handle incoming messages from the desktop app.\n */\n private async handleMessage(message: ReceivedMessageOuter): Promise<void> {\n if (DEBUG) {\n console.error(\n `[DEBUG] Received message:`,\n JSON.stringify(message, null, 2),\n );\n }\n\n switch (message.command) {\n case \"setupEncryption\":\n if (message.appId !== this.appId) {\n return;\n }\n await this.handleSetupEncryption(message);\n break;\n\n case \"invalidateEncryption\": {\n if (message.appId !== this.appId) {\n return;\n }\n const invalidError = new Error(\n \"Encryption channel invalidated by Desktop app\",\n );\n if (this.secureChannel?.setupReject) {\n this.secureChannel.setupReject(invalidError);\n }\n this.secureChannel = null;\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(invalidError);\n }\n this.callbacks.clear();\n this.connected = false;\n this.ipcSocket.disconnect();\n break;\n }\n\n case \"wrongUserId\": {\n const wrongUserError = new Error(\n \"Account mismatch: CLI and Desktop app are logged into different accounts\",\n );\n if (this.secureChannel?.setupReject) {\n this.secureChannel.setupReject(wrongUserError);\n }\n this.secureChannel = null;\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(wrongUserError);\n }\n this.callbacks.clear();\n this.connected = false;\n this.ipcSocket.disconnect();\n break;\n }\n\n case \"verifyDesktopIPCFingerprint\":\n await this.showFingerprint();\n break;\n\n default:\n // Ignore messages for other apps\n if (message.appId !== this.appId) {\n return;\n }\n\n if (message.message != null) {\n await this.handleEncryptedMessage(message.message);\n }\n }\n }\n\n /**\n * Handle the setupEncryption response from the desktop app.\n */\n private async handleSetupEncryption(\n message: ReceivedMessageOuter,\n ): Promise<void> {\n if (DEBUG) {\n console.error(\n `[DEBUG] handleSetupEncryption called, sharedSecret present: ${message.sharedSecret != null}`,\n );\n }\n\n if (message.sharedSecret == null) {\n if (DEBUG) {\n console.error(`[DEBUG] No sharedSecret in message`);\n }\n return;\n }\n\n if (this.secureChannel == null) {\n if (DEBUG) {\n console.error(`[DEBUG] No secureChannel setup`);\n }\n return;\n }\n\n // Decrypt the shared secret using our private key (RSA-OAEP with SHA-1)\n const encrypted = Buffer.from(message.sharedSecret, \"base64\");\n if (DEBUG) {\n console.error(\n `[DEBUG] Encrypted sharedSecret length: ${encrypted.length}`,\n );\n }\n\n const decrypted = crypto.privateDecrypt(\n {\n key: this.secureChannel.privateKey,\n oaepHash: \"sha1\",\n padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,\n },\n encrypted,\n );\n\n this.secureChannel.sharedSecret = decrypted;\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Decrypted sharedSecret length: ${this.secureChannel.sharedSecret.length}`,\n );\n }\n\n if (this.secureChannel.setupResolve) {\n this.secureChannel.setupResolve();\n }\n }\n\n /**\n * Handle an encrypted message from the desktop app.\n */\n private async handleEncryptedMessage(\n rawMessage: ReceivedMessage | EncryptedMessage,\n ): Promise<void> {\n if (this.secureChannel?.sharedSecret == null) {\n return;\n }\n\n let message: ReceivedMessage;\n\n if (\"encryptionType\" in rawMessage || \"encryptedString\" in rawMessage) {\n // Decrypt the message\n const encMsg = rawMessage as EncryptedMessage;\n const iv = Buffer.from(encMsg.iv, \"base64\");\n const data = Buffer.from(encMsg.data, \"base64\");\n const mac = Buffer.from(encMsg.mac, \"base64\");\n\n const sharedSecret = this.secureChannel.sharedSecret;\n const encKey = sharedSecret.subarray(0, 32);\n const macKey = sharedSecret.subarray(32, 64);\n\n // Verify HMAC\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(data);\n const expectedMac = hmac.digest();\n\n if (!crypto.timingSafeEqual(mac, expectedMac)) {\n throw new Error(\"Message integrity check failed\");\n }\n\n // Decrypt\n const decipher = crypto.createDecipheriv(\"aes-256-cbc\", encKey, iv);\n const decrypted = Buffer.concat([\n decipher.update(data),\n decipher.final(),\n ]);\n message = JSON.parse(decrypted.toString(\"utf8\"));\n } else {\n message = rawMessage as ReceivedMessage;\n }\n\n this.processDecryptedMessage(message);\n }\n\n /**\n * Process a decrypted message and resolve any pending callbacks.\n */\n private processDecryptedMessage(message: ReceivedMessage): void {\n if (DEBUG) {\n console.error(\n `[DEBUG] Decrypted message:`,\n JSON.stringify(message, null, 2),\n );\n }\n\n if (Math.abs(message.timestamp - Date.now()) > MESSAGE_VALID_TIMEOUT) {\n if (DEBUG) {\n console.error(\n `[DEBUG] Message too old, ignoring. Timestamp: ${message.timestamp}, now: ${Date.now()}`,\n );\n }\n return;\n }\n\n const messageId = message.messageId;\n\n if (this.callbacks.has(messageId)) {\n // biome-ignore lint/style/noNonNullAssertion: guaranteed by .has() check above\n const callback = this.callbacks.get(messageId)!;\n clearTimeout(callback.timeout);\n this.callbacks.delete(messageId);\n callback.resolver(message);\n } else if (DEBUG) {\n console.error(`[DEBUG] No callback found for messageId: ${messageId}`);\n }\n }\n\n /**\n * Set up secure communication with RSA key exchange.\n */\n private async secureCommunication(): Promise<void> {\n // Generate RSA key pair\n const { publicKey, privateKey } = crypto.generateKeyPairSync(\"rsa\", {\n modulusLength: 2048,\n });\n\n // Export public key in SPKI/DER format (base64 encoded)\n const publicKeyDer = publicKey.export({ type: \"spki\", format: \"der\" });\n const publicKeyB64 = publicKeyDer.toString(\"base64\");\n\n const setupMessage = {\n appId: this.appId,\n message: {\n command: \"setupEncryption\",\n publicKey: publicKeyB64,\n userId: this.userId ?? undefined,\n messageId: this.messageId++,\n timestamp: Date.now(),\n },\n };\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Sending setupEncryption:`,\n JSON.stringify(\n {\n ...setupMessage,\n message: {\n ...setupMessage.message,\n publicKey: `${publicKeyB64.slice(0, 50)}...`,\n },\n },\n null,\n 2,\n ),\n );\n }\n\n this.postMessage(setupMessage);\n\n return new Promise((resolve, reject) => {\n this.secureChannel = {\n publicKey,\n privateKey,\n setupResolve: resolve,\n setupReject: reject,\n };\n\n // Timeout for key exchange\n setTimeout(() => {\n if (this.secureChannel && !this.secureChannel.sharedSecret) {\n reject(new Error(\"Secure channel setup timed out\"));\n }\n }, DEFAULT_TIMEOUT);\n });\n }\n\n /**\n * Display the fingerprint for verification.\n */\n private async showFingerprint(): Promise<void> {\n if (this.secureChannel?.publicKey == null) {\n return;\n }\n\n // Generate fingerprint from public key\n const publicKeyDer = this.secureChannel.publicKey.export({\n type: \"spki\",\n format: \"der\",\n });\n const hash = crypto.createHash(\"sha256\").update(publicKeyDer).digest();\n\n // Format as 5 groups of alphanumeric characters (like Bitwarden)\n const fingerprint = hash.toString(\"hex\").slice(0, 25).toUpperCase();\n const formatted = fingerprint.match(/.{1,5}/g)?.join(\"-\") || fingerprint;\n\n // Write to stderr so it doesn't interfere with command output\n const dim = \"\\x1b[2m\";\n const cyan = \"\\x1b[36m\";\n const bold = \"\\x1b[1m\";\n const reset = \"\\x1b[0m\";\n\n console.error(\"\");\n console.error(`${bold}Bitwarden Desktop App Verification${reset}`);\n console.error(\n \"Verify this fingerprint matches the one shown in the Desktop app:\",\n );\n console.error(\"\");\n console.error(`${dim} ${cyan}${formatted}${reset}`);\n console.error(\"\");\n console.error(\"Accept the connection in the Desktop app to continue.\");\n console.error(\"\");\n }\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * AesCbc256_HmacSha256_B64 encryption type (matches Bitwarden's format)\n */\nconst ENCRYPTION_TYPE = 2;\n\n/**\n * Get the CLI data directory path for the current platform.\n */\nfunction getCliDataDir(): string {\n if (process.env.BITWARDENCLI_APPDATA_DIR) {\n return path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);\n }\n\n const platform = os.platform();\n const homeDir = os.homedir();\n\n if (platform === \"darwin\") {\n return path.join(homeDir, \"Library/Application Support/Bitwarden CLI\");\n } else if (platform === \"win32\") {\n return path.join(process.env.APPDATA ?? homeDir, \"Bitwarden CLI\");\n } else {\n // Linux\n const configDir =\n process.env.XDG_CONFIG_HOME ?? path.join(homeDir, \".config\");\n return path.join(configDir, \"Bitwarden CLI\");\n }\n}\n\n/**\n * Generate a new session key (64 random bytes, base64 encoded).\n */\nexport function generateSessionKey(): string {\n const keyBytes = crypto.randomBytes(64);\n return keyBytes.toString(\"base64\");\n}\n\n/**\n * Encrypt data using AES-256-CBC with HMAC-SHA256 (Bitwarden's type 2 format).\n *\n * @param data - The data to encrypt (as Uint8Array)\n * @param sessionKey - The session key (base64 encoded, 64 bytes when decoded)\n * @returns Encrypted data as base64 string\n */\nfunction encryptWithSessionKey(data: Uint8Array, sessionKey: string): string {\n // Decode session key - first 32 bytes for AES, last 32 for HMAC\n const keyBytes = Buffer.from(sessionKey, \"base64\");\n if (keyBytes.length !== 64) {\n throw new Error(\"Session key must be 64 bytes\");\n }\n\n const encKey = keyBytes.subarray(0, 32);\n const macKey = keyBytes.subarray(32, 64);\n\n // Generate random IV\n const iv = crypto.randomBytes(16);\n\n // Encrypt with AES-256-CBC\n const cipher = crypto.createCipheriv(\"aes-256-cbc\", encKey, iv);\n const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);\n\n // Calculate HMAC-SHA256 over IV + ciphertext\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(encrypted);\n const mac = hmac.digest();\n\n // Assemble: [encType, iv, mac, ciphertext]\n const result = Buffer.alloc(1 + 16 + 32 + encrypted.length);\n result.writeUInt8(ENCRYPTION_TYPE, 0);\n iv.copy(result, 1);\n mac.copy(result, 17);\n encrypted.copy(result, 49);\n\n return result.toString(\"base64\");\n}\n\n/**\n * Read the CLI's data.json file.\n */\nfunction readCliData(): Record<string, unknown> {\n const dataPath = path.join(getCliDataDir(), \"data.json\");\n\n try {\n const content = fs.readFileSync(dataPath, \"utf-8\");\n return content ? JSON.parse(content) : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Get the active user ID from CLI's data storage.\n */\nexport function getActiveUserId(): string | null {\n const data = readCliData();\n const userId = data.global_account_activeAccountId;\n return typeof userId === \"string\" ? userId : null;\n}\n\n/**\n * Write to the CLI's data.json file.\n */\nfunction writeCliData(data: Record<string, unknown>): void {\n const dataDir = getCliDataDir();\n const dataPath = path.join(dataDir, \"data.json\");\n\n // Ensure directory exists\n if (!fs.existsSync(dataDir)) {\n fs.mkdirSync(dataDir, { recursive: true, mode: 0o700 });\n }\n\n // Write with restrictive permissions\n fs.writeFileSync(dataPath, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Store the user key in CLI's data storage, encrypted with the session key.\n *\n * This mimics what the CLI does internally:\n * - Encrypts the user key with BW_SESSION\n * - Stores it in data.json with key \"__PROTECTED__{userId}_user_auto\"\n *\n * @param userKeyB64 - The user key from biometric unlock (base64 encoded)\n * @param userId - The user ID\n * @param sessionKey - The BW_SESSION key (base64 encoded, 64 bytes)\n */\nexport function storeUserKeyForSession(\n userKeyB64: string,\n userId: string,\n sessionKey: string,\n): void {\n // The user key is already base64 - convert to bytes for encryption\n const userKeyBytes = Buffer.from(userKeyB64, \"base64\");\n\n // Encrypt the user key with the session key\n const encryptedUserKey = encryptWithSessionKey(userKeyBytes, sessionKey);\n\n // Store in CLI's data.json\n const storageKey = `__PROTECTED__${userId}_user_auto`;\n\n const data = readCliData();\n data[storageKey] = encryptedUserKey;\n writeCliData(data);\n}\n\n/**\n * Clean up the stored user key.\n *\n * @param userId - The user ID\n */\nexport function clearStoredUserKey(userId: string): void {\n const storageKey = `__PROTECTED__${userId}_user_auto`;\n\n const data = readCliData();\n delete data[storageKey];\n writeCliData(data);\n}\n","/**\n * Passthrough command detection\n *\n * These commands are passed directly to `bw` without attempting biometric unlock.\n */\n\nconst PASSTHROUGH_COMMANDS = new Set([\n \"login\",\n \"logout\",\n \"lock\",\n \"config\",\n \"update\",\n \"completion\",\n \"status\",\n \"serve\",\n]);\n\nconst PASSTHROUGH_FLAGS = new Set([\"--help\", \"-h\", \"--version\", \"-v\"]);\n\n/**\n * Determines if the given arguments represent a passthrough command.\n *\n * Passthrough commands are executed directly without biometric unlock:\n * - Commands that don't need an unlocked vault (login, logout, status, etc.)\n * - Help and version flags\n *\n * @param args - Command line arguments (without node and script path)\n * @returns true if this is a passthrough command\n */\nexport function isPassthroughCommand(args: string[]): boolean {\n if (args.length === 0) {\n // No args = show help, which is passthrough\n return true;\n }\n\n const firstArg = args[0];\n\n // Check for passthrough flags anywhere in args\n if (args.some((arg) => PASSTHROUGH_FLAGS.has(arg))) {\n return true;\n }\n\n // Check if first arg is a passthrough command\n if (PASSTHROUGH_COMMANDS.has(firstArg)) {\n return true;\n }\n\n return false;\n}\n","#!/usr/bin/env node\n\nimport { main } from \"./main\";\n\n// Get command line arguments (skip node and script path)\nconst args = process.argv.slice(2);\n\nmain(args)\n .then((code) => {\n process.exit(code);\n })\n .catch((err) => {\n console.error(\"Unexpected error:\", err);\n process.exit(1);\n });\n"],"mappings":"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","names":["crypto","platform","resolve","crypto","DEBUG","BiometricsStatus","resolve","crypto","fs","os","path","platform","args","args","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/main.ts","../src/biometrics.ts","../src/ipc/ipc-socket.service.ts","../src/ipc/native-messaging-client.ts","../src/log.ts","../src/session-storage.ts","../src/passthrough.ts","../src/index.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { attemptBiometricUnlock } from \"./biometrics\";\nimport { isPassthroughCommand } from \"./passthrough\";\nimport { generateSessionKey, storeUserKeyForSession } from \"./session-storage\";\n\nfunction writeLn(s: string): void {\n if (process.env.BW_QUIET !== \"true\") {\n process.stdout.write(`${s}\\n`);\n }\n}\n\n/**\n * Get the path to the official bw CLI.\n *\n * Shell aliases don't apply when spawning via child_process,\n * so aliasing bwbio as bw won't cause a conflict here.\n */\nfunction getBwPath(): string {\n return \"bw\";\n}\n\n/**\n * Execute the official bw CLI with the given arguments.\n *\n * @param args - Command line arguments to pass to bw\n * @param sessionKey - Optional BW_SESSION value to set\n * @returns Exit code from bw\n */\nasync function executeBw(args: string[], sessionKey?: string): Promise<number> {\n return new Promise((resolve) => {\n const env = { ...process.env };\n\n if (sessionKey) {\n env.BW_SESSION = sessionKey;\n }\n\n const child = spawn(getBwPath(), args, {\n stdio: \"inherit\",\n env,\n // On Windows, spawn needs shell to resolve .cmd wrappers (e.g. bw.cmd)\n shell: process.platform === \"win32\",\n });\n\n child.on(\"error\", (err) => {\n console.error(`Failed to execute bw: ${err.message}`);\n resolve(1);\n });\n\n child.on(\"close\", (code) => {\n resolve(code ?? 0);\n });\n });\n}\n\n/**\n * Handle the 'unlock' command specially to output BW_SESSION export.\n */\nasync function handleUnlock(\n args: string[],\n sessionKey: string,\n): Promise<number> {\n // Check if --raw flag is present\n const isRaw = args.includes(\"--raw\");\n\n if (isRaw) {\n writeLn(sessionKey);\n } else {\n writeLn(`export BW_SESSION=\"${sessionKey}\"`);\n writeLn(`# Run this command to set the session: eval $(bwbio unlock)`);\n }\n\n return 0;\n}\n\n/**\n * Main entry point for the CLI wrapper.\n *\n * Attempts biometric unlock via the Desktop app, then delegates to bw.\n * Skips biometrics when BW_SESSION is set, in non-interactive mode, or for passthrough commands.\n */\nexport async function main(args: string[]): Promise<number> {\n // Mirror --quiet and --nointeraction flags to env vars (same as bw CLI)\n if (args.includes(\"--quiet\")) {\n process.env.BW_QUIET = \"true\";\n }\n if (args.includes(\"--nointeraction\")) {\n process.env.BW_NOINTERACTION = \"true\";\n }\n\n // Skip biometric unlock when not needed or not possible\n if (\n process.env.BW_SESSION ||\n process.env.BW_NOINTERACTION === \"true\" ||\n isPassthroughCommand(args)\n ) {\n return executeBw(args);\n }\n\n // Attempt biometric unlock\n const result = await attemptBiometricUnlock();\n\n if (result.success) {\n // Generate a new session key and store the user key\n const sessionKey = generateSessionKey();\n storeUserKeyForSession(result.userKeyB64, result.userId, sessionKey);\n\n // Check if this is an explicit 'unlock' command\n if (args[0] === \"unlock\") {\n return handleUnlock(args, sessionKey);\n }\n\n // Execute the requested command with the session\n return executeBw(args, sessionKey);\n }\n\n // Biometric unlock failed or unavailable - fall back to regular bw CLI\n return executeBw(args);\n}\n","import * as crypto from \"node:crypto\";\nimport { BiometricsStatus, NativeMessagingClient } from \"./ipc\";\nimport { log, logVerbose } from \"./log\";\nimport { getActiveUserId } from \"./session-storage\";\n\n/**\n * Get the platform-specific biometric method name.\n */\nfunction getBiometricMethodName(): string {\n switch (process.platform) {\n case \"darwin\":\n return \"Touch ID\";\n case \"win32\":\n return \"Windows Hello\";\n default:\n return \"Polkit\";\n }\n}\n\n/**\n * Result of a biometric unlock attempt.\n */\nexport type BiometricUnlockResult =\n | {\n success: true;\n /** The user's encryption key (base64 encoded) - NOT the session key */\n userKeyB64: string;\n userId: string;\n }\n | {\n success: false;\n };\n\n/**\n * Options for biometric unlock.\n */\nexport interface BiometricUnlockOptions {\n userId?: string;\n}\n\n/**\n * Generate a unique app ID for this CLI instance.\n */\nfunction generateAppId(): string {\n return `bwbio-${crypto.randomUUID()}`;\n}\n\n/**\n * Attempt to unlock the vault using biometrics via the Desktop app.\n */\nexport async function attemptBiometricUnlock(\n options: BiometricUnlockOptions = {},\n): Promise<BiometricUnlockResult> {\n // Get the user ID from CLI data - this is required for the desktop app\n const userId = options.userId || getActiveUserId();\n if (!userId) {\n logVerbose(\n \"Biometric unlock unavailable: No user ID available - please log in first\",\n );\n return { success: false };\n }\n\n const appId = generateAppId();\n const client = new NativeMessagingClient(appId, userId);\n\n try {\n // Check if desktop app is available\n const available = await client.isDesktopAppAvailable();\n if (!available) {\n logVerbose(\n \"Biometric unlock unavailable: Bitwarden Desktop app is not running\",\n );\n return { success: false };\n }\n\n logVerbose(\"Connecting to Bitwarden Desktop...\");\n\n await client.connect();\n\n // Get user-specific biometrics status\n logVerbose(\"Checking biometrics status...\");\n\n const userStatus = await client.getBiometricsStatusForUser(userId);\n\n // BiometricsStatus is an enum - Available (0) means biometrics can be used\n if (userStatus !== BiometricsStatus.Available) {\n const statusName =\n BiometricsStatus[userStatus] || `Unknown(${userStatus})`;\n logVerbose(`Biometric unlock unavailable: ${statusName}`);\n return { success: false };\n }\n\n // Request biometric unlock\n log(\n `Authenticate with ${getBiometricMethodName()} on Desktop app to continue...`,\n );\n\n const userKey = await client.unlockWithBiometricsForUser(userId);\n\n if (!userKey) {\n log(\"Biometric unlock was denied or failed\");\n return { success: false };\n }\n\n return {\n success: true,\n userKeyB64: userKey,\n userId,\n };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log(`Biometric unlock failed: ${error}`);\n return { success: false };\n } finally {\n client.disconnect();\n }\n}\n\n/**\n * Check biometrics availability without attempting unlock.\n */\nexport async function checkBiometricsAvailable(): Promise<boolean> {\n const userId = getActiveUserId();\n if (!userId) {\n return false;\n }\n\n const appId = generateAppId();\n const client = new NativeMessagingClient(appId, userId);\n\n try {\n const available = await client.isDesktopAppAvailable();\n if (!available) {\n return false;\n }\n\n await client.connect();\n const status = await client.getBiometricsStatusForUser(userId);\n return status === BiometricsStatus.Available;\n } catch {\n return false;\n } finally {\n client.disconnect();\n }\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as net from \"node:net\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst DEBUG = process.env.BWBIO_DEBUG === \"true\";\n\n/**\n * Platform-specific IPC socket service for connecting to the Bitwarden desktop app.\n *\n * The desktop app listens on a Unix domain socket (macOS/Linux) or named pipe (Windows).\n * This service provides a platform-agnostic way to connect and communicate with it.\n */\nexport class IpcSocketService {\n private socket: net.Socket | null = null;\n private messageBuffer: Buffer = Buffer.alloc(0);\n private messageHandler: ((message: unknown) => void) | null = null;\n private disconnectHandler: (() => void) | null = null;\n\n /**\n * Get the IPC socket path for the current platform.\n * This mirrors the logic in desktop_native/core/src/ipc/mod.rs\n */\n getSocketPath(): string {\n if (process.env.BWBIO_IPC_SOCKET_PATH) {\n return process.env.BWBIO_IPC_SOCKET_PATH;\n }\n\n const platform = os.platform();\n\n if (platform === \"win32\") {\n return this.getWindowsSocketPath();\n }\n\n if (platform === \"darwin\") {\n return this.getMacSocketPath();\n }\n\n // Linux: use XDG cache directory or fallback\n return this.getLinuxSocketPath();\n }\n\n /**\n * Windows named pipe path - uses hash of home directory.\n */\n private getWindowsSocketPath(): string {\n const homeDir = os.homedir();\n const hash = crypto.createHash(\"sha256\").update(homeDir).digest();\n // Use URL-safe base64 without padding (like Rust's URL_SAFE_NO_PAD)\n const hashB64 = hash\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n return `\\\\\\\\.\\\\pipe\\\\${hashB64}.s.bw`;\n }\n\n /**\n * Get the socket path on macOS.\n * The Desktop app can be sandboxed (Mac App Store) or non-sandboxed.\n * We check both paths and return the one that exists.\n */\n private getMacSocketPath(): string {\n const homeDir = os.homedir();\n\n // Path for sandboxed Desktop app (Mac App Store version)\n const sandboxedPath = path.join(\n homeDir,\n \"Library\",\n \"Group Containers\",\n \"LTZ2PFU5D6.com.bitwarden.desktop\",\n \"s.bw\",\n );\n\n // Path for non-sandboxed Desktop app\n const nonSandboxedPath = path.join(\n homeDir,\n \"Library\",\n \"Caches\",\n \"com.bitwarden.desktop\",\n \"s.bw\",\n );\n\n // Check sandboxed path first (most common for Mac App Store users)\n try {\n fs.accessSync(sandboxedPath);\n return sandboxedPath;\n } catch {\n // Socket not found at sandboxed path\n }\n\n // Check non-sandboxed path\n try {\n fs.accessSync(nonSandboxedPath);\n return nonSandboxedPath;\n } catch {\n // Socket not found at non-sandboxed path either\n }\n\n // Default to sandboxed path\n return sandboxedPath;\n }\n\n /**\n * Linux socket path - uses XDG_CACHE_HOME or ~/.cache.\n */\n private getLinuxSocketPath(): string {\n const cacheDir =\n process.env.XDG_CACHE_HOME != null\n ? process.env.XDG_CACHE_HOME\n : path.join(os.homedir(), \".cache\");\n return path.join(cacheDir, \"com.bitwarden.desktop\", \"s.bw\");\n }\n\n /**\n * Check if the desktop app socket exists (quick availability check).\n */\n async isSocketAvailable(): Promise<boolean> {\n const socketPath = this.getSocketPath();\n try {\n await fs.promises.access(socketPath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Connect to the desktop app's IPC socket.\n */\n async connect(): Promise<void> {\n if (this.socket != null) {\n return;\n }\n\n const socketPath = this.getSocketPath();\n\n if (DEBUG) {\n console.error(`[DEBUG] Connecting to socket: ${socketPath}`);\n }\n\n return new Promise((resolve, reject) => {\n const socket = net.createConnection(socketPath);\n\n socket.on(\"connect\", () => {\n if (DEBUG) {\n console.error(`[DEBUG] Socket connected`);\n }\n this.socket = socket;\n resolve();\n });\n\n socket.on(\"data\", (data: Buffer) => {\n if (DEBUG) {\n console.error(`[DEBUG] Received raw data: ${data.length} bytes`);\n }\n this.processIncomingData(data);\n });\n\n socket.on(\"error\", (err) => {\n if (this.socket == null) {\n reject(new Error(`Failed to connect to desktop app: ${err.message}`));\n }\n });\n\n socket.on(\"close\", () => {\n this.socket = null;\n this.messageBuffer = Buffer.alloc(0);\n if (this.disconnectHandler) {\n this.disconnectHandler();\n }\n });\n\n // Timeout for initial connection\n socket.setTimeout(5000, () => {\n if (this.socket == null) {\n socket.destroy();\n reject(new Error(\"Connection to desktop app timed out\"));\n }\n });\n });\n }\n\n /**\n * Disconnect from the socket.\n */\n disconnect(): void {\n if (this.socket != null) {\n this.socket.destroy();\n this.socket = null;\n }\n this.messageBuffer = Buffer.alloc(0);\n }\n\n /**\n * Check if currently connected.\n */\n isConnected(): boolean {\n return this.socket != null && !this.socket.destroyed;\n }\n\n /**\n * Set the handler for incoming messages.\n */\n onMessage(handler: (message: unknown) => void): void {\n this.messageHandler = handler;\n }\n\n /**\n * Set the handler for disconnect events.\n */\n onDisconnect(handler: () => void): void {\n this.disconnectHandler = handler;\n }\n\n /**\n * Send a message to the desktop app.\n * Uses length-delimited protocol: 4-byte little-endian length prefix + JSON payload.\n */\n sendMessage(message: unknown): void {\n if (this.socket == null || this.socket.destroyed) {\n throw new Error(\"Not connected to desktop app\");\n }\n\n const messageStr = JSON.stringify(message);\n const messageBytes = Buffer.from(messageStr, \"utf8\");\n\n // Create buffer with 4-byte length prefix (little-endian)\n const buffer = Buffer.alloc(4 + messageBytes.length);\n buffer.writeUInt32LE(messageBytes.length, 0);\n messageBytes.copy(buffer, 4);\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Sending ${buffer.length} bytes (message: ${messageBytes.length} bytes)`,\n );\n }\n\n this.socket.write(buffer);\n }\n\n /**\n * Process incoming data from the socket.\n * Messages are length-delimited: 4-byte LE length + JSON payload.\n */\n private processIncomingData(data: Buffer): void {\n this.messageBuffer = Buffer.concat([this.messageBuffer, data]);\n\n // Process all complete messages in the buffer\n while (this.messageBuffer.length >= 4) {\n const messageLength = this.messageBuffer.readUInt32LE(0);\n\n // Check if we have the full message\n if (this.messageBuffer.length < 4 + messageLength) {\n break;\n }\n\n // Extract and parse the message\n const messageBytes = this.messageBuffer.subarray(4, 4 + messageLength);\n const messageStr = messageBytes.toString(\"utf8\");\n\n // Update buffer to remove processed message\n this.messageBuffer = this.messageBuffer.subarray(4 + messageLength);\n\n try {\n const message = JSON.parse(messageStr);\n if (this.messageHandler) {\n this.messageHandler(message);\n }\n } catch {\n // Failed to parse message\n }\n }\n }\n}\n","import * as crypto from \"node:crypto\";\nimport { IpcSocketService } from \"./ipc-socket.service\";\n\nconst MESSAGE_VALID_TIMEOUT = 10 * 1000; // 10 seconds\nconst DEFAULT_TIMEOUT = 10 * 1000; // 10 seconds for protocol messages\nconst USER_INTERACTION_TIMEOUT = 60 * 1000; // 60 seconds for biometric prompts\n\nconst DEBUG = process.env.BWBIO_DEBUG === \"true\";\n\n/**\n * Biometrics commands matching the desktop app's expected commands.\n */\nexport const BiometricsCommands = {\n AuthenticateWithBiometrics: \"authenticateWithBiometrics\",\n GetBiometricsStatus: \"getBiometricsStatus\",\n UnlockWithBiometricsForUser: \"unlockWithBiometricsForUser\",\n GetBiometricsStatusForUser: \"getBiometricsStatusForUser\",\n CanEnableBiometricUnlock: \"canEnableBiometricUnlock\",\n} as const;\n\n/**\n * Biometrics status enum matching the desktop app's BiometricsStatus.\n */\nexport enum BiometricsStatus {\n Available = 0,\n UnlockNeeded = 1,\n HardwareUnavailable = 2,\n AutoSetupNeeded = 3,\n ManualSetupNeeded = 4,\n PlatformUnsupported = 5,\n DesktopDisconnected = 6,\n NotEnabledLocally = 7,\n NotEnabledInConnectedDesktopApp = 8,\n NativeMessagingPermissionMissing = 9,\n}\n\ntype Message = {\n command: string;\n messageId?: number;\n userId?: string;\n timestamp?: number;\n publicKey?: string;\n};\n\ntype OuterMessage = {\n message: Message | EncryptedMessage;\n appId: string;\n};\n\ntype EncryptedMessage = {\n encryptedString: string;\n encryptionType: number;\n data: string;\n iv: string;\n mac: string;\n};\n\ntype ReceivedMessage = {\n timestamp: number;\n command: string;\n messageId: number;\n response?: unknown;\n userKeyB64?: string;\n};\n\ntype ReceivedMessageOuter = {\n command: string;\n appId: string;\n messageId?: number;\n message?: ReceivedMessage | EncryptedMessage;\n sharedSecret?: string;\n};\n\ntype Callback = {\n resolver: (value: ReceivedMessage) => void;\n rejecter: (reason?: unknown) => void;\n timeout: ReturnType<typeof setTimeout>;\n};\n\ntype SecureChannel = {\n privateKey: crypto.KeyObject;\n publicKey: crypto.KeyObject;\n sharedSecret?: Buffer;\n setupResolve?: () => void;\n setupReject?: (reason?: unknown) => void;\n};\n\n/**\n * Native messaging client for communicating with the Bitwarden desktop app.\n *\n * This implements the same IPC protocol used by the browser extension:\n * 1. Connect to the desktop app via Unix socket / named pipe\n * 2. Set up encrypted communication using RSA key exchange\n * 3. Send/receive encrypted commands (biometric unlock, status checks, etc.)\n */\nexport class NativeMessagingClient {\n private connected = false;\n private connecting = false;\n private appId: string;\n\n private secureChannel: SecureChannel | null = null;\n private messageId = 0;\n private callbacks = new Map<number, Callback>();\n\n private ipcSocket: IpcSocketService;\n private userId: string | null = null;\n\n constructor(appId: string, userId?: string) {\n this.appId = appId;\n this.userId = userId ?? null;\n this.ipcSocket = new IpcSocketService();\n }\n\n /**\n * Check if the desktop app is available (socket exists).\n */\n async isDesktopAppAvailable(): Promise<boolean> {\n return this.ipcSocket.isSocketAvailable();\n }\n\n /**\n * Connect to the desktop app.\n */\n async connect(): Promise<void> {\n if (this.connected || this.connecting) {\n return;\n }\n\n this.connecting = true;\n\n try {\n await this.ipcSocket.connect();\n\n // Set up message handler\n this.ipcSocket.onMessage((message) => {\n this.handleMessage(message as ReceivedMessageOuter);\n });\n\n this.ipcSocket.onDisconnect(() => {\n this.connected = false;\n this.secureChannel = null;\n\n // Clear timeouts and reject all pending callbacks\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(new Error(\"Disconnected from Desktop app\"));\n }\n this.callbacks.clear();\n });\n\n this.connected = true;\n this.connecting = false;\n } catch (e) {\n this.connecting = false;\n throw e;\n }\n }\n\n /**\n * Disconnect from the desktop app.\n */\n disconnect(): void {\n this.ipcSocket.disconnect();\n this.connected = false;\n this.secureChannel = null;\n }\n\n /**\n * Send a command to the desktop app and wait for a response.\n */\n async callCommand(\n message: Message,\n timeoutMs: number = DEFAULT_TIMEOUT,\n ): Promise<ReceivedMessage> {\n const messageId = this.messageId++;\n\n const callback = new Promise<ReceivedMessage>((resolver, rejecter) => {\n const timeout = setTimeout(() => {\n if (this.callbacks.has(messageId)) {\n this.callbacks.delete(messageId);\n rejecter(\n new Error(\"Message timed out waiting for Desktop app response\"),\n );\n }\n }, timeoutMs);\n\n this.callbacks.set(messageId, { resolver, rejecter, timeout });\n });\n\n message.messageId = messageId;\n\n try {\n await this.send(message);\n } catch (e) {\n const cb = this.callbacks.get(messageId);\n if (cb) {\n clearTimeout(cb.timeout);\n this.callbacks.delete(messageId);\n cb.rejecter(e instanceof Error ? e : new Error(String(e)));\n }\n }\n\n return callback;\n }\n\n /**\n * Get biometrics status from the desktop app.\n */\n async getBiometricsStatus(): Promise<BiometricsStatus> {\n const response = await this.callCommand({\n command: BiometricsCommands.GetBiometricsStatus,\n });\n return response.response as BiometricsStatus;\n }\n\n /**\n * Get biometrics status for a specific user.\n */\n async getBiometricsStatusForUser(userId: string): Promise<BiometricsStatus> {\n const response = await this.callCommand({\n command: BiometricsCommands.GetBiometricsStatusForUser,\n userId: userId,\n });\n return response.response as BiometricsStatus;\n }\n\n /**\n * Unlock with biometrics for a specific user.\n * Returns the user key if successful.\n */\n async unlockWithBiometricsForUser(userId: string): Promise<string | null> {\n const response = await this.callCommand(\n {\n command: BiometricsCommands.UnlockWithBiometricsForUser,\n userId: userId,\n },\n USER_INTERACTION_TIMEOUT,\n );\n\n if (response.response) {\n return response.userKeyB64 ?? null;\n }\n\n return null;\n }\n\n /**\n * Send a message to the desktop app (encrypted if secure channel is established).\n */\n private async send(message: Message): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n\n message.userId = this.userId ?? undefined;\n message.timestamp = Date.now();\n\n this.postMessage({\n appId: this.appId,\n message: await this.encryptMessage(message),\n });\n }\n\n /**\n * Encrypt a message using the secure channel's shared secret.\n */\n private async encryptMessage(\n message: Message,\n ): Promise<EncryptedMessage | Message> {\n if (this.secureChannel?.sharedSecret == null) {\n await this.secureCommunication();\n }\n\n // biome-ignore lint/style/noNonNullAssertion: guaranteed by secureCommunication() above\n const sharedSecret = this.secureChannel!.sharedSecret!;\n const messageJson = JSON.stringify(message);\n\n // Use AES-256-CBC encryption (matching Bitwarden's EncryptionType.AesCbc256_HmacSha256_B64 = 2)\n const iv = crypto.randomBytes(16);\n const cipher = crypto.createCipheriv(\n \"aes-256-cbc\",\n sharedSecret.subarray(0, 32),\n iv,\n );\n const encrypted = Buffer.concat([\n cipher.update(messageJson, \"utf8\"),\n cipher.final(),\n ]);\n\n // Create HMAC using the second half of the key\n const macKey = sharedSecret.subarray(32, 64);\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(encrypted);\n const mac = hmac.digest();\n\n return {\n encryptionType: 2, // AesCbc256_HmacSha256_B64\n encryptedString: `2.${iv.toString(\"base64\")}|${encrypted.toString(\"base64\")}|${mac.toString(\"base64\")}`,\n iv: iv.toString(\"base64\"),\n data: encrypted.toString(\"base64\"),\n mac: mac.toString(\"base64\"),\n };\n }\n\n /**\n * Post a message to the IPC socket.\n */\n private postMessage(message: OuterMessage): void {\n try {\n this.ipcSocket.sendMessage(message);\n } catch (e) {\n this.secureChannel = null;\n this.connected = false;\n throw e;\n }\n }\n\n /**\n * Handle incoming messages from the desktop app.\n */\n private async handleMessage(message: ReceivedMessageOuter): Promise<void> {\n if (DEBUG) {\n console.error(\n `[DEBUG] Received message:`,\n JSON.stringify(message, null, 2),\n );\n }\n\n switch (message.command) {\n case \"setupEncryption\":\n if (message.appId !== this.appId) {\n return;\n }\n await this.handleSetupEncryption(message);\n break;\n\n case \"invalidateEncryption\": {\n if (message.appId !== this.appId) {\n return;\n }\n const invalidError = new Error(\n \"Encryption channel invalidated by Desktop app\",\n );\n if (this.secureChannel?.setupReject) {\n this.secureChannel.setupReject(invalidError);\n }\n this.secureChannel = null;\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(invalidError);\n }\n this.callbacks.clear();\n this.connected = false;\n this.ipcSocket.disconnect();\n break;\n }\n\n case \"wrongUserId\": {\n const wrongUserError = new Error(\n \"Account mismatch: CLI and Desktop app are logged into different accounts\",\n );\n if (this.secureChannel?.setupReject) {\n this.secureChannel.setupReject(wrongUserError);\n }\n this.secureChannel = null;\n for (const callback of this.callbacks.values()) {\n clearTimeout(callback.timeout);\n callback.rejecter(wrongUserError);\n }\n this.callbacks.clear();\n this.connected = false;\n this.ipcSocket.disconnect();\n break;\n }\n\n case \"verifyDesktopIPCFingerprint\":\n await this.showFingerprint();\n break;\n\n default:\n // Ignore messages for other apps\n if (message.appId !== this.appId) {\n return;\n }\n\n if (message.message != null) {\n await this.handleEncryptedMessage(message.message);\n }\n }\n }\n\n /**\n * Handle the setupEncryption response from the desktop app.\n */\n private async handleSetupEncryption(\n message: ReceivedMessageOuter,\n ): Promise<void> {\n if (DEBUG) {\n console.error(\n `[DEBUG] handleSetupEncryption called, sharedSecret present: ${message.sharedSecret != null}`,\n );\n }\n\n if (message.sharedSecret == null) {\n if (DEBUG) {\n console.error(`[DEBUG] No sharedSecret in message`);\n }\n return;\n }\n\n if (this.secureChannel == null) {\n if (DEBUG) {\n console.error(`[DEBUG] No secureChannel setup`);\n }\n return;\n }\n\n // Decrypt the shared secret using our private key (RSA-OAEP with SHA-1)\n const encrypted = Buffer.from(message.sharedSecret, \"base64\");\n if (DEBUG) {\n console.error(\n `[DEBUG] Encrypted sharedSecret length: ${encrypted.length}`,\n );\n }\n\n const decrypted = crypto.privateDecrypt(\n {\n key: this.secureChannel.privateKey,\n oaepHash: \"sha1\",\n padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,\n },\n encrypted,\n );\n\n this.secureChannel.sharedSecret = decrypted;\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Decrypted sharedSecret length: ${this.secureChannel.sharedSecret.length}`,\n );\n }\n\n if (this.secureChannel.setupResolve) {\n this.secureChannel.setupResolve();\n }\n }\n\n /**\n * Handle an encrypted message from the desktop app.\n */\n private async handleEncryptedMessage(\n rawMessage: ReceivedMessage | EncryptedMessage,\n ): Promise<void> {\n if (this.secureChannel?.sharedSecret == null) {\n return;\n }\n\n let message: ReceivedMessage;\n\n if (\"encryptionType\" in rawMessage || \"encryptedString\" in rawMessage) {\n // Decrypt the message\n const encMsg = rawMessage as EncryptedMessage;\n const iv = Buffer.from(encMsg.iv, \"base64\");\n const data = Buffer.from(encMsg.data, \"base64\");\n const mac = Buffer.from(encMsg.mac, \"base64\");\n\n const sharedSecret = this.secureChannel.sharedSecret;\n const encKey = sharedSecret.subarray(0, 32);\n const macKey = sharedSecret.subarray(32, 64);\n\n // Verify HMAC\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(data);\n const expectedMac = hmac.digest();\n\n if (!crypto.timingSafeEqual(mac, expectedMac)) {\n throw new Error(\"Message integrity check failed\");\n }\n\n // Decrypt\n const decipher = crypto.createDecipheriv(\"aes-256-cbc\", encKey, iv);\n const decrypted = Buffer.concat([\n decipher.update(data),\n decipher.final(),\n ]);\n message = JSON.parse(decrypted.toString(\"utf8\"));\n } else {\n message = rawMessage as ReceivedMessage;\n }\n\n this.processDecryptedMessage(message);\n }\n\n /**\n * Process a decrypted message and resolve any pending callbacks.\n */\n private processDecryptedMessage(message: ReceivedMessage): void {\n if (DEBUG) {\n console.error(\n `[DEBUG] Decrypted message:`,\n JSON.stringify(message, null, 2),\n );\n }\n\n if (Math.abs(message.timestamp - Date.now()) > MESSAGE_VALID_TIMEOUT) {\n if (DEBUG) {\n console.error(\n `[DEBUG] Message too old, ignoring. Timestamp: ${message.timestamp}, now: ${Date.now()}`,\n );\n }\n return;\n }\n\n const messageId = message.messageId;\n\n if (this.callbacks.has(messageId)) {\n // biome-ignore lint/style/noNonNullAssertion: guaranteed by .has() check above\n const callback = this.callbacks.get(messageId)!;\n clearTimeout(callback.timeout);\n this.callbacks.delete(messageId);\n callback.resolver(message);\n } else if (DEBUG) {\n console.error(`[DEBUG] No callback found for messageId: ${messageId}`);\n }\n }\n\n /**\n * Set up secure communication with RSA key exchange.\n */\n private async secureCommunication(): Promise<void> {\n // Generate RSA key pair\n const { publicKey, privateKey } = crypto.generateKeyPairSync(\"rsa\", {\n modulusLength: 2048,\n });\n\n // Export public key in SPKI/DER format (base64 encoded)\n const publicKeyDer = publicKey.export({ type: \"spki\", format: \"der\" });\n const publicKeyB64 = publicKeyDer.toString(\"base64\");\n\n const setupMessage = {\n appId: this.appId,\n message: {\n command: \"setupEncryption\",\n publicKey: publicKeyB64,\n userId: this.userId ?? undefined,\n messageId: this.messageId++,\n timestamp: Date.now(),\n },\n };\n\n if (DEBUG) {\n console.error(\n `[DEBUG] Sending setupEncryption:`,\n JSON.stringify(\n {\n ...setupMessage,\n message: {\n ...setupMessage.message,\n publicKey: `${publicKeyB64.slice(0, 50)}...`,\n },\n },\n null,\n 2,\n ),\n );\n }\n\n this.postMessage(setupMessage);\n\n return new Promise((resolve, reject) => {\n this.secureChannel = {\n publicKey,\n privateKey,\n setupResolve: resolve,\n setupReject: reject,\n };\n\n // Timeout for key exchange\n setTimeout(() => {\n if (this.secureChannel && !this.secureChannel.sharedSecret) {\n reject(new Error(\"Secure channel setup timed out\"));\n }\n }, DEFAULT_TIMEOUT);\n });\n }\n\n /**\n * Display the fingerprint for verification.\n */\n private async showFingerprint(): Promise<void> {\n if (this.secureChannel?.publicKey == null) {\n return;\n }\n\n // Generate fingerprint from public key\n const publicKeyDer = this.secureChannel.publicKey.export({\n type: \"spki\",\n format: \"der\",\n });\n const hash = crypto.createHash(\"sha256\").update(publicKeyDer).digest();\n\n // Format as 5 groups of alphanumeric characters (like Bitwarden)\n const fingerprint = hash.toString(\"hex\").slice(0, 25).toUpperCase();\n const formatted = fingerprint.match(/.{1,5}/g)?.join(\"-\") || fingerprint;\n\n // Write to stderr so it doesn't interfere with command output\n const dim = \"\\x1b[2m\";\n const cyan = \"\\x1b[36m\";\n const bold = \"\\x1b[1m\";\n const reset = \"\\x1b[0m\";\n\n console.error(\"\");\n console.error(`${bold}Bitwarden Desktop App Verification${reset}`);\n console.error(\n \"Verify this fingerprint matches the one shown in the Desktop app:\",\n );\n console.error(\"\");\n console.error(`${dim} ${cyan}${formatted}${reset}`);\n console.error(\"\");\n console.error(\"Accept the connection in the Desktop app to continue.\");\n console.error(\"\");\n }\n}\n","/**\n * Log a message to stderr unless BW_QUIET is set.\n */\nexport function log(message: string): void {\n if (process.env.BW_QUIET !== \"true\") {\n console.error(message);\n }\n}\n\n/**\n * Log a message to stderr only when BWBIO_VERBOSE is set (and not quiet).\n */\nexport function logVerbose(message: string): void {\n if (process.env.BWBIO_VERBOSE === \"true\" && process.env.BW_QUIET !== \"true\") {\n console.error(message);\n }\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * AesCbc256_HmacSha256_B64 encryption type (matches Bitwarden's format)\n */\nconst ENCRYPTION_TYPE = 2;\n\n/**\n * Get the CLI data directory path for the current platform.\n */\nfunction getCliDataDir(): string {\n if (process.env.BITWARDENCLI_APPDATA_DIR) {\n return path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);\n }\n\n const platform = os.platform();\n const homeDir = os.homedir();\n\n if (platform === \"darwin\") {\n return path.join(homeDir, \"Library/Application Support/Bitwarden CLI\");\n } else if (platform === \"win32\") {\n return path.join(process.env.APPDATA ?? homeDir, \"Bitwarden CLI\");\n } else {\n // Linux\n const configDir =\n process.env.XDG_CONFIG_HOME ?? path.join(homeDir, \".config\");\n return path.join(configDir, \"Bitwarden CLI\");\n }\n}\n\n/**\n * Generate a new session key (64 random bytes, base64 encoded).\n */\nexport function generateSessionKey(): string {\n const keyBytes = crypto.randomBytes(64);\n return keyBytes.toString(\"base64\");\n}\n\n/**\n * Encrypt data using AES-256-CBC with HMAC-SHA256 (Bitwarden's type 2 format).\n *\n * @param data - The data to encrypt (as Uint8Array)\n * @param sessionKey - The session key (base64 encoded, 64 bytes when decoded)\n * @returns Encrypted data as base64 string\n */\nfunction encryptWithSessionKey(data: Uint8Array, sessionKey: string): string {\n // Decode session key - first 32 bytes for AES, last 32 for HMAC\n const keyBytes = Buffer.from(sessionKey, \"base64\");\n if (keyBytes.length !== 64) {\n throw new Error(\"Session key must be 64 bytes\");\n }\n\n const encKey = keyBytes.subarray(0, 32);\n const macKey = keyBytes.subarray(32, 64);\n\n // Generate random IV\n const iv = crypto.randomBytes(16);\n\n // Encrypt with AES-256-CBC\n const cipher = crypto.createCipheriv(\"aes-256-cbc\", encKey, iv);\n const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);\n\n // Calculate HMAC-SHA256 over IV + ciphertext\n const hmac = crypto.createHmac(\"sha256\", macKey);\n hmac.update(iv);\n hmac.update(encrypted);\n const mac = hmac.digest();\n\n // Assemble: [encType, iv, mac, ciphertext]\n const result = Buffer.alloc(1 + 16 + 32 + encrypted.length);\n result.writeUInt8(ENCRYPTION_TYPE, 0);\n iv.copy(result, 1);\n mac.copy(result, 17);\n encrypted.copy(result, 49);\n\n return result.toString(\"base64\");\n}\n\n/**\n * Read the CLI's data.json file.\n */\nfunction readCliData(): Record<string, unknown> {\n const dataPath = path.join(getCliDataDir(), \"data.json\");\n\n try {\n const content = fs.readFileSync(dataPath, \"utf-8\");\n return content ? JSON.parse(content) : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Get the active user ID from CLI's data storage.\n */\nexport function getActiveUserId(): string | null {\n const data = readCliData();\n const userId = data.global_account_activeAccountId;\n return typeof userId === \"string\" ? userId : null;\n}\n\n/**\n * Write to the CLI's data.json file.\n */\nfunction writeCliData(data: Record<string, unknown>): void {\n const dataDir = getCliDataDir();\n const dataPath = path.join(dataDir, \"data.json\");\n\n // Ensure directory exists\n if (!fs.existsSync(dataDir)) {\n fs.mkdirSync(dataDir, { recursive: true, mode: 0o700 });\n }\n\n // Write with restrictive permissions\n fs.writeFileSync(dataPath, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Store the user key in CLI's data storage, encrypted with the session key.\n *\n * This mimics what the CLI does internally:\n * - Encrypts the user key with BW_SESSION\n * - Stores it in data.json with key \"__PROTECTED__{userId}_user_auto\"\n *\n * @param userKeyB64 - The user key from biometric unlock (base64 encoded)\n * @param userId - The user ID\n * @param sessionKey - The BW_SESSION key (base64 encoded, 64 bytes)\n */\nexport function storeUserKeyForSession(\n userKeyB64: string,\n userId: string,\n sessionKey: string,\n): void {\n // The user key is already base64 - convert to bytes for encryption\n const userKeyBytes = Buffer.from(userKeyB64, \"base64\");\n\n // Encrypt the user key with the session key\n const encryptedUserKey = encryptWithSessionKey(userKeyBytes, sessionKey);\n\n // Store in CLI's data.json\n const storageKey = `__PROTECTED__${userId}_user_auto`;\n\n const data = readCliData();\n data[storageKey] = encryptedUserKey;\n writeCliData(data);\n}\n\n/**\n * Clean up the stored user key.\n *\n * @param userId - The user ID\n */\nexport function clearStoredUserKey(userId: string): void {\n const storageKey = `__PROTECTED__${userId}_user_auto`;\n\n const data = readCliData();\n delete data[storageKey];\n writeCliData(data);\n}\n","/**\n * Passthrough command detection\n *\n * These commands are passed directly to `bw` without attempting biometric unlock.\n */\n\nconst PASSTHROUGH_COMMANDS = new Set([\n \"login\",\n \"logout\",\n \"lock\",\n \"config\",\n \"update\",\n \"completion\",\n \"status\",\n \"serve\",\n]);\n\nconst PASSTHROUGH_FLAGS = new Set([\"--help\", \"-h\", \"--version\", \"-v\"]);\n\n/**\n * Determines if the given arguments represent a passthrough command.\n *\n * Passthrough commands are executed directly without biometric unlock:\n * - Commands that don't need an unlocked vault (login, logout, status, etc.)\n * - Help and version flags\n *\n * @param args - Command line arguments (without node and script path)\n * @returns true if this is a passthrough command\n */\nexport function isPassthroughCommand(args: string[]): boolean {\n if (args.length === 0) {\n // No args = show help, which is passthrough\n return true;\n }\n\n const firstArg = args[0];\n\n // Check for passthrough flags anywhere in args\n if (args.some((arg) => PASSTHROUGH_FLAGS.has(arg))) {\n return true;\n }\n\n // Check if first arg is a passthrough command\n if (PASSTHROUGH_COMMANDS.has(firstArg)) {\n return true;\n }\n\n return false;\n}\n","#!/usr/bin/env node\n\nimport { main } from \"./main\";\n\n// Get command line arguments (skip node and script path)\nconst args = process.argv.slice(2);\n\nmain(args)\n .then((code) => {\n process.exit(code);\n })\n .catch((err) => {\n console.error(\"Unexpected error:\", err);\n process.exit(1);\n });\n"],"mappings":"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","names":["crypto","platform","resolve","crypto","DEBUG","BiometricsStatus","resolve","crypto","fs","os","path","platform","args","args","resolve"]}
|