Skip to content

Security: plugin-observer/plugin

Security

SECURITY.md

Security

Security Policy

Report vulnerabilities to: security@plugin.observer

We will acknowledge reports within 48 hours and triage within 7 days. Good-faith security researchers will not face legal action.

Scope: vulnerabilities in the plugin itself, its data handling, or its communication with the plugin.observer API.

When reporting, include: a description of the issue, steps to reproduce, and the affected plugin version.

Supported versions: current release only.

Data Transmitted

The plugin communicates with a single API server (default: https://plugin.observer, user-configurable in settings). Two endpoints are used:

GET /api/scan/challenge

Detail Value
Custom headers None
Request body None
Response { nonce, difficulty, expiry, signature }
Purpose Obtain a challenge token for proof-of-work and request signing

POST /api/scan

Request body:

Field Type Example Description
plugins[].id string dataview Community plugin ID
plugins[].version string 0.5.67 Installed version

No other fields are sent. The plugin never transmits vault contents, filenames, note text, user configuration, or any other personal data.

Request headers:

Header Description
Content-Type application/json
x-challenge Challenge nonce — binds request to a specific challenge
x-challenge-expiry Challenge expiry timestamp
x-challenge-sig Server-issued signature proving challenge authenticity
x-challenge-difficulty Proof-of-work difficulty level
x-pow-solution Client's proof-of-work solution (SHA-256 with leading zero bits)
x-request-sig HMAC-SHA256 signature over the request body, using a key derived from the challenge nonce and a build-time client secret (two-step HMAC: derive key from nonce, then sign body with derived key)

Requests are batched at 200 plugins maximum. If more are installed, multiple requests are sent, each with its own challenge.

Network Activity

  • Polling: checks the installed plugin list every 10 seconds via a local in-memory comparison — no network request is made unless the list has changed or the previous scan failed
  • Scans on change: contacts the API only when a plugin is installed, updated, or removed
  • Debounce: 3-second delay after detecting a change before scanning
  • Pre-update scan: single-plugin scan when the user installs or updates a plugin (if pre-update warnings are enabled in settings)
  • First run: immediate scan on plugin activation
  • Manual trigger: "Scan now" command in the command palette
  • Transport: all requests use Obsidian's requestUrl API (Electron's net.request; respects system proxy settings)
  • Authentication: challenge-response, proof-of-work, and HMAC-SHA256 signing — no user credentials or API keys are transmitted
  • Failure behavior: returns empty results and shows an error icon in the status bar; retries on the next poll cycle (10 seconds) if the plugin list is unchanged; no aggressive retry loop or request queuing

Data Collected

From Obsidian:

app.plugins.manifests — a record of installed community plugin IDs, versions, and display names. This is the only data read from the Obsidian runtime beyond the plugin's own settings.

Not collected:

  • Vault contents, filenames, or directory structure
  • Note text or metadata
  • Other plugin configurations or settings
  • Obsidian account information
  • System information (OS, hardware, etc.)

Data Stored

All local storage uses Obsidian's Plugin.loadData() / Plugin.saveData() API. Data is written to .obsidian/plugins/observer/data.json.

Settings:

Field Type Description
apiUrl string API server URL
alertThreshold number Score threshold (0–100) for alerts
preUpdateWarnings boolean Show confirmation before installing flagged plugins
notificationStyle "notice" | "silent" How alerts are displayed
ignoredPlugins string[] Plugin IDs excluded from alerts

Scan cache:

Field Type Description
results ScanResult[] Scan results: scores, findings, alerts
scannedAt number Timestamp of last scan (ms)

No data is encrypted locally — all stored data is non-sensitive (security scores and user preferences).

Permissions and API Usage

Obsidian API Purpose
Plugin Lifecycle management (load, unload, save data)
requestUrl HTTP requests to the scan API
Notice Toast notifications for scan results
Modal Alert detail modal and pre-update confirmation dialog
PluginSettingTab / Setting Settings UI
setIcon Render shield icons in status bar and modals

Undocumented API usage:

The plugin accesses app.plugins.manifests (read-only) to list installed plugins, and temporarily replaces app.plugins.installPlugin to show a confirmation dialog before installing plugins with security alerts. Neither of these are part of Obsidian's public API.

  • The installPlugin replacement passes all arguments through to the original function unchanged — it never modifies what gets installed
  • The original function is restored when the plugin unloads
  • If the replacement encounters any error, it falls through to the original function (fail-safe)
  • If pre-update warnings are disabled in settings, the original function is called immediately with no interception

Obsidian does not provide an official hook for pre-install interception. This is the only way to warn users before a flagged plugin is installed.

Code Integrity

  • No eval(), new Function(), or dynamic code execution
  • No innerHTML or raw HTML injection — all DOM created via Obsidian's safe createEl / createDiv API
  • No direct filesystem access — only Obsidian's loadData() / saveData()
  • No WebSocket connections or background workers
  • Cryptographic operations use the Web Crypto API (crypto.subtle) exclusively for proof-of-work (SHA-256) and request signing (HMAC-SHA256) — not for encrypting user data

Third-Party Code

None. The plugin has no runtime dependencies and no vendored code.

Build-Time Secrets

CLIENT_SIGN_KEY is the only secret. It is injected at build time via esbuild's define mechanism and is never present in source control.

  • Dev builds use a hardcoded default that matches the local dev server
  • Production builds receive the key via a CI environment variable
  • Purpose: HMAC request signing only — authenticates requests to the scan API, not user data

Users can verify the built main.js contains no other embedded secrets by searching for the known define patterns: __CLIENT_SIGN_KEY__ and __DEV__.

There aren’t any published security advisories