Obsidian Enterprise RBAC Overview Vault-level access control patterns for Obsidian in team environments. Covers folder-based permissions via files, read-only enforcement for shared vaults, plugin allowlisting, and configuration lockdown through restricted mode. Prerequisites - Obsidian desktop app with a shared/synced vault - Understanding of Obsidian's configuration directory - A sync mechanism in place (Git, Obsidian Sync, or shared filesystem) - Node.js 18+ for scripted permission enforcement Instructions Step 1: Define a Permission Model Create at the vault root. This JSON file maps roles…

);\n return regex.test(path);\n });\n }\n}\n```\n\n### Step 3: Enforce Read-Only Mode on Shared Vaults\n\nFor vaults where most users should only read, set restricted mode in `.obsidian/app.json`:\n\n```json\n{\n \"strictLineBreaks\": false,\n \"readableLineLength\": true,\n \"vimMode\": false,\n \"livePreview\": true\n}\n```\n\nThen in your RBAC plugin, enforce read-only for non-editor roles by overriding the editor:\n\n```typescript\n// In onload(), after permission check:\nif (!this.canWrite('/')) {\n // Disable editing commands\n this.registerEvent(\n this.app.workspace.on('editor-change', (editor) => {\n // Revert changes for read-only users\n editor.undo();\n new Notice('This vault is read-only for your role.');\n })\n );\n}\n```\n\n### Step 4: Plugin Allowlisting\n\nLock down which community plugins can be enabled. Edit `.obsidian/community-plugins.json` to contain only approved plugins:\n\n```json\n[\"obsidian-git\", \"dataview\", \"templater-obsidian\", \"your-rbac-plugin\"]\n```\n\nThen protect this file from modification by non-admins. In your RBAC plugin, watch for changes:\n\n```typescript\nthis.registerEvent(\n this.app.vault.on('modify', async (file) => {\n if (file.path === '.obsidian/community-plugins.json') {\n const role = this.config?.users[this.currentUser];\n if (role !== 'admin') {\n // Restore the approved list\n const approved = await this.loadData();\n await this.app.vault.modify(\n file as TFile,\n JSON.stringify(approved.allowedPlugins)\n );\n new Notice('Only admins can modify the plugin allowlist.');\n }\n }\n })\n);\n```\n\n### Step 5: Configuration Lockdown via Restricted Mode\n\nObsidian's restricted mode disables all community plugins. For enterprise deployments, combine this with a config lockdown:\n\n```typescript\n// Store a hash of critical config files at deploy time\nconst LOCKED_CONFIGS = [\n '.obsidian/app.json',\n '.obsidian/appearance.json',\n '.obsidian/hotkeys.json',\n '.obsidian/community-plugins.json',\n];\n\nasync lockdownConfigs() {\n const hashes: Record\u003cstring, string> = {};\n for (const path of LOCKED_CONFIGS) {\n const file = this.app.vault.getAbstractFileByPath(path);\n if (file instanceof TFile) {\n const content = await this.app.vault.read(file);\n hashes[path] = await this.hash(content);\n }\n }\n await this.saveData({ ...await this.loadData(), configHashes: hashes });\n}\n\nasync verifyConfigs(): Promise\u003cstring[]> {\n const data = await this.loadData();\n const violations: string[] = [];\n for (const [path, expectedHash] of Object.entries(data.configHashes ?? {})) {\n const file = this.app.vault.getAbstractFileByPath(path);\n if (file instanceof TFile) {\n const content = await this.app.vault.read(file);\n const actual = await this.hash(content);\n if (actual !== expectedHash) {\n violations.push(path);\n }\n }\n }\n return violations;\n}\n\nprivate async hash(content: string): Promise\u003cstring> {\n const encoder = new TextEncoder();\n const data = encoder.encode(content);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');\n}\n```\n\nRun `verifyConfigs()` on plugin load and periodically. Alert admins if violations are detected.\n\n## Output\n\n- `.obsidian-permissions` file defining roles, folder access, and user mappings\n- RBAC plugin that intercepts create/modify/delete operations\n- Read-only enforcement for non-editor roles\n- Plugin allowlist protection in `community-plugins.json`\n- Configuration lockdown with hash verification for critical `.obsidian/` files\n\n## Error Handling\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| Permission denied on all files | User email not set in plugin settings | Open RBAC plugin settings, enter your email |\n| Allowlist keeps resetting | Non-admin edited `community-plugins.json` | Only admins can modify; check audit log |\n| Config hash mismatch on every load | Config changed legitimately | Admin runs `lockdownConfigs()` to update hashes |\n| Plugin not intercepting writes | Event handler registration failed | Check console for plugin load errors |\n| Sync conflicts on `.obsidian-permissions` | Multiple admins editing simultaneously | Use Git with merge strategy or Obsidian Sync |\n\n## Examples\n\n**Team vault with three roles**: Deploy the `.obsidian-permissions` file above. Set each user's email in the RBAC plugin settings. Editors can modify `projects/` and `shared/` folders; viewers can only read `shared/` and `published/`.\n\n**Locked-down training vault**: Set all users to `viewer` role except instructors (`editor`). Lock config files with `lockdownConfigs()`. Students can read all materials but cannot modify notes or install plugins.\n\n**Plugin governance**: Maintain an allowlist of 5 approved plugins in `community-plugins.json`. The RBAC plugin reverts any unauthorized additions. New plugin requests go through admin approval.\n\n## Resources\n\n- [Obsidian Plugin API - Vault Events](https://docs.obsidian.md/Reference/TypeScript+API/Vault)\n- [Obsidian Sync for Teams](https://obsidian.md/sync)\n- [RBAC Concepts](https://en.wikipedia.org/wiki/Role-based_access_control)\n- [Obsidian Git Plugin](https://github.com/denolehov/obsidian-git) -- version control for shared vaults\n\n## Next Steps\n\nFor data backup and sync patterns, see `obsidian-data-handling`. For multi-environment testing of RBAC rules, see `obsidian-multi-env-setup`.\n---","attachment_filenames":["references/implementation-guide.md"],"attachments":[{"filename":"references/implementation-guide.md","content":"# Obsidian Enterprise RBAC - Implementation Guide\n\n> Full implementation details for the parent SKILL.md.\n\n## Detailed Instructions\n\n### Step 1: Permission System\n\n```typescript\n// src/rbac/permissions.ts\nexport type Role = 'viewer' | 'editor' | 'contributor' | 'manager' | 'admin';\nexport type Permission = 'read' | 'write' | 'delete' | 'admin' | 'settings';\n\nexport interface User {\n id: string;\n name: string;\n email: string;\n role: Role;\n teamIds: string[];\n projectIds: string[];\n}\n\nexport interface FolderPermission {\n path: string;\n allowedRoles: Role[];\n allowedTeams?: string[];\n allowedUsers?: string[];\n}\n\nconst rolePermissions: Record\u003cRole, Permission[]> = {\n viewer: ['read'],\n editor: ['read', 'write'],\n contributor: ['read', 'write', 'delete'], // delete own only\n manager: ['read', 'write', 'delete', 'settings'],\n admin: ['read', 'write', 'delete', 'admin', 'settings'],\n};\n\nexport class PermissionService {\n private currentUser: User | null = null;\n private folderPermissions: FolderPermission[] = [];\n\n setCurrentUser(user: User): void {\n this.currentUser = user;\n }\n\n setFolderPermissions(permissions: FolderPermission[]): void {\n this.folderPermissions = permissions;\n }\n\n hasPermission(permission: Permission): boolean {\n if (!this.currentUser) return false;\n return rolePermissions[this.currentUser.role].includes(permission);\n }\n\n canAccessFolder(path: string): boolean {\n if (!this.currentUser) return false;\n\n // Admin can access everything\n if (this.currentUser.role === 'admin') return true;\n\n // Check folder permissions\n const folderPerm = this.findFolderPermission(path);\n if (!folderPerm) return true; // No restrictions\n\n // Check role\n if (folderPerm.allowedRoles.includes(this.currentUser.role)) {\n return true;\n }\n\n // Check team membership\n if (folderPerm.allowedTeams) {\n const hasTeam = folderPerm.allowedTeams.some(\n teamId => this.currentUser!.teamIds.includes(teamId)\n );\n if (hasTeam) return true;\n }\n\n // Check user allowlist\n if (folderPerm.allowedUsers) {\n if (folderPerm.allowedUsers.includes(this.currentUser.id)) {\n return true;\n }\n }\n\n return false;\n }\n\n canReadFile(path: string): boolean {\n return this.hasPermission('read') && this.canAccessFolder(path);\n }\n\n canWriteFile(path: string): boolean {\n return this.hasPermission('write') && this.canAccessFolder(path);\n }\n\n canDeleteFile(path: string, ownerId?: string): boolean {\n if (!this.currentUser) return false;\n\n // Contributor can only delete own files\n if (this.currentUser.role === 'contributor') {\n return ownerId === this.currentUser.id && this.canAccessFolder(path);\n }\n\n return this.hasPermission('delete') && this.canAccessFolder(path);\n }\n\n private findFolderPermission(path: string): FolderPermission | null {\n // Find most specific matching folder permission\n let match: FolderPermission | null = null;\n let matchLength = 0;\n\n for (const perm of this.folderPermissions) {\n if (path.startsWith(perm.path) && perm.path.length > matchLength) {\n match = perm;\n matchLength = perm.path.length;\n }\n }\n\n return match;\n }\n}\n```\n\n### Step 2: Protected Operations Wrapper\n\n```typescript\n// src/rbac/protected-vault.ts\nimport { App, TFile, Notice } from 'obsidian';\nimport { PermissionService } from './permissions';\n\nexport class ProtectedVault {\n constructor(\n private app: App,\n private permissions: PermissionService\n ) {}\n\n async readFile(file: TFile): Promise\u003cstring | null> {\n if (!this.permissions.canReadFile(file.path)) {\n new Notice('Permission denied: Cannot read this file');\n return null;\n }\n return this.app.vault.read(file);\n }\n\n async writeFile(file: TFile, content: string): Promise\u003cboolean> {\n if (!this.permissions.canWriteFile(file.path)) {\n new Notice('Permission denied: Cannot write to this file');\n return false;\n }\n await this.app.vault.modify(file, content);\n return true;\n }\n\n async createFile(path: string, content: string): Promise\u003cTFile | null> {\n if (!this.permissions.canWriteFile(path)) {\n new Notice('Permission denied: Cannot create files in this folder');\n return null;\n }\n return this.app.vault.create(path, content);\n }\n\n async deleteFile(file: TFile, ownerId?: string): Promise\u003cboolean> {\n if (!this.permissions.canDeleteFile(file.path, ownerId)) {\n new Notice('Permission denied: Cannot delete this file');\n return false;\n }\n await this.app.vault.delete(file);\n return true;\n }\n\n getAccessibleFiles(): TFile[] {\n return this.app.vault.getMarkdownFiles().filter(\n file => this.permissions.canReadFile(file.path)\n );\n }\n}\n```\n\n### Step 3: User Management\n\n```typescript\n// src/rbac/user-manager.ts\nimport { Plugin } from 'obsidian';\n\nexport interface TeamConfig {\n id: string;\n name: string;\n members: string[];\n folders: string[];\n}\n\nexport interface RBACConfig {\n enabled: boolean;\n users: Record\u003cstring, {\n role: Role;\n teams: string[];\n }>;\n teams: TeamConfig[];\n folderPermissions: FolderPermission[];\n}\n\nexport class UserManager {\n private config: RBACConfig;\n private plugin: Plugin;\n\n constructor(plugin: Plugin) {\n this.plugin = plugin;\n this.config = this.loadConfig();\n }\n\n private loadConfig(): RBACConfig {\n // Load from plugin settings or external config\n return {\n enabled: true,\n users: {},\n teams: [],\n folderPermissions: [],\n };\n }\n\n getCurrentUser(): User | null {\n // Get current user from auth system\n // This could be from local storage, external auth, etc.\n const userId = this.getCurrentUserId();\n if (!userId) return null;\n\n const userConfig = this.config.users[userId];\n if (!userConfig) return null;\n\n return {\n id: userId,\n name: this.getUserName(userId),\n email: this.getUserEmail(userId),\n role: userConfig.role,\n teamIds: userConfig.teams,\n projectIds: this.getProjectsForUser(userId),\n };\n }\n\n private getCurrentUserId(): string | null {\n // Implementation depends on auth system\n // Could be from: local file, external service, etc.\n return localStorage.getItem('obsidian-user-id');\n }\n\n private getUserName(userId: string): string {\n // Fetch from user directory\n return userId;\n }\n\n private getUserEmail(userId: string): string {\n // Fetch from user directory\n return `${userId}@example.com`;\n }\n\n private getProjectsForUser(userId: string): string[] {\n // Get projects user has access to\n return [];\n }\n\n getTeams(): TeamConfig[] {\n return this.config.teams;\n }\n\n getFolderPermissions(): FolderPermission[] {\n return this.config.folderPermissions;\n }\n\n // Admin functions\n async addUser(userId: string, role: Role, teams: string[]): Promise\u003cvoid> {\n this.config.users[userId] = { role, teams };\n await this.saveConfig();\n }\n\n async updateUserRole(userId: string, role: Role): Promise\u003cvoid> {\n if (this.config.users[userId]) {\n this.config.users[userId].role = role;\n await this.saveConfig();\n }\n }\n\n async addFolderPermission(permission: FolderPermission): Promise\u003cvoid> {\n this.config.folderPermissions.push(permission);\n await this.saveConfig();\n }\n\n private async saveConfig(): Promise\u003cvoid> {\n // Save to plugin data or external storage\n }\n}\n```\n\n### Step 4: Audit Logging\n\n```typescript\n// src/rbac/audit-log.ts\nexport interface AuditEntry {\n timestamp: string;\n userId: string;\n action: 'read' | 'write' | 'delete' | 'permission_denied' | 'login' | 'logout';\n resource: string;\n details?: Record\u003cstring, any>;\n success: boolean;\n}\n\nexport class AuditLogger {\n private entries: AuditEntry[] = [];\n private maxEntries = 1000;\n\n log(entry: Omit\u003cAuditEntry, 'timestamp'>): void {\n const fullEntry: AuditEntry = {\n ...entry,\n timestamp: new Date().toISOString(),\n };\n\n this.entries.push(fullEntry);\n\n // Trim old entries\n if (this.entries.length > this.maxEntries) {\n this.entries = this.entries.slice(-this.maxEntries);\n }\n\n // Console log for debugging\n console.log(`[Audit] ${fullEntry.action}: ${fullEntry.resource}`, {\n user: fullEntry.userId,\n success: fullEntry.success,\n });\n }\n\n logAccess(userId: string, path: string, action: 'read' | 'write' | 'delete', success: boolean): void {\n this.log({\n userId,\n action,\n resource: path,\n success,\n });\n }\n\n logPermissionDenied(userId: string, path: string, requestedAction: string): void {\n this.log({\n userId,\n action: 'permission_denied',\n resource: path,\n details: { requestedAction },\n success: false,\n });\n }\n\n getEntries(filter?: {\n userId?: string;\n action?: AuditEntry['action'];\n since?: Date;\n }): AuditEntry[] {\n let results = [...this.entries];\n\n if (filter?.userId) {\n results = results.filter(e => e.userId === filter.userId);\n }\n\n if (filter?.action) {\n results = results.filter(e => e.action === filter.action);\n }\n\n if (filter?.since) {\n results = results.filter(e =>\n new Date(e.timestamp) >= filter.since!\n );\n }\n\n return results;\n }\n\n getPermissionDenials(): AuditEntry[] {\n return this.entries.filter(e => e.action === 'permission_denied');\n }\n\n export(): string {\n return JSON.stringify(this.entries, null, 2);\n }\n}\n```\n\n### Step 5: UI Integration\n\n```typescript\n// src/rbac/rbac-ui.ts\nimport { App, Modal, Setting } from 'obsidian';\nimport { Role, User, PermissionService } from './permissions';\n\nexport class UserRoleModal extends Modal {\n private user: User;\n private onSave: (role: Role) => void;\n\n constructor(app: App, user: User, onSave: (role: Role) => void) {\n super(app);\n this.user = user;\n this.onSave = onSave;\n }\n\n onOpen() {\n const { contentEl } = this;\n contentEl.empty();\n\n contentEl.createEl('h2', { text: `Edit User: ${this.user.name}` });\n\n new Setting(contentEl)\n .setName('Role')\n .setDesc('User role determines permissions')\n .addDropdown(dropdown => {\n dropdown\n .addOption('viewer', 'Viewer')\n .addOption('editor', 'Editor')\n .addOption('contributor', 'Contributor')\n .addOption('manager', 'Manager')\n .addOption('admin', 'Admin')\n .setValue(this.user.role)\n .onChange(value => {\n this.user.role = value as Role;\n });\n });\n\n new Setting(contentEl)\n .addButton(btn => btn\n .setButtonText('Save')\n .setCta()\n .onClick(() => {\n this.onSave(this.user.role);\n this.close();\n }))\n .addButton(btn => btn\n .setButtonText('Cancel')\n .onClick(() => this.close()));\n }\n}\n\n// Status bar indicator\nexport function addRoleIndicator(plugin: Plugin, permissions: PermissionService): void {\n const user = permissions.getCurrentUser();\n if (!user) return;\n\n const statusBar = plugin.addStatusBarItem();\n statusBar.setText(`${user.role.toUpperCase()}`);\n statusBar.addClass(`role-indicator-${user.role}`);\n}\n```\n\n## Complete Examples\n\n### Configuration File\n\n```yaml\nenabled: true\n\nusers:\n alice:\n role: admin\n teams: [management, engineering]\n bob:\n role: editor\n teams: [engineering]\n charlie:\n role: viewer\n teams: [support]\n\nteams:\n - id: management\n name: Management\n folders: [management/, projects/]\n - id: engineering\n name: Engineering\n folders: [engineering/, projects/]\n - id: support\n name: Support\n folders: [support/, public/]\n\nfolderPermissions:\n - path: public/\n allowedRoles: [viewer, editor, contributor, manager, admin]\n - path: management/\n allowedRoles: [manager, admin]\n allowedTeams: [management]\n - path: admin/\n allowedRoles: [admin]\n```\n\n## Access Control Concepts\n\n### Role Hierarchy\n\n| Role | Read | Write | Delete | Admin | Settings |\n|------|------|-------|--------|-------|----------|\n| Viewer | Yes | No | No | No | No |\n| Editor | Yes | Yes | No | No | No |\n| Contributor | Yes | Yes | Own | No | No |\n| Manager | Yes | Yes | Yes | No | View |\n| Admin | Yes | Yes | Yes | Yes | Full |\n\n### Folder-Based Permissions\n\n```\nvault/\n├── public/ # All roles can read\n├── team/ # Editors+ can read/write\n├── projects/\n│ ├── project-a/ # Project members only\n│ └── project-b/ # Project members only\n├── management/ # Managers+ only\n└── admin/ # Admins only\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12624,"content_sha256":"aec88f54fb87e7ab49cdea6d5a343dabe74d445f511e0e15c41e8c0b8c926dc3"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Obsidian Enterprise RBAC","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Vault-level access control patterns for Obsidian in team environments. Covers folder-based permissions via ","type":"text"},{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":" files, read-only enforcement for shared vaults, plugin allowlisting, and configuration lockdown through restricted mode.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Obsidian desktop app with a shared/synced vault","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understanding of Obsidian's ","type":"text"},{"text":".obsidian/","type":"text","marks":[{"type":"code_inline"}]},{"text":" configuration directory","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A sync mechanism in place (Git, Obsidian Sync, or shared filesystem)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Node.js 18+ for scripted permission enforcement","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Instructions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Define a Permission Model","type":"text"}]},{"type":"paragraph","content":[{"text":"Create ","type":"text"},{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":" at the vault root. This JSON file maps roles to folder access:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"version\": 1,\n \"roles\": {\n \"admin\": {\n \"folders\": [\"*\"],\n \"permissions\": [\"read\", \"write\", \"delete\", \"manage\"]\n },\n \"editor\": {\n \"folders\": [\"projects/*\", \"shared/*\", \"templates/*\"],\n \"permissions\": [\"read\", \"write\"]\n },\n \"viewer\": {\n \"folders\": [\"shared/*\", \"published/*\"],\n \"permissions\": [\"read\"]\n }\n },\n \"users\": {\n \"[email protected]\": \"admin\",\n \"[email protected]\": \"editor\",\n \"[email protected]\": \"viewer\"\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Obsidian itself has no built-in RBAC, so this file is consumed by a custom plugin that intercepts file operations.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Build the Permission Checker Plugin","type":"text"}]},{"type":"paragraph","content":[{"text":"Create a plugin that reads ","type":"text"},{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":" and gates vault operations:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"import { Plugin, TFile, Notice } from 'obsidian';\n\ninterface PermissionConfig {\n version: number;\n roles: Record\u003cstring, { folders: string[]; permissions: string[] }>;\n users: Record\u003cstring, string>;\n}\n\nexport default class RBACPlugin extends Plugin {\n private config: PermissionConfig | null = null;\n private currentUser: string = '';\n\n async onload() {\n await this.loadPermissions();\n\n // Intercept file modifications\n this.registerEvent(\n this.app.vault.on('modify', (file) => {\n if (!this.canWrite(file.path)) {\n new Notice(`Permission denied: ${file.path} is read-only for your role`);\n }\n })\n );\n\n // Intercept file creation\n this.registerEvent(\n this.app.vault.on('create', (file) => {\n if (file instanceof TFile && !this.canWrite(file.parent?.path ?? '/')) {\n new Notice(`Permission denied: cannot create files in ${file.parent?.path}`);\n // Move to user's writable area or delete\n this.app.vault.delete(file);\n }\n })\n );\n }\n\n private async loadPermissions() {\n const permFile = this.app.vault.getAbstractFileByPath('.obsidian-permissions');\n if (permFile instanceof TFile) {\n const content = await this.app.vault.read(permFile);\n this.config = JSON.parse(content);\n }\n // Identify current user from plugin settings or environment\n const data = await this.loadData();\n this.currentUser = data?.userEmail ?? '';\n }\n\n private canWrite(path: string): boolean {\n if (!this.config || !this.currentUser) return true; // Fail open if no config\n const role = this.config.users[this.currentUser];\n if (!role) return false;\n const roleDef = this.config.roles[role];\n if (!roleDef) return false;\n if (!roleDef.permissions.includes('write')) return false;\n\n return roleDef.folders.some(pattern => {\n if (pattern === '*') return true;\n const regex = new RegExp('^' + pattern.replace(/\\*/g, '.*') + '

Obsidian Enterprise RBAC Overview Vault-level access control patterns for Obsidian in team environments. Covers folder-based permissions via files, read-only enforcement for shared vaults, plugin allowlisting, and configuration lockdown through restricted mode. Prerequisites - Obsidian desktop app with a shared/synced vault - Understanding of Obsidian's configuration directory - A sync mechanism in place (Git, Obsidian Sync, or shared filesystem) - Node.js 18+ for scripted permission enforcement Instructions Step 1: Define a Permission Model Create at the vault root. This JSON file maps roles…

);\n return regex.test(path);\n });\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Enforce Read-Only Mode on Shared Vaults","type":"text"}]},{"type":"paragraph","content":[{"text":"For vaults where most users should only read, set restricted mode in ","type":"text"},{"text":".obsidian/app.json","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"strictLineBreaks\": false,\n \"readableLineLength\": true,\n \"vimMode\": false,\n \"livePreview\": true\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Then in your RBAC plugin, enforce read-only for non-editor roles by overriding the editor:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// In onload(), after permission check:\nif (!this.canWrite('/')) {\n // Disable editing commands\n this.registerEvent(\n this.app.workspace.on('editor-change', (editor) => {\n // Revert changes for read-only users\n editor.undo();\n new Notice('This vault is read-only for your role.');\n })\n );\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Plugin Allowlisting","type":"text"}]},{"type":"paragraph","content":[{"text":"Lock down which community plugins can be enabled. Edit ","type":"text"},{"text":".obsidian/community-plugins.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" to contain only approved plugins:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"[\"obsidian-git\", \"dataview\", \"templater-obsidian\", \"your-rbac-plugin\"]","type":"text"}]},{"type":"paragraph","content":[{"text":"Then protect this file from modification by non-admins. In your RBAC plugin, watch for changes:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"this.registerEvent(\n this.app.vault.on('modify', async (file) => {\n if (file.path === '.obsidian/community-plugins.json') {\n const role = this.config?.users[this.currentUser];\n if (role !== 'admin') {\n // Restore the approved list\n const approved = await this.loadData();\n await this.app.vault.modify(\n file as TFile,\n JSON.stringify(approved.allowedPlugins)\n );\n new Notice('Only admins can modify the plugin allowlist.');\n }\n }\n })\n);","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Configuration Lockdown via Restricted Mode","type":"text"}]},{"type":"paragraph","content":[{"text":"Obsidian's restricted mode disables all community plugins. For enterprise deployments, combine this with a config lockdown:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// Store a hash of critical config files at deploy time\nconst LOCKED_CONFIGS = [\n '.obsidian/app.json',\n '.obsidian/appearance.json',\n '.obsidian/hotkeys.json',\n '.obsidian/community-plugins.json',\n];\n\nasync lockdownConfigs() {\n const hashes: Record\u003cstring, string> = {};\n for (const path of LOCKED_CONFIGS) {\n const file = this.app.vault.getAbstractFileByPath(path);\n if (file instanceof TFile) {\n const content = await this.app.vault.read(file);\n hashes[path] = await this.hash(content);\n }\n }\n await this.saveData({ ...await this.loadData(), configHashes: hashes });\n}\n\nasync verifyConfigs(): Promise\u003cstring[]> {\n const data = await this.loadData();\n const violations: string[] = [];\n for (const [path, expectedHash] of Object.entries(data.configHashes ?? {})) {\n const file = this.app.vault.getAbstractFileByPath(path);\n if (file instanceof TFile) {\n const content = await this.app.vault.read(file);\n const actual = await this.hash(content);\n if (actual !== expectedHash) {\n violations.push(path);\n }\n }\n }\n return violations;\n}\n\nprivate async hash(content: string): Promise\u003cstring> {\n const encoder = new TextEncoder();\n const data = encoder.encode(content);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"verifyConfigs()","type":"text","marks":[{"type":"code_inline"}]},{"text":" on plugin load and periodically. Alert admins if violations are detected.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":" file defining roles, folder access, and user mappings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RBAC plugin that intercepts create/modify/delete operations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read-only enforcement for non-editor roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plugin allowlist protection in ","type":"text"},{"text":"community-plugins.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configuration lockdown with hash verification for critical ","type":"text"},{"text":".obsidian/","type":"text","marks":[{"type":"code_inline"}]},{"text":" files","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Issue","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Permission denied on all files","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User email not set in plugin settings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Open RBAC plugin settings, enter your email","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Allowlist keeps resetting","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Non-admin edited ","type":"text"},{"text":"community-plugins.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Only admins can modify; check audit log","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Config hash mismatch on every load","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Config changed legitimately","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Admin runs ","type":"text"},{"text":"lockdownConfigs()","type":"text","marks":[{"type":"code_inline"}]},{"text":" to update hashes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Plugin not intercepting writes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Event handler registration failed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check console for plugin load errors","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sync conflicts on ","type":"text"},{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multiple admins editing simultaneously","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use Git with merge strategy or Obsidian Sync","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Examples","type":"text"}]},{"type":"paragraph","content":[{"text":"Team vault with three roles","type":"text","marks":[{"type":"strong"}]},{"text":": Deploy the ","type":"text"},{"text":".obsidian-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":" file above. Set each user's email in the RBAC plugin settings. Editors can modify ","type":"text"},{"text":"projects/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"shared/","type":"text","marks":[{"type":"code_inline"}]},{"text":" folders; viewers can only read ","type":"text"},{"text":"shared/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"published/","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Locked-down training vault","type":"text","marks":[{"type":"strong"}]},{"text":": Set all users to ","type":"text"},{"text":"viewer","type":"text","marks":[{"type":"code_inline"}]},{"text":" role except instructors (","type":"text"},{"text":"editor","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Lock config files with ","type":"text"},{"text":"lockdownConfigs()","type":"text","marks":[{"type":"code_inline"}]},{"text":". Students can read all materials but cannot modify notes or install plugins.","type":"text"}]},{"type":"paragraph","content":[{"text":"Plugin governance","type":"text","marks":[{"type":"strong"}]},{"text":": Maintain an allowlist of 5 approved plugins in ","type":"text"},{"text":"community-plugins.json","type":"text","marks":[{"type":"code_inline"}]},{"text":". The RBAC plugin reverts any unauthorized additions. New plugin requests go through admin approval.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Obsidian Plugin API - Vault Events","type":"text","marks":[{"type":"link","attrs":{"href":"https://docs.obsidian.md/Reference/TypeScript+API/Vault","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Obsidian Sync for Teams","type":"text","marks":[{"type":"link","attrs":{"href":"https://obsidian.md/sync","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RBAC Concepts","type":"text","marks":[{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Role-based_access_control","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Obsidian Git Plugin","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/denolehov/obsidian-git","title":null}}]},{"text":" -- version control for shared vaults","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Next Steps","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"For data backup and sync patterns, see ","type":"text"},{"text":"obsidian-data-handling","type":"text","marks":[{"type":"code_inline"}]},{"text":". For multi-environment testing of RBAC rules, see ","type":"text"},{"text":"obsidian-multi-env-setup","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"obsidian-enterprise-rbac","tags":["saas","obsidian","obsidian-enterprise"],"author":"@skillopedia","source":{"stars":2275,"repo_name":"claude-code-plugins-plus-skills","origin_url":"https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/obsidian-pack/skills/obsidian-enterprise-rbac/SKILL.md","repo_owner":"jeremylongshore","body_sha256":"1947a37e9b78c6f89b4c96cd590245d2326af8fb0b8bb29dd5acdc0800924fe9","cluster_key":"e788c6e2a74c2d38e8ee0a298e507af7d1afca81de1e4c11249fa114ac4729e0","clean_bundle":{"format":"clean-skill-bundle-v1","source":"jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/obsidian-pack/skills/obsidian-enterprise-rbac/SKILL.md","attachments":[{"id":"22f83db3-8e4d-5878-ab2c-fbaa19e82e47","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/22f83db3-8e4d-5878-ab2c-fbaa19e82e47/attachment.md","path":"references/implementation-guide.md","size":12624,"sha256":"aec88f54fb87e7ab49cdea6d5a343dabe74d445f511e0e15c41e8c0b8c926dc3","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"c8a79a9c46717a9edcf4c96459c2739d420f5d338a5f1fbd2e58a36d8c8e6d34","attachment_count":1,"text_attachments":1,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/saas-packs/obsidian-pack/skills/obsidian-enterprise-rbac/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"Implement team vault access patterns and role-based controls.\nUse when managing shared vaults, implementing access controls,\nor building team collaboration features for Obsidian.\nTrigger with phrases like \"obsidian team\", \"obsidian access control\",\n\"obsidian enterprise\", \"shared vault permissions\".\n","allowed-tools":"Read, Write, Edit","compatibility":"Designed for Claude Code, also compatible with Codex and OpenClaw"}},"renderedAt":1782981591023}

Obsidian Enterprise RBAC Overview Vault-level access control patterns for Obsidian in team environments. Covers folder-based permissions via files, read-only enforcement for shared vaults, plugin allowlisting, and configuration lockdown through restricted mode. Prerequisites - Obsidian desktop app with a shared/synced vault - Understanding of Obsidian's configuration directory - A sync mechanism in place (Git, Obsidian Sync, or shared filesystem) - Node.js 18+ for scripted permission enforcement Instructions Step 1: Define a Permission Model Create at the vault root. This JSON file maps roles…