Obsidian Security BasicsSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-09Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: obsidian-security-basics description: '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". ' allowed-tools: Read, Write, Edit, Grep version: 1.13.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - obsidian - security - authentication - privacy - electron compatibility: Designed for Claude Code --- # 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 - Awareness of [Obsidian Plugin Guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines) ## Instructions ### Step 1: Credential Storage — Never in data.json 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. ```typescript // BAD: API key stored in plugin settings (synced to cloud, committed to Git) interface BadSettings { apiKey: string; // This ends up in .obsidian/plugins/my-plugin/data.json } // GOOD: Use Electron's safeStorage for desktop (encrypted at OS level) import { Platform } from 'obsidian'; export class SecureStorage { private plugin: Plugin; constructor(plugin: Plugin) { this.plugin = plugin; } async storeSecret(key: string, value: string): Promise<void> { if (Platform.isDesktopApp) { // Electron's safeStorage uses OS keychain (Keychain on macOS, DPAPI on Windows) const { safeStorage } = require('electron').remote || require('@electron/remote'); if (safeStorage.isEncryptionAvailable()) { const encrypted = safeStorage.encryptString(value); const data = await this.plugin.loadData() ?? {}; data[`_encrypted_${key}`] = encrypted.toString('base64'); await this.plugin.saveData(data); return; } } // Fallback for mobile or when encryption unavailable: prompt each session // Store only in memory — never persisted this.memoryStore.set(key, value); } async getSecret(key: string): Promise<string | null> { if (Platform.isDesktopApp) { const { safeStorage } = require('electron').remote || require('@electron/remote'); const data = await this.plugin.loadData(); const encrypted = data?.[`_encrypted_${key}`]; if (encrypted && safeStorage.isEncryptionAvailable()) { return safeStorage.decryptString(Buffer.from(encrypted, 'base64')); } } return this.memoryStore.get(key) ?? null; } private memoryStore = new Map<string, string>(); } // Alternative: prompt user each session (simplest, most secure) async onload() { if (!this.apiKey) { this.apiKey = await this.promptForApiKey(); } } ``` ### Step 2: Input Validation and XSS Prevention Data from HTTP responses, clipboard, or URI handlers must be sanitized before rendering. ```typescript // Sanitize HTML content before inserting into Obsidian views function sanitizeHtml(input: string): string { // Strip dangerous elements input = input.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ''); input = input.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/gi, ''); input = input.replace(/<object[^>]*>[\s\S]*?<\/object>/gi, ''); input = input.replace(/<embed[^>]*>/gi, ''); // Strip event handlers input = input.replace(/\bon\w+\s*=\s*"[^"]*"/gi, ''); input = input.replace(/\bon\w+\s*=\s*'[^']*'/gi, ''); // Strip javascript: URIs input = input.replace(/href\s*=\s*"javascript:[^"]*"/gi, 'href="#"'); return input; } // For plain text in DOM elements — escape instead of strip function escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Safe DOM creation (preferred in Obsidian) // Use createEl with text content — Obsidian escapes automatically container.createEl('p', { text: userInput }); // Safe — text is escaped container.createEl('p').innerHTML = userInput; // DANGEROUS — raw HTML injection // For markdown content from external sources function sanitizeMarkdown(md: string): string { // Remove HTML blocks that could contain scripts md = md.replace(/<script[\s\S]*?<\/script>/gi, ''); // Remove image onerror handlers md = md.replace(/onerror\s*=\s*["'][^"']*["']/gi, ''); // Limit length to prevent DoS if (md.length > 500_000) md = md.substring(0, 500_000); return md; } ``` ### Step 3: Secure URI Handler Registration Obsidian's `registerObsidianProtocolHandler` lets external apps trigger plugin actions via `obsidian://` URIs. Validate all parameters. ```typescript this.registerObsidianProtocolHandler('myplugin', async (params) => { // Whitelist allowed actions const ALLOWED_ACTIONS = ['open', 'create', 'search'] as const; type Action = typeof ALLOWED_ACTIONS[number]; const action = params.action as string; if (!ALLOWED_ACTIONS.includes(action as Action)) { new Notice(`Invalid action: ${action}`); return; } // Sanitize file paths — prevent directory traversal const path = params.path?.replace(/\.\./g, '').replace(/^\//, ''); if (!path) { new Notice('Missing path parameter'); return; } // Validate path is within vault const normalized = normalizePath(
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__obsidian-security-basics.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-09 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Obsidian Security Basics skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Obsidian Security Basics safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Obsidian Security Basics access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Obsidian Security Basics work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-09. The repository is watched, and a new audit runs when it changes — this is the first audit.