Secure Secret Vault Extension TypeScript

👤 Sharing: AI
```typescript
// src/extension.ts

import * as vscode from 'vscode';

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

	// Use the console to diagnose issues and errors
	console.log('Congratulations, your extension "secure-secret-vault" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let storeSecretCommand = vscode.commands.registerCommand('secure-secret-vault.storeSecret', async () => {
		// The code you place here will be executed every time your command is executed

		// 1. Prompt the user for the secret key
		const key = await vscode.window.showInputBox({
			prompt: 'Enter the secret key (e.g., API_KEY):',
			placeHolder: 'Secret Key',
			validateInput: (value: string | undefined) => {
				if (!value || value.trim() === '') {
					return 'Key cannot be empty.';
				}
				return null; // Return null for valid input
			}
		});

		if (!key) {
			// User cancelled the input.  Don't proceed.
			return;
		}

		// 2. Prompt the user for the secret value
		const secret = await vscode.window.showInputBox({
			prompt: `Enter the value for secret key "${key}":`,
			placeHolder: 'Secret Value',
			password: true, // Hide the input
			validateInput: (value: string | undefined) => {
				if (!value || value.trim() === '') {
					return 'Secret value cannot be empty.';
				}
				return null; // Return null for valid input
			}
		});

		if (!secret) {
			// User cancelled the input.  Don't proceed.
			return;
		}

		// 3. Store the secret using VS Code's secret storage
		try {
			await context.secrets.store(key, secret);
			vscode.window.showInformationMessage(`Secret "${key}" successfully stored!`);
		} catch (error: any) {
			vscode.window.showErrorMessage(`Failed to store secret "${key}": ${error.message}`);
		}
	});

	let getSecretCommand = vscode.commands.registerCommand('secure-secret-vault.getSecret', async () => {
		// 1. Prompt the user for the secret key
		const key = await vscode.window.showInputBox({
			prompt: 'Enter the secret key to retrieve:',
			placeHolder: 'Secret Key',
			validateInput: (value: string | undefined) => {
				if (!value || value.trim() === '') {
					return 'Key cannot be empty.';
				}
				return null; // Return null for valid input
			}
		});

		if (!key) {
			// User cancelled the input.  Don't proceed.
			return;
		}

		// 2. Retrieve the secret
		try {
			const secret = await context.secrets.get(key);

			if (secret) {
				// 3. Show the secret (ideally, don't show directly, but for demo purposes)
				//  A real application would use the secret internally.
				vscode.window.showInformationMessage(`Secret "${key}" value: ${secret}`);
			} else {
				vscode.window.showWarningMessage(`Secret "${key}" not found.`);
			}
		} catch (error: any) {
			vscode.window.showErrorMessage(`Failed to retrieve secret "${key}": ${error.message}`);
		}
	});


	let deleteSecretCommand = vscode.commands.registerCommand('secure-secret-vault.deleteSecret', async () => {
		// 1. Prompt the user for the secret key to delete
		const key = await vscode.window.showInputBox({
			prompt: 'Enter the secret key to delete:',
			placeHolder: 'Secret Key',
			validateInput: (value: string | undefined) => {
				if (!value || value.trim() === '') {
					return 'Key cannot be empty.';
				}
				return null; // Return null for valid input
			}
		});

		if (!key) {
			// User cancelled the input.  Don't proceed.
			return;
		}

		// 2. Confirm deletion with the user
		const confirmation = await vscode.window.showWarningMessage(
			`Are you sure you want to delete secret "${key}"?`,
			{ modal: true }, // Make the dialog modal to ensure user response
			'Yes',
			'No'
		);

		if (confirmation === 'Yes') {
			// 3. Delete the secret
			try {
				await context.secrets.delete(key);
				vscode.window.showInformationMessage(`Secret "${key}" successfully deleted!`);
			} catch (error: any) {
				vscode.window.showErrorMessage(`Failed to delete secret "${key}": ${error.message}`);
			}
		} else {
			vscode.window.showInformationMessage('Deletion cancelled.');
		}
	});




	context.subscriptions.push(storeSecretCommand, getSecretCommand, deleteSecretCommand);
}

// This method is called when your extension is deactivated
export function deactivate() {}
```

```json
// package.json
{
	"name": "secure-secret-vault",
	"displayName": "Secure Secret Vault",
	"description": "A simple VS Code extension for securely storing and retrieving secrets using VS Code's secret storage API.",
	"version": "0.0.1",
	"engines": {
		"vscode": "^1.85.0"
	},
	"categories": [
		"Other"
	],
	"activationEvents": [
		"onCommand:secure-secret-vault.storeSecret",
		"onCommand:secure-secret-vault.getSecret",
		"onCommand:secure-secret-vault.deleteSecret"
	],
	"main": "./out/extension.js",
	"contributes": {
		"commands": [
			{
				"command": "secure-secret-vault.storeSecret",
				"title": "Secure Secret Vault: Store Secret"
			},
			{
				"command": "secure-secret-vault.getSecret",
				"title": "Secure Secret Vault: Get Secret"
			},
			{
				"command": "secure-secret-vault.deleteSecret",
				"title": "Secure Secret Vault: Delete Secret"
			}
		],
		"menus": {
			"editor/context": [
				{
					"command": "secure-secret-vault.storeSecret",
					"group": "z_vscode"
				},
				{
					"command": "secure-secret-vault.getSecret",
					"group": "z_vscode"
				},
				{
					"command": "secure-secret-vault.deleteSecret",
					"group": "z_vscode"
				}
			],
			"explorer/context": [
				{
					"command": "secure-secret-vault.storeSecret",
					"group": "z_vscode"
				},
				{
					"command": "secure-secret-vault.getSecret",
					"group": "z_vscode"
				},
				{
					"command": "secure-secret-vault.deleteSecret",
					"group": "z_vscode"
				}
			],
			"commandPalette": [
				{
					"command": "secure-secret-vault.storeSecret",
					"when": "true"
				},
				{
					"command": "secure-secret-vault.getSecret",
					"when": "true"
				},
				{
					"command": "secure-secret-vault.deleteSecret",
					"when": "true"
				}
			]
		}
	},
	"scripts": {
		"vscode:prepublish": "npm run compile",
		"compile": "tsc -p ./",
		"watch": "tsc -watch -p ./",
		"pretest": "npm run compile && npm run lint",
		"lint": "eslint src --ext ts",
		"test": "node ./out/test/runTest.js"
	},
	"devDependencies": {
		"@types/vscode": "^1.85.0",
		"@types/glob": "^8.1.0",
		"@types/mocha": "^10.0.3",
		"@types/node": "18.x",
		"@typescript-eslint/eslint-plugin": "^6.15.0",
		"@typescript-eslint/parser": "^6.15.0",
		"eslint": "^8.56.0",
		"glob": "^8.1.0",
		"mocha": "^10.2.0",
		"typescript": "^5.3.3",
		"@vscode/test-electron": "^2.3.8"
	}
}
```

**Explanation:**

1.  **Project Setup:**
    *   Create a new VS Code extension project using `yo code`.  Choose "TypeScript" as the language.
    *   The `package.json` file is crucial.  It defines:
        *   `name`, `displayName`, `description`, `version`: Basic metadata about your extension.
        *   `engines.vscode`:  Specifies the minimum VS Code version your extension requires.
        *   `activationEvents`:  Determines when your extension is activated.  In this case, it's activated when the `storeSecret`, `getSecret`, or `deleteSecret` commands are executed. Using `*` would activate the extension on VS Code start, which is generally discouraged.  Activation only when needed improves performance.
        *   `main`:  The entry point of your extension (`./out/extension.js`, which is the compiled JavaScript from `src/extension.ts`).
        *   `contributes.commands`: Defines the commands your extension provides. Each command has an `id` (used in code) and a `title` (shown to the user).
        *   `contributes.menus`:  Adds your commands to various VS Code menus (e.g., editor context menu, explorer context menu, command palette).  This makes it easy for users to find and use your extension.   The `commandPalette` is important because it allows the user to trigger the commands using Ctrl+Shift+P (or Cmd+Shift+P on macOS).

2.  **`src/extension.ts` (Main Extension File):**
    *   `activate(context: vscode.ExtensionContext)`:  This function is called when your extension is activated.  The `context` object provides access to VS Code APIs and allows you to register commands and store extension-specific data.
    *   `vscode.commands.registerCommand(commandId: string, callback: Function)`:  This is how you register a command with VS Code.  The `commandId` must match the one defined in your `package.json`. The `callback` function is executed when the command is invoked.
    *   **Storing Secrets (`storeSecretCommand`):**
        *   `vscode.window.showInputBox()`:  This displays an input box to the user, prompting them to enter the secret key and value.
        *   `validateInput`: A function to validate the input and ensure it is not empty.
        *   `password: true`:  This makes the input box behave like a password field, hiding the entered text.
        *   `context.secrets.store(key: string, value: string)`:  This is the core of the extension.  It uses VS Code's `secrets` API to securely store the secret.  The secret is stored in the VS Code's secure storage backend (e.g., the operating system's keychain).  The key is used to identify the secret.
        *   Error Handling: The `try...catch` block handles potential errors during secret storage (e.g., if the user denies access to the keychain).
    *   **Retrieving Secrets (`getSecretCommand`):**
        *   Similar to storing, it prompts the user for the secret key.
        *   `context.secrets.get(key: string)`:  Retrieves the secret associated with the given key.
        *   Displaying the Secret:  **Important:** In a real-world application, you would *not* typically display the secret directly to the user.  Instead, you would use it internally within your extension (e.g., to make an API call). Showing the secret defeats the purpose of secure storage.  This example displays the secret for demonstration purposes only.
    *   **Deleting Secrets (`deleteSecretCommand`):**
        *   Prompts the user for the key of the secret to delete.
        *   Confirmation Dialog:  Uses `vscode.window.showWarningMessage` with the `modal: true` option to show a modal confirmation dialog.  This prevents accidental deletions.
        *   `context.secrets.delete(key: string)`:  Deletes the secret associated with the given key.
    *   `context.subscriptions.push(...)`:  Registers the commands with VS Code so they are properly disposed of when the extension is deactivated.
    *   `deactivate()`: This function is called when the extension is deactivated.  You can use it to clean up resources.

**How to Run the Extension:**

1.  **Install the Yeoman extension generator:**  If you don't have it already, run `npm install -g yo generator-code`.
2.  **Create the extension project:**  Run `yo code` and follow the prompts. Choose TypeScript as the language and select "New Extension (TypeScript)" as the type.
3.  **Replace the contents of `src/extension.ts` and `package.json`** with the code provided above.
4.  **Compile the extension:**  Run `npm install` to install the dependencies, and then `npm run compile` to compile the TypeScript code into JavaScript.
5.  **Run the extension in debug mode:**  In VS Code, press F5 to launch a new VS Code window with your extension loaded.
6.  **Use the commands:**  Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) to open the command palette, and then type "Secure Secret Vault" to find and run the `Store Secret`, `Get Secret`, and `Delete Secret` commands.

**Key Considerations and Security Best Practices:**

*   **Never hardcode secrets:**  This is the most important rule.  Never store API keys, passwords, or other sensitive information directly in your code.
*   **Use secure storage:** VS Code's `secrets` API provides a secure way to store secrets. It uses the operating system's keychain or credential manager to protect the secrets.  Don't roll your own encryption or storage solution unless you're an expert in cryptography.
*   **Minimize exposure:** Only retrieve and use secrets when absolutely necessary.  Avoid storing secrets in memory for longer than required.
*   **User confirmation for deletion:** Always ask for confirmation before deleting a secret to prevent accidental data loss.
*   **Don't display secrets directly (in real applications):** As mentioned earlier, showing the secret value defeats the purpose of secure storage. Instead, use the secret internally within your extension.
*   **Error handling:** Implement robust error handling to gracefully handle situations where secret storage or retrieval fails (e.g., if the user denies access to the keychain).
*   **Permissions:**  Consider the necessary permissions for your extension.  Only request the permissions you need.
*   **Code review:**  Have your code reviewed by another developer, especially if it deals with sensitive information.

This example demonstrates a basic secure secret vault.  A more advanced extension could provide features like:

*   Secret management UI (instead of just input boxes)
*   Support for multiple secrets
*   Integration with environment variables
*   Secret rotation

Remember to adapt the code and security measures to the specific needs of your extension.
👁️ Viewed: 8

Comments