Tutorial
beginner

VS Code Extension Development Guide

Complete guide to developing secure VS Code extensions

Development Team
2 hours
#vscode#extension#development#tutorial

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.