Atlas / Skills / jeremylongshore / Obsidian Security Basics

Obsidian Security BasicsSAFE

skills/jeremylongshore/obsidian-security-basics

Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.

Verdict
SAFE
Grade
B
Trust score
89 /100
Version
1.13.0
Hosts
1 documented
License
MIT
Stars
2,824
01

Overview

Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.

Read from source at commit 4f83675ca38aOBSERVED · 2026-10-09
02

Host compatibility

What the documentation claims. We have not run a compatibility test.

HostStatusNotes
claude-codementioned
03

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(
04

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.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codePASS
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (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.

Audited 2026-10-09 · audit v0.4.1 · source sha 4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__obsidian-security-basics.json · Report an issue / request a re-scan
05

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-094f83675ca38aSAFEB89first audit
06

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.

Advertisement