Implement secure Obsidian plugin development practices. Covers credential
storage, input validation, XSS prevention, network security, URI handler
safety, and Electron security. Use when handling user data, storing API keys,
making network requests, or preparing for community plugin submission.
Trigger with phrases like "obsidian security", "secure obsidian plugin",
"obsidian data protection", "obsidian privacy", "obsidian api key storage".
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Implement secure Obsidian plugin development practices. Covers credential
storage, input validation, XSS prevention, network security, URI handler
safety, and Electron security. Use when handling user data, storing API keys,
making network requests, or preparing for community plugin submission.
Trigger with phrases like "obsidian security", "secure obsidian plugin",
"obsidian data protection", "obsidian privacy", "obsidian api key storage".
Designed for Claude Code, also compatible with Codex and OpenClaw
Obsidian Security Basics
Overview
Security practices for Obsidian plugin development. Plugins run with full vault filesystem access and can make arbitrary network requests inside Electron. Responsible development requires protecting credentials, sanitizing external data, validating URI handlers, minimizing permissions, and following Obsidian's plugin guidelines to avoid community submission rejection.
Prerequisites
Obsidian plugin development environment
Understanding that .obsidian/plugins/<id>/data.json is synced by cloud services
Plugin settings (data.json) live inside the vault and are synced by iCloud, Dropbox, Obsidian Sync, and Git. API keys stored here are effectively public.
import { requestUrl, RequestUrlParam } from'obsidian';
// Always use Obsidian's requestUrl — it respects proxy settings and CORSasyncfunctionsecureFetch(url: string, options?: Partial<RequestUrlParam>): Promise<any> {
// Enforce HTTPSif (!url.startsWith('https://')) {
thrownewError('Only HTTPS requests are allowed');
}
// Allowlist domains (prevents SSRF if URL comes from user input)constALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];
const urlObj = newURL(url);
if (!ALLOWED_DOMAINS.includes(urlObj.hostname)) {
thrownewError(`Domain not allowed: ${urlObj.hostname}`);
}
const response = awaitrequestUrl({
url,
method: 'GET',
headers: {
'User-Agent': 'ObsidianPlugin/1.0',
...options?.headers,
},
...options,
});
if (response.status < 200 || response.status >= 300) {
thrownewError(`HTTP ${response.status}: ${url}`);
}
return response.json;
}
// Never log or display full API responses — they may contain PIIfunctionredactForLogging(data: any): any {
const redacted = { ...data };
const sensitiveKeys = ['apiKey', 'token', 'password', 'secret', 'authorization'];
for (const key ofObject.keys(redacted)) {
if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
redacted[key] = '[REDACTED]';
}
}
return redacted;
}
Step 5: Permission Minimization
// manifest.json — only set isDesktopOnly if you actually need Electron APIs
{
"isDesktopOnly": false// Obsidian has no granular permission model in manifest.json.// The review team evaluates your code for:// - Network requests: must be essential to plugin function// - Filesystem access outside vault: strongly discouraged// - No telemetry/analytics without explicit user consent// - No remote code loading (eval, new Function, loading JS from URL)
}
// At runtime: guard platform-specific codeimport { Platform, FileSystemAdapter } from'obsidian';
functiongetVaultBasePath(): string | null {
if (this.app.vault.adapterinstanceofFileSystemAdapter) {
returnthis.app.vault.adapter.getBasePath();
// IMPORTANT: never access files outside this basePath
}
returnnull; // Mobile — no filesystem access outside vault
}
// Guard Electron APIsif (Platform.isDesktopApp) {
// Safe to use: require('electron'), child_process, etc.
} else {
// Mobile: these APIs don't exist — provide fallback or disable feature
}
Step 6: Plugin Review Rejection Checklist
Obsidian's plugin review team will reject plugins for these violations:
// REJECTED: eval() or dynamic code executioneval(userInput); // NevernewFunction('return ' + code)(); // Neverdocument.createElement('script'); // Never for external scripts// REJECTED: remote code loadingconst script = document.createElement('script');
script.src = 'https://cdn.example.com/lib.js'; // Load at build time instead// REJECTED: console.log in productionconsole.log('user data:', settings); // Remove before submission// REJECTED: unencrypted credential storagethis.saveData({ apiKey: 'sk-abc123' }); // Use SecureStorage (Step 1)// REJECTED: undisclosed network requestsfetch('https://analytics.example.com/track', { body: ... }); // No hidden telemetry// APPROVED alternatives:// - Bundle dependencies with esbuild (no runtime loading)// - Use a debug flag for console statements// - Document all network requests in README// - Get explicit consent before any data leaves the device
Output
SecureStorage class using Electron's safeStorage for encrypted credential storage
HTML and markdown sanitization for all external content
URI handler with action whitelist and path validation
Secure network request wrapper with HTTPS enforcement and domain allowlist
Platform-specific guards for desktop/mobile code paths
Plugin review rejection checklist with approved alternatives
Error Handling
Issue
Cause
Solution
API key synced to cloud
Stored in data.json
Use SecureStorage with Electron safeStorage
XSS in note preview
Unsanitized external HTML
Use createEl with text property, or sanitizeHtml
Directory traversal via URI
Unvalidated path parameter
Strip .., normalize, validate within vault
SSRF from user-provided URL
No domain allowlist
Validate against ALLOWED_DOMAINS before requestUrl
Plugin rejected on review
eval, console.log, or telemetry
Follow rejection checklist (Step 6)
safeStorage unavailable
Older Electron version or mobile
Fall back to per-session prompt
Examples
Content Security for Custom Views
// When rendering external content in an ItemViewasynconOpen() {
const externalData = awaitthis.fetchData();
const container = this.containerEl.children[1];
container.empty();
// Safe: text content is escaped by createEl
container.createEl('h3', { text: externalData.title });
container.createEl('p', { text: externalData.summary });
// If you must render HTML, sanitize firstconst safeHtml = sanitizeHtml(externalData.htmlContent);
const htmlContainer = container.createEl('div');
htmlContainer.innerHTML = safeHtml;
}