[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M754HWBEA3JM7SHF8EX5TQRF":3,"skill-same:01M754HWBEA3JM7SHF8EX5TQRF":49},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":29,"commitSha":30,"skillDirectory":5,"forksCount":14,"repositoryHtmlUrl":31,"repositoryHomepageUrl":6,"ownerHtmlUrl":32,"ownerBlogUrl":33,"ownerDescription":6,"updatedAt":34,"install":35,"resources":48},"01M754HWBEA3JM7SHF8EX5TQRF","obsidian-security-basics",null,"Implement secure Obsidian plugin development practices.\nUse when handling user data, implementing authentication,\nor ensuring plugin security best practices.\nTrigger with phrases like \"obsidian security\", \"secure obsidian plugin\",\n\"obsidian data protection\", \"obsidian privacy\".","SamuelChien\u002Fmega-skills-collection","SamuelChien","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F4652114?v=4","01M74KK34WVHA0P5ZYSJRDDFMG","Samuel Chien","01M74KK35FQN1N9VZ9EPTH3P2S",0,"MIT","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fobsidian-security-basics\u002FSKILL.md",[],[],"2026-11-12 13:55:43",false,"---\nname: obsidian-security-basics\ndescription: |\n  Implement secure Obsidian plugin development practices.\n  Use when handling user data, implementing authentication,\n  or ensuring plugin security best practices.\n  Trigger with phrases like \"obsidian security\", \"secure obsidian plugin\",\n  \"obsidian data protection\", \"obsidian privacy\".\nallowed-tools: Read, Write, Edit\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\ncompatible-with: claude-code, codex, openclaw\n---\n# Obsidian Security Basics\n\n## Overview\nSecurity practices for Obsidian plugin development. Plugins run with full access to the vault filesystem and can make network requests -- responsible plugin development requires protecting user data, validating inputs, and managing external connections securely.\n\n## Prerequisites\n- Obsidian plugin development environment\n- Understanding of Electron security model\n- Awareness of Obsidian's plugin guidelines\n\n## Instructions\n\n### Step 1: Never Store Credentials in Vault Files\n\nPlugin settings are stored as JSON in the vault's `.obsidian\u002Fplugins\u002F` directory. This is synced by many cloud services.\n\n```typescript\n\u002F\u002F BAD: API key stored in plugin settings (synced to cloud)\ninterface BadSettings {\n  apiKey: string;  \u002F\u002F Synced to iCloud, Google Drive, etc.\n}\n\n\u002F\u002F GOOD: use system keychain or prompt each session\nimport { Platform } from 'obsidian';\n\nasync function storeSecurely(key: string, value: string): Promise\u003Cvoid> {\n  if (Platform.isDesktopApp) {\n    \u002F\u002F Use Electron's safeStorage (encrypted at OS level)\n    const { safeStorage } = require('electron').remote;\n    const encrypted = safeStorage.encryptString(value);\n    await plugin.saveData({ encryptedKey: encrypted.toString('base64') });\n  }\n}\n```\n\n### Step 2: Validate All External Data\n\nData from HTTP requests, clipboard, or URI handlers must be validated.\n\n```typescript\nfunction sanitizeExternalContent(content: string): string {\n  \u002F\u002F Strip HTML to prevent XSS in preview\n  content = content.replace(\u002F\u003Cscript[^>]*>.*?\u003C\\\u002Fscript>\u002Fgi, '');\n  content = content.replace(\u002F\u003Ciframe[^>]*>.*?\u003C\\\u002Fiframe>\u002Fgi, '');\n  content = content.replace(\u002Fon\\w+=\"[^\"]*\"\u002Fgi, '');\n  \u002F\u002F Limit length\n  if (content.length > 100000) {  # 100000 = configured value\n    content = content.substring(0, 100000);\n  }\n  return content;\n}\n\n\u002F\u002F Validate URI handler parameters\nthis.registerObsidianProtocolHandler('myplugin', async (params) => {\n  const action = params.action;\n  if (!['open', 'create', 'search'].includes(action)) {\n    new Notice('Invalid action');\n    return;\n  }\n  \u002F\u002F Sanitize file paths to prevent directory traversal\n  const path = params.path?.replace(\u002F\\.\\.\u002Fg, '');\n  if (path) await handleAction(action, path);\n});\n```\n\n### Step 3: Secure Network Requests\n\nAlways use HTTPS and validate responses.\n\n```typescript\nasync function secureFetch(url: string): Promise\u003Cany> {\n  \u002F\u002F Enforce HTTPS\n  if (!url.startsWith('https:\u002F\u002F')) {\n    throw new Error('Only HTTPS requests allowed');\n  }\n  const response = await requestUrl({\n    url,\n    method: 'GET',\n    headers: { 'User-Agent': 'ObsidianPlugin\u002F1.0' }\n  });\n  if (response.status !== 200) {  # HTTP 200 OK\n    throw new Error(`HTTP ${response.status}`);\n  }\n  return response.json;\n}\n```\n\n### Step 4: Permission Minimization\n\nOnly request the permissions your plugin actually needs.\n\n```typescript\n\u002F\u002F In manifest.json\n{\n  \"isDesktopOnly\": false,\n  \u002F\u002F Don't access filesystem beyond vault unless necessary\n  \u002F\u002F Don't make network requests unless core to functionality\n  \u002F\u002F Document WHY you need each permission in README\n}\n\n\u002F\u002F At runtime: check before accessing sensitive APIs\nif (this.app.vault.adapter instanceof FileSystemAdapter) {\n  \u002F\u002F Desktop-only file operations\n  const basePath = this.app.vault.adapter.getBasePath();\n  \u002F\u002F Never access files outside basePath\n}\n```\n\n## Error Handling\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| API key synced to cloud | Stored in plugin settings | Use OS keychain or encrypted storage |\n| XSS in note preview | Unsanitized external content | Strip HTML\u002FJS from external data |\n| Directory traversal | Unvalidated file paths | Remove `..` from paths, validate against vault |\n| HTTP data leak | Using HTTP instead of HTTPS | Enforce HTTPS for all requests |\n\n## Examples\n\n### Content Security Policy\n```typescript\n\u002F\u002F Before injecting HTML into Obsidian views\nfunction escapeHtml(text: string): string {\n  return text.replace(\u002F&\u002Fg, '&amp;').replace(\u002F\u003C\u002Fg, '&lt;')\n    .replace(\u002F>\u002Fg, '&gt;').replace(\u002F\"\u002Fg, '&quot;');\n}\n```\n\n## Resources\n- [Obsidian Plugin Guidelines](https:\u002F\u002Fdocs.obsidian.md\u002FPlugins\u002FReleasing\u002FPlugin+guidelines)\n- [Electron Security](https:\u002F\u002Fwww.electronjs.org\u002Fdocs\u002Flatest\u002Ftutorial\u002Fsecurity)\n\n## Output\n\n- Configuration files or code changes applied to the project\n- Validation report confirming correct implementation\n- Summary of changes made and their rationale","# Obsidian Security Basics\n\n## Overview\nSecurity practices for Obsidian plugin development. Plugins run with full access to the vault filesystem and can make network requests -- responsible plugin development requires protecting user data, validating inputs, and managing external connections securely.\n\n## Prerequisites\n- Obsidian plugin development environment\n- Understanding of Electron security model\n- Awareness of Obsidian's plugin guidelines\n\n## Instructions\n\n### Step 1: Never Store Credentials in Vault Files\n\nPlugin settings are stored as JSON in the vault's `.obsidian\u002Fplugins\u002F` directory. This is synced by many cloud services.\n\n```typescript\n\u002F\u002F BAD: API key stored in plugin settings (synced to cloud)\ninterface BadSettings {\n  apiKey: string;  \u002F\u002F Synced to iCloud, Google Drive, etc.\n}\n\n\u002F\u002F GOOD: use system keychain or prompt each session\nimport { Platform } from 'obsidian';\n\nasync function storeSecurely(key: string, value: string): Promise\u003Cvoid> {\n  if (Platform.isDesktopApp) {\n    \u002F\u002F Use Electron's safeStorage (encrypted at OS level)\n    const { safeStorage } = require('electron').remote;\n    const encrypted = safeStorage.encryptString(value);\n    await plugin.saveData({ encryptedKey: encrypted.toString('base64') });\n  }\n}\n```\n\n### Step 2: Validate All External Data\n\nData from HTTP requests, clipboard, or URI handlers must be validated.\n\n```typescript\nfunction sanitizeExternalContent(content: string): string {\n  \u002F\u002F Strip HTML to prevent XSS in preview\n  content = content.replace(\u002F\u003Cscript[^>]*>.*?\u003C\\\u002Fscript>\u002Fgi, '');\n  content = content.replace(\u002F\u003Ciframe[^>]*>.*?\u003C\\\u002Fiframe>\u002Fgi, '');\n  content = content.replace(\u002Fon\\w+=\"[^\"]*\"\u002Fgi, '');\n  \u002F\u002F Limit length\n  if (content.length > 100000) {  # 100000 = configured value\n    content = content.substring(0, 100000);\n  }\n  return content;\n}\n\n\u002F\u002F Validate URI handler parameters\nthis.registerObsidianProtocolHandler('myplugin', async (params) => {\n  const action = params.action;\n  if (!['open', 'create', 'search'].includes(action)) {\n    new Notice('Invalid action');\n    return;\n  }\n  \u002F\u002F Sanitize file paths to prevent directory traversal\n  const path = params.path?.replace(\u002F\\.\\.\u002Fg, '');\n  if (path) await handleAction(action, path);\n});\n```\n\n### Step 3: Secure Network Requests\n\nAlways use HTTPS and validate responses.\n\n```typescript\nasync function secureFetch(url: string): Promise\u003Cany> {\n  \u002F\u002F Enforce HTTPS\n  if (!url.startsWith('https:\u002F\u002F')) {\n    throw new Error('Only HTTPS requests allowed');\n  }\n  const response = await requestUrl({\n    url,\n    method: 'GET',\n    headers: { 'User-Agent': 'ObsidianPlugin\u002F1.0' }\n  });\n  if (response.status !== 200) {  # HTTP 200 OK\n    throw new Error(`HTTP ${response.status}`);\n  }\n  return response.json;\n}\n```\n\n### Step 4: Permission Minimization\n\nOnly request the permissions your plugin actually needs.\n\n```typescript\n\u002F\u002F In manifest.json\n{\n  \"isDesktopOnly\": false,\n  \u002F\u002F Don't access filesystem beyond vault unless necessary\n  \u002F\u002F Don't make network requests unless core to functionality\n  \u002F\u002F Document WHY you need each permission in README\n}\n\n\u002F\u002F At runtime: check before accessing sensitive APIs\nif (this.app.vault.adapter instanceof FileSystemAdapter) {\n  \u002F\u002F Desktop-only file operations\n  const basePath = this.app.vault.adapter.getBasePath();\n  \u002F\u002F Never access files outside basePath\n}\n```\n\n## Error Handling\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| API key synced to cloud | Stored in plugin settings | Use OS keychain or encrypted storage |\n| XSS in note preview | Unsanitized external content | Strip HTML\u002FJS from external data |\n| Directory traversal | Unvalidated file paths | Remove `..` from paths, validate against vault |\n| HTTP data leak | Using HTTP instead of HTTPS | Enforce HTTPS for all requests |\n\n## Examples\n\n### Content Security Policy\n```typescript\n\u002F\u002F Before injecting HTML into Obsidian views\nfunction escapeHtml(text: string): string {\n  return text.replace(\u002F&\u002Fg, '&amp;').replace(\u002F\u003C\u002Fg, '&lt;')\n    .replace(\u002F>\u002Fg, '&gt;').replace(\u002F\"\u002Fg, '&quot;');\n}\n```\n\n## Resources\n- [Obsidian Plugin Guidelines](https:\u002F\u002Fdocs.obsidian.md\u002FPlugins\u002FReleasing\u002FPlugin+guidelines)\n- [Electron Security](https:\u002F\u002Fwww.electronjs.org\u002Fdocs\u002Flatest\u002Ftutorial\u002Fsecurity)\n\n## Output\n\n- Configuration files or code changes applied to the project\n- Validation report confirming correct implementation\n- Summary of changes made and their rationale",{"name":5,"author":24,"license":15,"version":25,"description":26,"allowed-tools":27,"compatible-with":28},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Implement secure Obsidian plugin development practices.\nUse when handling user data, implementing authentication,\nor ensuring plugin security best practices.\nTrigger with phrases like \"obsidian security\", \"secure obsidian plugin\",\n\"obsidian data protection\", \"obsidian privacy\".\n","Read, Write, Edit","claude-code, codex, openclaw","main","c2b1fdccc779e99ac814e55fb69ca8d71215dd0f","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection","https:\u002F\u002Fgithub.com\u002FSamuelChien","http:\u002F\u002Fsamuelchien.com\u002F","2026-05-26 08:27:14",{"source":8,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add SamuelChien\u002Fmega-skills-collection --skill obsidian-security-basics",{"tool":41,"command":42},"bunx","bunx skills add SamuelChien\u002Fmega-skills-collection --skill obsidian-security-basics",{"tool":44,"command":45},"pnpm","pnpm dlx skills add SamuelChien\u002Fmega-skills-collection --skill obsidian-security-basics","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fobsidian-security-basics\u002FSKILL.md",true,[],[50,57,64,71,78,86],{"skillNo":51,"name":52,"nameZh":6,"description":53,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":54,"categories":55,"keywords":56,"publishedAt":19},"01M76R4WZQZAXGGXMQCA0FZ716","zustand-store-ts","Create Zustand stores with TypeScript, subscribeWithSelector middleware, and proper state\u002Faction separation. Use when building React state management, creating global stores, or implementing reactive state patterns with Zustand.","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fzustand-store-ts\u002FSKILL.md",[],[],{"skillNo":58,"name":59,"nameZh":6,"description":60,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":61,"categories":62,"keywords":63,"publishedAt":19},"01M76R4T9KENDH7FENGZQHBW4G","zustand-store-creator","Zustand Store Creator - Auto-activating skill for Frontend Development.\nTriggers on: zustand store creator, zustand store creator\nPart of the Frontend Development skill category.","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fzustand-store-creator\u002FSKILL.md",[],[],{"skillNo":65,"name":66,"nameZh":6,"description":67,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":70,"publishedAt":19},"01M76R3YWN84B7SANJ52X799ZE","zero-trust-config-helper","Zero Trust Config Helper - Auto-activating skill for Security Advanced.\nTriggers on: zero trust config helper, zero trust config helper\nPart of the Security Advanced skill category.","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fzero-trust-config-helper\u002FSKILL.md",[],[],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":19},"01M76R3HNSV2AEYSKHM915GJ58","zapier-integration-helper","Zapier Integration Helper - Auto-activating skill for Business Automation.\nTriggers on: zapier integration helper, zapier integration helper\nPart of the Business Automation skill category.","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fzapier-integration-helper\u002FSKILL.md",[],[],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":82,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M76R3DJSKAT4FA7DV4BF3GCE","zai-cli","Execute z.AI CLI providing vision, search, reader, and GitHub exploration via CLI and MCP.\nUse when user needs image\u002Fvideo analysis, OCR, UI-to-code conversion, error diagnosis,\nreal-time web search, web page to markdown extraction, or GitHub code exploration.\nTrigger with phrases like \"analyze this image\", \"search the web for\", \"read this page\",\n\"explore this repo\", or \"use zai\". Requires Z_AI_API_KEY.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fzai-cli\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":19},"01M76R2KAM699XE9AJVAWE4H2E","yt-research","Research competitor YouTube channels, niches, and trending topics for your content strategy.\nUse this skill whenever the user says \"research channels\", \"analyze competitors\", \"find trending topics\",\n\"niche analysis\", \"competitive research\", \"what are other creators doing\", \"scrape YouTube channels\",\nor wants to understand the competitive landscape for a specific tool or topic area. Use when working with yt research. Trigger with 'yt', 'research'.","https:\u002F\u002Fgithub.com\u002FSamuelChien\u002Fmega-skills-collection\u002Fblob\u002Fmain\u002Fyt-research\u002FSKILL.md",[],[]]