navidrome/plugins/manifest-schema.json
Kendall Garner 54fe6c254e
feat(plugins): implement plugin specific storage (#5839)
* implement storage api/hooks

* fine. if the error messages are different, just match error

* rename storageMount

* add independent plugin test

* round 2

* .-.

* one more copypasta fail

* docs(plugins): document the Storage host service

The README is the plugin author reference and every other host service has a
section there, but Storage had none, so the /storage guest path contract only
existed in code.

Documents the mount point and its backing directory, the manifest permission,
the host function, and the two behaviours an author would otherwise discover
the hard way: there is no size limit, and the directory outlives an uninstall.

* docs(plugins): correct the library filesystem security notes

The README stated in three places that library filesystem access is read-only,
which stopped being true when AllowWriteAccess was added: an administrator can
grant a plugin write access to libraries.

Corrects those and gives the library and storage mounts the same wording for
what the sandbox guarantees, since both now go through the same jail.

* docs(plugins): describe symlink handling in mounts accurately

The security notes claimed paths resolving outside a mount are rejected, which
overstates the jail. Only lexical escapes are: '..' and absolute paths. Creating
symlinks is denied, but symlinks already present are followed and do reach
outside the mount, which is what lets music libraries link folders in from
elsewhere. Both behaviours are pinned by tests in the plugins package.

---------

Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2026-08-03 14:23:42 -04:00

264 lines
7.8 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://navidrome.org/schemas/Manifest.json",
"title": "Manifest",
"description": "Plugin manifest for Navidrome plugins",
"type": "object",
"additionalProperties": false,
"required": ["name", "author", "version"],
"properties": {
"name": {
"type": "string",
"description": "The display name of the plugin",
"minLength": 1
},
"author": {
"type": "string",
"description": "The author of the plugin",
"minLength": 1
},
"version": {
"type": "string",
"description": "The version of the plugin (semver recommended)",
"minLength": 1
},
"description": {
"type": "string",
"description": "A brief description of what the plugin does"
},
"website": {
"type": "string",
"description": "URL to the plugin's website or repository",
"format": "uri"
},
"permissions": {
"$ref": "#/$defs/Permissions"
},
"config": {
"$ref": "#/$defs/ConfigDefinition"
}
},
"$defs": {
"ConfigDefinition": {
"type": "object",
"description": "Configuration schema for the plugin using JSON Schema (draft-07) and optional JSONForms UI Schema",
"additionalProperties": false,
"required": ["schema"],
"properties": {
"schema": {
"type": "object",
"description": "JSON Schema (draft-07) defining the plugin's configuration options"
},
"uiSchema": {
"type": "object",
"description": "Optional JSONForms UI Schema for customizing form layout"
}
}
},
"Permissions": {
"type": "object",
"description": "Permissions required by the plugin",
"additionalProperties": false,
"properties": {
"http": {
"$ref": "#/$defs/HTTPPermission"
},
"subsonicapi": {
"$ref": "#/$defs/SubsonicAPIPermission"
},
"scheduler": {
"$ref": "#/$defs/SchedulerPermission"
},
"websocket": {
"$ref": "#/$defs/WebSocketPermission"
},
"artwork": {
"$ref": "#/$defs/ArtworkPermission"
},
"cache": {
"$ref": "#/$defs/CachePermission"
},
"library": {
"$ref": "#/$defs/LibraryPermission"
},
"kvstore": {
"$ref": "#/$defs/KVStorePermission"
},
"users": {
"$ref": "#/$defs/UsersPermission"
},
"taskqueue": {
"$ref": "#/$defs/TaskQueuePermission"
},
"matcher": {
"$ref": "#/$defs/MatcherPermission"
},
"storage": {
"$ref": "#/$defs/StoragePermission"
}
}
},
"ArtworkPermission": {
"type": "object",
"description": "Artwork service permissions for generating artwork URLs",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why artwork access is needed"
}
}
},
"CachePermission": {
"type": "object",
"description": "Cache service permissions for storing and retrieving data",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why cache access is needed"
}
}
},
"HTTPPermission": {
"type": "object",
"description": "HTTP access permissions for a plugin",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why HTTP access is needed"
},
"requiredHosts": {
"type": "array",
"description": "List of required host patterns for HTTP requests (e.g., 'api.example.com', '*.musicbrainz.org')",
"items": {
"type": "string"
}
}
}
},
"SubsonicAPIPermission": {
"type": "object",
"description": "SubsonicAPI service permissions. Requires 'users' permission to be declared.",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why SubsonicAPI access is needed"
}
}
},
"SchedulerPermission": {
"type": "object",
"description": "Scheduler service permissions for scheduling tasks",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why scheduler access is needed"
}
}
},
"WebSocketPermission": {
"type": "object",
"description": "WebSocket service permissions for establishing WebSocket connections",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why WebSocket access is needed"
},
"requiredHosts": {
"type": "array",
"description": "List of required host patterns for WebSocket connections (e.g., 'api.example.com', '*.musicbrainz.org')",
"items": {
"type": "string"
}
}
}
},
"LibraryPermission": {
"type": "object",
"description": "Library service permissions for accessing library metadata and optionally filesystem",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why library access is needed"
},
"filesystem": {
"type": "boolean",
"description": "Whether the plugin requires read-only filesystem access to library directories",
"default": false
}
}
},
"KVStorePermission": {
"type": "object",
"description": "Key-value store permissions for persistent plugin storage",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why key-value store access is needed"
},
"maxSize": {
"type": "string",
"description": "Maximum storage size (e.g., '1MB', '500KB'). Default: 1MB"
}
}
},
"TaskQueuePermission": {
"type": "object",
"description": "Task queue permissions for background task processing",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why task queue access is needed"
},
"maxConcurrency": {
"type": "integer",
"description": "Maximum total concurrent workers across all queues. Default: 1",
"minimum": 1,
"default": 1
}
}
},
"UsersPermission": {
"type": "object",
"description": "Users service permissions for accessing user information",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why users access is needed"
}
}
},
"MatcherPermission": {
"type": "object",
"description": "Matcher service permissions for resolving external songs to local library tracks",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why matcher access is needed"
}
}
},
"StoragePermission": {
"type": "object",
"description": "Storage permissions for enabling persistent read-write storage exclusively for the plugin",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"description": "Explanation for why storage access is needed"
}
}
}
}
}