@bitwarden/mcp-server 2025.7.1 → 2025.7.2

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +130 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -147,6 +147,7 @@ The server provides the following Bitwarden CLI tools:
147
147
  - **Use environment variables** for sensitive configuration
148
148
  - **Validate all inputs** using Zod schemas (already implemented)
149
149
  - **Test with non-production data** when possible
150
+ - Understand the security and privacy impacts of exposing sensitive vault data to LLM and AI tools. Using a self-hosted or local LLM may be appropriate, for example.
150
151
 
151
152
  ## Troubleshooting
152
153
 
package/dist/index.js CHANGED
@@ -492,7 +492,101 @@ export function validateInput(schema, args) {
492
492
  }
493
493
  const execPromise = promisify(exec);
494
494
  /**
495
- * Executes a Bitwarden CLI command and returns the result.
495
+ * Sanitizes a string to prevent command injection by removing dangerous characters
496
+ * and escape sequences that could be used to execute arbitrary commands.
497
+ *
498
+ * @param {string} input - The input string to sanitize
499
+ * @returns {string} The sanitized string safe for use in shell commands
500
+ */
501
+ export function sanitizeInput(input) {
502
+ if (typeof input !== 'string') {
503
+ throw new Error('Input must be a string');
504
+ }
505
+ // Remove or escape dangerous characters that could be used for command injection
506
+ return (input
507
+ // Remove null bytes
508
+ .replace(/\0/g, '')
509
+ // Remove command separators and operators
510
+ .replace(/[;&|`$(){}[\]<>'"]/g, '')
511
+ // Remove escape sequences and control characters
512
+ .replace(/\\./g, '')
513
+ // Remove newlines and carriage returns
514
+ .replace(/[\r\n]/g, '')
515
+ // Remove tab characters
516
+ .replace(/\t/g, ' ')
517
+ // Collapse multiple spaces
518
+ .replace(/\s+/g, ' ')
519
+ // Trim whitespace
520
+ .trim());
521
+ }
522
+ /**
523
+ * Safely escapes a parameter value for use in shell commands by using single quotes
524
+ * and properly escaping any single quotes within the value.
525
+ *
526
+ * @param {string} value - The parameter value to escape
527
+ * @returns {string} The safely escaped parameter
528
+ */
529
+ export function escapeShellParameter(value) {
530
+ if (typeof value !== 'string') {
531
+ throw new Error('Parameter must be a string');
532
+ }
533
+ // Replace single quotes with '\'' (end quote, escaped quote, start quote)
534
+ // This is the safest way to handle single quotes in shell parameters
535
+ return `'${value.replace(/'/g, "'\\''")}'`;
536
+ }
537
+ /**
538
+ * Builds a safe Bitwarden CLI command with properly escaped parameters.
539
+ *
540
+ * @param {string} baseCommand - The base command (e.g., 'get', 'list')
541
+ * @param {string[]} parameters - Array of parameters to append safely
542
+ * @returns {string} The safely constructed command
543
+ */
544
+ export function buildSafeCommand(baseCommand, parameters = []) {
545
+ // Sanitize the base command
546
+ const sanitizedBase = sanitizeInput(baseCommand);
547
+ // Escape all parameters
548
+ const escapedParams = parameters.map((param) => escapeShellParameter(param));
549
+ // Combine base command with escaped parameters
550
+ return [sanitizedBase, ...escapedParams].join(' ');
551
+ }
552
+ /**
553
+ * Validates that a command is safe and contains only allowed Bitwarden CLI commands.
554
+ *
555
+ * @param {string} command - The command to validate
556
+ * @returns {boolean} True if the command is safe, false otherwise
557
+ */
558
+ export function isValidBitwardenCommand(command) {
559
+ // List of allowed Bitwarden CLI commands
560
+ const allowedCommands = [
561
+ 'lock',
562
+ 'unlock',
563
+ 'sync',
564
+ 'status',
565
+ 'list',
566
+ 'get',
567
+ 'generate',
568
+ 'create',
569
+ 'edit',
570
+ 'delete',
571
+ 'confirm',
572
+ 'import',
573
+ 'export',
574
+ 'serve',
575
+ 'config',
576
+ 'login',
577
+ 'logout',
578
+ ];
579
+ // Split command into parts
580
+ const parts = command.trim().split(/\s+/);
581
+ if (parts.length === 0) {
582
+ return false;
583
+ }
584
+ // First part should be a valid Bitwarden command
585
+ const baseCommand = parts[0];
586
+ return allowedCommands.includes(baseCommand);
587
+ }
588
+ /**
589
+ * Executes a Bitwarden CLI command safely with input sanitization and validation.
496
590
  *
497
591
  * @async
498
592
  * @param {string} command - The Bitwarden CLI command to execute (without 'bw' prefix)
@@ -500,7 +594,16 @@ const execPromise = promisify(exec);
500
594
  */
501
595
  async function executeCliCommand(command) {
502
596
  try {
503
- const { stdout, stderr } = await execPromise(`bw ${command}`);
597
+ // Sanitize the command input
598
+ const sanitizedCommand = sanitizeInput(command);
599
+ // Validate that it's a safe Bitwarden command
600
+ if (!isValidBitwardenCommand(sanitizedCommand)) {
601
+ return {
602
+ errorOutput: 'Invalid or unsafe command. Only Bitwarden CLI commands are allowed.',
603
+ };
604
+ }
605
+ // Execute the sanitized command with bw prefix
606
+ const { stdout, stderr } = await execPromise(`bw ${sanitizedCommand}`);
504
607
  return {
505
608
  output: stdout,
506
609
  errorOutput: stderr,
@@ -534,7 +637,7 @@ async function runServer() {
534
637
  console.error('Bitwarden MCP Server starting ...');
535
638
  const server = new Server({
536
639
  name: 'Bitwarden MCP Server',
537
- version: '2025.7.1',
640
+ version: '2025.7.2',
538
641
  }, {
539
642
  capabilities: {
540
643
  tools: {},
@@ -648,7 +751,8 @@ async function runServer() {
648
751
  return validationResult;
649
752
  }
650
753
  const { object, id } = validationResult;
651
- const result = await executeCliCommand(`get ${object} "${id}"`);
754
+ const command = buildSafeCommand('get', [object, id]);
755
+ const result = await executeCliCommand(command);
652
756
  return {
653
757
  content: [
654
758
  {
@@ -722,7 +826,10 @@ async function runServer() {
722
826
  };
723
827
  const folderJson = JSON.stringify(folderObject);
724
828
  const folderBase64 = Buffer.from(folderJson, 'utf8').toString('base64');
725
- const createCommand = `create folder ${folderBase64}`;
829
+ const createCommand = buildSafeCommand('create', [
830
+ 'folder',
831
+ folderBase64,
832
+ ]);
726
833
  const result = await executeCliCommand(createCommand);
727
834
  return {
728
835
  content: [
@@ -761,7 +868,10 @@ async function runServer() {
761
868
  }
762
869
  const itemJson = JSON.stringify(itemObject);
763
870
  const itemBase64 = Buffer.from(itemJson, 'utf8').toString('base64');
764
- const createCommand = `create item ${itemBase64}`;
871
+ const createCommand = buildSafeCommand('create', [
872
+ 'item',
873
+ itemBase64,
874
+ ]);
765
875
  const result = await executeCliCommand(createCommand);
766
876
  return {
767
877
  content: [
@@ -783,7 +893,8 @@ async function runServer() {
783
893
  const { objectType, id, name: itemName, notes, login, } = validationResult;
784
894
  if (objectType === 'folder') {
785
895
  // Edit folder
786
- const getResult = await executeCliCommand(`get folder ${id}`);
896
+ const command = buildSafeCommand('get', ['folder', id]);
897
+ const getResult = await executeCliCommand(command);
787
898
  if (getResult.errorOutput) {
788
899
  return {
789
900
  content: [
@@ -817,7 +928,11 @@ async function runServer() {
817
928
  }
818
929
  const folderJson = JSON.stringify(currentFolder);
819
930
  const folderBase64 = Buffer.from(folderJson, 'utf8').toString('base64');
820
- const editCommand = `edit folder ${id} ${folderBase64}`;
931
+ const editCommand = buildSafeCommand('edit', [
932
+ 'folder',
933
+ id,
934
+ folderBase64,
935
+ ]);
821
936
  const result = await executeCliCommand(editCommand);
822
937
  return {
823
938
  content: [
@@ -833,7 +948,8 @@ async function runServer() {
833
948
  }
834
949
  else {
835
950
  // Edit item
836
- const getResult = await executeCliCommand(`get item ${id}`);
951
+ const command = buildSafeCommand('get', ['item', id]);
952
+ const getResult = await executeCliCommand(command);
837
953
  if (getResult.errorOutput) {
838
954
  return {
839
955
  content: [
@@ -883,7 +999,11 @@ async function runServer() {
883
999
  // Perform the edit
884
1000
  const itemJson = JSON.stringify(currentItem);
885
1001
  const itemBase64 = Buffer.from(itemJson, 'utf8').toString('base64');
886
- const editCommand = `edit item ${id} ${itemBase64}`;
1002
+ const editCommand = buildSafeCommand('edit', [
1003
+ 'item',
1004
+ id,
1005
+ itemBase64,
1006
+ ]);
887
1007
  const result = await executeCliCommand(editCommand);
888
1008
  return {
889
1009
  content: [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@bitwarden/mcp-server",
4
- "version": "2025.7.1",
4
+ "version": "2025.7.2",
5
5
  "description": "Bitwarden MCP Server",
6
6
  "repository": {
7
7
  "type": "git",