Permissions

Understand how the permission system works and how to declare permissions in your extension.

Overview

Extensions must declare all required permissions in their manifest.json. Users review and approve these permissions during installation. This ensures transparency and gives users control over what extensions can access.

Granted

Permission is approved

Denied

Permission was rejected

Ask

Will prompt the user

{
  "permissions": {
    "database": [
      { "target": "MCowBQYDK2Vw...__password-manager__credentials", "operation": "read" },
      { "target": "MCowBQYDK2Vw...__password-manager__categories", "operation": "readWrite" }
    ],
    "filesystem": [
      { "target": "/exports/**", "operation": "readWrite" }
    ],
    "http": [
      { "target": "https://api.example.com/**" }
    ],
    "shell": null
  }
}

Permission Types

Database

Read and write data to SQLite tables

read
readWrite
create
delete
alterDrop

Filesystem

Save and open files through native dialogs

read
readWrite

HTTP

Make web requests to external APIs

(URL pattern only)

Shell

Execute system commands (restricted)

execute

Database Permissions

Database permissions control access to tables from other extensions. The target must be the full table name in the format ____:

Extensions can create, read, and modify their own tables within their namespace without any permissions. Permissions are only required when accessing tables from other extensions.

// Access another extension's table.
// Format: {publicKey}__{extensionName}__{tableName}
{ "target": "MCowBQYDK2Vw...__password-manager__credentials", "operation": "read" }

// Read + write on another extension's table:
{ "target": "MCowBQYDK2Vw...__password-manager__categories", "operation": "readWrite" }

// Allowed manifest operations for database:
//   "read" | "readWrite" | "create" | "delete" | "alterDrop"
//
// The SDK's permission check API uses a simplified pair:
//   "read" | "write"
// where "write" subsumes readWrite/create/delete/alterDrop.

Accessing Dependency Tables

import { useHaexVaultSdk } from '@haex-space/vault-sdk/vue'

const { client } = useHaexVaultSdk()

// Access another extension's table using getDependencyTableName
const depTable = client.getDependencyTableName(
  'MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV',
  'password-manager',
  'credentials'
)

// Query the dependency table (requires the permission entry shown above)
const credentials = await client.query(`SELECT * FROM ${depTable}`)

Filesystem Permissions

Filesystem permissions use glob patterns to specify which files your extension can access:

// Filesystem manifest operations: "read" | "readWrite"

// Read text files anywhere
{ "target": "**/*.txt", "operation": "read" }

// Read and write in a specific directory
{ "target": "/exports/**", "operation": "readWrite" }

// Full access to JSON files in documents
{ "target": "/documents/*.json", "operation": "readWrite" }

All filesystem operations show native dialogs to the user, providing an additional layer of security.

HTTP Permissions

HTTP permissions specify which URLs your extension can make requests to:

// HTTP permissions are URL-based only.
// The runtime check (PermissionManager::check_web_permission) only
// matches the URL / domain - HTTP methods are NOT enforced today.

// Allow all requests to a specific API
{ "target": "https://api.example.com/**" }

// Specific path pattern
{ "target": "https://api.github.com/users/*/repos" }

// Bare domain target (matches the host)
{ "target": "api.example.com" }

Pattern Syntax

  • ** - Matches any path segments
  • * - Matches a single path segment

Checking Permissions

Before performing an operation, you can check if the permission is granted:

import { useHaexVaultSdk } from '@haex-space/vault-sdk/vue'

const { client } = useHaexVaultSdk()

// Check database permission ("read" | "write" only at the SDK boundary)
const canReadUsers  = await client.permissions.checkDatabaseAsync(usersTable, 'read')
const canWriteUsers = await client.permissions.checkDatabaseAsync(usersTable, 'write')

// Check filesystem permission
const canReadExports = await client.permissions.checkFilesystemAsync('/exports/data.json', 'read')

// Check HTTP permission (URL must match a manifest http target)
const canFetchApi = await client.permissions.checkWebAsync('https://api.example.com/data')

if (canWriteUsers) {
  await client.database.insert(usersTable, { name: 'John', email: 'john@example.com' })
} else {
  console.warn('No write permission for users table')
}

Requesting Permissions

You can request permissions at runtime, which will prompt the user:

import { useHaexVaultSdk } from '@haex-space/vault-sdk/vue'

const { client } = useHaexVaultSdk()

// Request a database permission at runtime (prompts the user)
const response = await client.requestDatabasePermission({
  resource: usersTable,
  operation: 'write',
  reason: 'Required to save your profile changes',
})

if (response.status === 'granted') {
  await client.database.insert(usersTable, userData)
} else if (response.status === 'denied') {
  showError('Permission denied. Cannot save data.')
}

Permission Status Values

// Permission status values (manifest + runtime)
type PermissionStatus = 'granted' | 'denied' | 'ask'

interface PermissionResponse {
  status: PermissionStatus
}

Best Practices

Request Minimal Permissions

Only request permissions your extension actually needs. Users are more likely to install extensions with fewer permission requests.

Be Specific

Use specific table names and URL patterns instead of wildcards. This makes your extension's behavior more predictable and trustworthy.

Explain Why

When requesting permissions at runtime, provide a clear reason why the permission is needed.

Handle Denials Gracefully

If a permission is denied, provide a good user experience. Show a helpful message and offer alternatives if possible.

Extensions that request excessive permissions may be flagged during marketplace review.