with one click
secrets-management
// Secure token storage, VS Code SecretStorage API, credential management, environment variable migration
// Secure token storage, VS Code SecretStorage API, credential management, environment variable migration
[HINT] Download the complete skill directory including SKILL.md and all related files
| type | skill |
| lifecycle | stable |
| inheritance | inheritable |
| name | secrets-management |
| description | Secure token storage, VS Code SecretStorage API, credential management, environment variable migration |
| tier | standard |
| applyTo | **/*secrets*,**/*management* |
| currency | "2026-04-22T00:00:00.000Z" |
| lastReviewed | "2026-04-30T00:00:00.000Z" |
Expert in secure credential storage for VS Code extensions using SecretStorage API, token lifecycle management, and security best practices.
| Method | Purpose | Returns |
|---|---|---|
secrets.store(key, value) | Save encrypted credential | Promise |
secrets.get(key) | Retrieve credential | Promise<string | undefined> |
secrets.delete(key) | Remove credential | Promise |
secrets.onDidChange | Listen for changes | Event |
| Platform | Storage Backend | Encryption |
|---|---|---|
| Windows | Windows Credential Manager | DPAPI (Data Protection API) |
| macOS | Keychain | Keychain Services |
| Linux | Secret Service API (libsecret) | OS keyring (GNOME/KDE) |
interface TokenConfig {
key: string; // SecretStorage key (namespaced)
displayName: string; // User-facing name
description: string; // Purpose explanation
getUrl?: string; // Where to obtain token
placeholder?: string; // Example format
envVar?: string; // Env var for migration
}
| Component | Responsibility |
|---|---|
| SecretStorage | OS-level encrypted storage (provided by VS Code) |
| Token Cache | In-memory Map for synchronous access |
| Config Registry | Token metadata (names, URLs, migration sources) |
| Migration Service | One-time env var ā SecretStorage transfer |
| UI Service | Quick pick, input box, warning prompts |
| Practice | Implementation |
|---|---|
| Namespace keys | Use extension.namespace.tokenName format |
| Never log tokens | Use [REDACTED] in console output |
| Validate input | Check format before storage (regex patterns) |
| Non-destructive migration | Keep env vars as fallback |
| Clear on error | Don't cache failed retrievals |
| Use placeholders | Show format without real credentials |
| Password input | Set password: true on input boxes |
SecretStorage is secure but inaccessible to external tools (CLI, PowerShell scripts, CI/CD). Solution: bidirectional flow.
.env file ā alex.migrateEnvSecrets ā SecretStoragealex.exportSecretsToEnv ā .env file| Direction | Command | Use Case |
|---|---|---|
| Import | Migrate .env to SecretStorage | Secure existing plaintext tokens |
| Export | Export SecretStorage to .env | Enable external tool access |
| Phase | Action | Safety Measure |
|---|---|---|
| Detection | Check for env var AND empty SecretStorage | Only migrate if both conditions met |
| Copy | secretStorage.store(key, process.env.VAR) | Non-destructive (env var remains) |
| Cache | Update in-memory cache | Avoid redundant SecretStorage reads |
| Fallback | If SecretStorage fails, use env var | Backward compatibility maintained |
| Logging | Console log migration success | User visibility without exposing tokens |
1. Feature triggered without token
ā
2. Warning message with 3 options:
- "Configure API Key" ā Opens token manager
- "Get API Key" ā Opens service URL in browser
- "Continue Anyway" ā Proceeds (may fail)
ā
3. Token manager quick pick:
- List all tokens with status icons
- ā
Configured | ā Not Configured
ā
4. Individual token prompt:
- Input box with password masking
- Placeholder showing format
- Validation before storage
ā
5. Confirmation:
- Success message
- Return to feature workflow
const result = await vscode.window.showWarningMessage(
`${SERVICE} API Key not configured. Set your API key to use ${FEATURE}.`,
"Configure API Key",
"Get API Key",
"Continue Anyway"
);
if (result === "Configure API Key") {
vscode.commands.executeCommand("extension.manageSecrets");
return;
}
if (result === "Get API Key") {
vscode.env.openExternal(vscode.Uri.parse(GET_URL));
return;
}
// Continue Anyway falls through
const TOKEN_CONFIGS: Record<string, TokenConfig> = {
SERVICE_TOKEN: {
key: 'extension.secrets.serviceToken',
displayName: 'Service API Token',
description: 'API token for external service integration',
getUrl: 'https://service.example.com/account/tokens',
placeholder: 'svc_xxxxxxxxxxxxxxxxxxxx',
envVar: 'SERVICE_API_TOKEN',
},
// Add more tokens as needed
};
Many VS Code APIs are synchronous, but SecretStorage is async. Solution:
// Module-level cache
const tokenCache: Map<string, string | null> = new Map();
// Async init (on activation)
async function initSecretsManager(context: vscode.ExtensionContext) {
secretStorage = context.secrets;
// Pre-load all tokens into cache
for (const config of Object.values(TOKEN_CONFIGS)) {
const token = await secretStorage.get(config.key);
tokenCache.set(config.key, token || null);
}
}
// Sync getter (safe after init)
function getToken(tokenName: string): string | null {
return tokenCache.get(tokenName) ?? null;
}
| Pitfall | Solution |
|---|---|
Calling SecretStorage before activate() | Initialize in activate(), check null |
| Async/sync mismatch | Use cache pattern for sync access |
| Logging actual tokens | Use console.log(\Migrated ${name}`)` without value |
| Overwriting user tokens | Check storage before migration |
| Hard-coded API keys | Always use SecretStorage |
| No fallback for missing tokens | Warn + offer config, don't crash |
| Platform-specific code | VS Code SecretStorage abstracts OS differences |
the AI assistant can automatically detect secrets in .env files and offer secure migration:
Detection Pattern:
// Scan workspace for .env files (excludes .env.example, .env.template)
const envFiles = await vscode.workspace.findFiles('**/.env*', '**/node_modules/**');
// Parse for secret patterns
const secretKeywords = [
'API_KEY', 'API_TOKEN', 'SECRET', 'PASSWORD', 'PASS',
'TOKEN', 'AUTH', 'CREDENTIAL', 'PRIVATE_KEY',
'ACCESS_KEY', 'SECRET_KEY', 'CLIENT_SECRET'
];
// Match: KEY_NAME=value (handles quotes, spaces, comments)
const envPattern = /^\s*([A-Z_][A-Z0-9_]*)\s*=\s*([^#\n]+)/i;
Migration Workflow:
.env files in workspaceUser Commands:
Migration UI Flow:
š Found 3 potential secret(s) in .env files:
ā
2 recognized (can auto-migrate)
ā ļø 1 custom (requires manual setup)
[Review Secrets] [Auto-Migrate Recognized] [Cancel]
Code Migration Guide: After migration, users must update their code:
context.secrets APISecurity Benefits:
.env filesVS Code SecretStorage is inaccessible to PowerShell scripts, CLI tools, and CI/CD pipelines. The export command bridges this gap.
Why Export is Needed:
brain-qa.cjs) can't access SecretStorageExport Implementation:
async function exportSecretsToEnv(): Promise<void> {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) return;
const envPath = path.join(workspaceFolder.uri.fsPath, '.env');
const secrets: string[] = [];
// Collect cached secrets
for (const [key, value] of tokenCache.entries()) {
if (value) {
// Find env var name from config
const config = Object.values(TOKEN_CONFIGS)
.find(c => c.key === key);
if (config?.envVar) {
secrets.push(`${config.envVar}=${value}`);
}
}
}
if (secrets.length === 0) {
vscode.window.showWarningMessage('No secrets to export');
return;
}
// Read existing .env, replace the AI assistant section
let content = '';
if (fs.existsSync(envPath)) {
content = fs.readFileSync(envPath, 'utf-8');
// Remove existing the AI assistant section
content = content.replace(
/\n?# the AI assistant Secrets Export[\s\S]*?(?=\n#|$)/g, ''
).trim();
}
// Append new section
const section = `\n\n# the AI assistant Secrets Export (auto-generated)\n${secrets.join('\n')}`;
fs.writeFileSync(envPath, content + section, 'utf-8');
vscode.window.showInformationMessage(
`Exported ${secrets.length} secret(s) to .env`
);
}
PowerShell Script Usage:
# Source the .env file in PowerShell
if (Test-Path .env) {
Get-Content .env | ForEach-Object {
if ($_ -match '^([^#=]+)=(.*)$') {
[Environment]::SetEnvironmentVariable($matches[1].Trim(), $matches[2].Trim())
}
}
}
# Now $env:REPLICATE_API_TOKEN is available
Security Considerations:
.gitignoresecretsManager.ts serviceinitSecretsManager(context) - Set up storage + cachegetToken(name) - Synchronous retrieval with fallbacksetToken(name, value) - Store + update cachedeleteToken(name) - Remove + clear cachemigrateSecretsFromEnvironment() - One-time migrationinitSecretsManager() in activate()getToken() instead of env vars