VS Code Extension Development Guide
Complete guide to developing secure VS Code extensions
VS Code Extension Development Guide
Visual Studio Code extensions can significantly enhance the coding experience. This guide covers secure development practices for VS Code extensions.
Getting Started
Prerequisites
Before developing VS Code extensions, ensure you have:
- Node.js (v16+)
- Visual Studio Code
- Yeoman and VS Code Extension Generator
- Basic knowledge of TypeScript/JavaScript
Setup Development Environment
# Install Yeoman and VS Code Extension Generator
npm install -g yo generator-code
# Create a new extension
yo code
# Install dependencies
cd your-extension-name
npm install
Extension Structure
Basic Extension Structure
my-extension/
├── src/
│ ├── extension.ts
│ └── test/
├── package.json
├── tsconfig.json
├── webpack.config.js
└── README.md
Key Files Explained
- package.json - Extension manifest and configuration
- src/extension.ts - Main extension logic
- tsconfig.json - TypeScript configuration
- webpack.config.js - Build configuration
Security Considerations
Extension Manifest Security
{
"name": "my-secure-extension",
"version": "1.0.0",
"engines": {
"vscode": "^1.74.0"
},
"categories": ["Other"],
"activationEvents": ["onCommand:myextension.helloWorld"],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "myextension.helloWorld",
"title": "Hello World"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "webpack",
"watch": "webpack --watch"
},
"devDependencies": {
"@types/vscode": "^1.74.0",
"@types/node": "16.x",
"typescript": "^4.9.4"
}
}
Activation Events
Use specific activation events to minimize security risk:
{
"activationEvents": ["onLanguage:javascript", "onCommand:extension.myCommand", "onUri"]
}
Secure Coding Practices
Input Validation
Always validate user inputs:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('extension.processInput', async () => {
const input = await vscode.window.showInputBox({
prompt: 'Enter your text',
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return 'Input cannot be empty';
}
if (value.length > 100) {
return 'Input is too long (max 100 characters)';
}
return null;
}
});
if (input) {
// Sanitize and process input
const sanitized = sanitizeInput(input);
vscode.window.showInformationMessage(`Processed: ${sanitized}`);
}
});
context.subscriptions.push(disposable);
}
function sanitizeInput(input: string): string {
// Remove potentially dangerous characters
return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}
Secure File Operations
Handle file operations securely:
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
export async function secureFileOperation(uri: vscode.Uri) {
try {
// Validate file path
const filePath = uri.fsPath;
const normalizedPath = path.normalize(filePath);
// Prevent directory traversal
if (normalizedPath.includes('..')) {
vscode.window.showErrorMessage('Invalid file path');
return;
}
// Check file size limit
const stats = fs.statSync(normalizedPath);
if (stats.size > 10 * 1024 * 1024) {
// 10MB limit
vscode.window.showErrorMessage('File too large');
return;
}
// Read file securely
const content = fs.readFileSync(normalizedPath, 'utf8');
// Process content safely
processContent(content);
} catch (error) {
vscode.window.showErrorMessage(`File operation failed: ${error}`);
}
}
Network Security
When making network requests:
import axios from 'axios';
export async function secureApiCall(data: any) {
try {
const response = await axios.post('https://api.example.com/data', data, {
headers: {
'Content-Type': 'application/json',
'User-Agent': vscode.env.appName
},
timeout: 10000, // 10 second timeout
validateStatus: (status) => status < 400
});
return response.data;
} catch (error) {
vscode.window.showErrorMessage(`API call failed: ${error}`);
throw error;
}
}
Common Extension Patterns
Webview Security
When using webviews:
import * as vscode from 'vscode';
export function createSecureWebview(panel: vscode.WebviewPanel) {
panel.webview.html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src ${panel.webview.cspSource} 'unsafe-inline';
script-src ${panel.webview.cspSource};
img-src ${panel.webview.cspSource} https:;
">
</head>
<body>
<h1>Secure Webview</h1>
<div id="content"></div>
<script src="${panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, 'out', 'webview.js'))}"></script>
</body>
</html>
`;
// Handle messages securely
panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case 'requestData':
if (typeof message.data === 'string' && message.data.length < 1000) {
// Process valid data
sendDataResponse(panel.webview);
}
break;
}
},
undefined,
context.subscriptions
);
}
Configuration Security
Handle extension configuration securely:
import * as vscode from 'vscode';
export function getSecureConfig() {
const config = vscode.workspace.getConfiguration('myExtension');
// Get configuration with defaults
const apiUrl = config.get<string>('apiUrl', 'https://api.example.com');
const timeout = config.get<number>('timeout', 5000);
const debug = config.get<boolean>('debug', false);
// Validate configuration values
if (!apiUrl.startsWith('https://')) {
vscode.window.showWarningMessage('API URL should use HTTPS');
}
if (timeout < 1000 || timeout > 30000) {
vscode.window.showWarningMessage('Timeout should be between 1-30 seconds');
}
return {
apiUrl,
timeout,
debug
};
}
Testing and Debugging
Unit Testing
Write comprehensive tests:
import * as assert from 'assert';
import { sanitizeInput } from '../extension';
suite('Extension Test Suite', () => {
test('Sanitize input should remove scripts', () => {
const malicious = '<script>alert("xss")</script>Hello';
const expected = 'Hello';
assert.strictEqual(sanitizeInput(malicious), expected);
});
test('Sanitize input should handle empty strings', () => {
assert.strictEqual(sanitizeInput(''), '');
assert.strictEqual(sanitizeInput(null), '');
});
});
Debugging
Use VS Code's debugging capabilities:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"]
}
]
}
Publishing and Distribution
Package Security
Before publishing:
# Check for vulnerabilities
npm audit
# Remove development dependencies
npm prune --production
# Create vsix package
vsce package
Version Management
Use semantic versioning:
{
"version": "1.2.3",
"engines": {
"vscode": "^1.74.0"
}
}
Best Practices Summary
Do's
- ✅ Use specific activation events
- ✅ Validate all inputs
- ✅ Use HTTPS for network requests
- ✅ Implement proper error handling
- ✅ Follow least privilege principle
- ✅ Regularly update dependencies
- ✅ Use Content Security Policy for webviews
Don'ts
- ❌ Use
eval()or similar functions - ❌ Request unnecessary permissions
- ❌ Hardcode sensitive information
- ❌ Ignore error handling
- ❌ Use HTTP for API calls
- ❌ Trust user input without validation
Conclusion
Following these security practices ensures your VS Code extensions are secure, reliable, and trustworthy. Always prioritize security throughout the development lifecycle.
Remember that security is an ongoing process - stay updated with the latest security practices and VS Code extension guidelines.