Permissions reference
Path-bearing Bridge endpoints are validated by BridgeCore path-security specs before the handler body runs. Validation may resolve file locations and query the filesystem and media membership; rejected requests do not enter the handler. A path the policy refuses returns PERMISSION_DENIED; a parameter of the wrong shape or type returns INVALID_PARAMS.
Counts are taken from the @security and @pathKey declarations in src/api/schema/*.ts, which generate the path-security specs every registration uses:
| Level | Spec count | Meaning |
|---|---|---|
Read | 6 | Ordinary filesystem read checks |
Write | 1 | Strict write destinations (config/temp style policy) |
MediaRead | 53 | Media-context read checks |
MediaWrite | 10 | Media-context write checks |
FileWrite | 11 | General file writes (file.*) |
| Total | 81 | 76 unique APIs |
Six-level model
| Level | Description | Validation summary |
|---|---|---|
None | No file-path parameter | No path validation |
Read | Read-only filesystem operations | Blocks system protected directories, device paths, and .. traversal |
Write | Write destinations under the strict write policy | Allowed only under foobar2000 profile / temp destinations enforced by PathSecurity |
MediaRead | Media metadata/content reads | Read rules apply to the physical location first; media-library, playlist and watch-folder trust provide a fallback that still checks the resolved target |
MediaWrite | Media mutation (tags, lyrics, artwork, counts) | Own chain: protected-directory blacklist, then strict write destination, media-library / playlist membership, media-library watch folders, or a sidecar sharing the directory of a trusted audio file. A non-system drive alone does not grant write access |
FileWrite | General file writes (file.*) | Own chain, not a superset of MediaWrite: blacklist, then strict write destination, media-library watch folders, a non-system volume (including a true remote share; loopback shares follow their local target), or media-library / playlist membership — no sidecar step. The watch-folder and non-system-volume steps let file.mkdir and file.write create brand-new paths |
Level relationships
- Standard filesystem channel:
None < Read < Write - Media channel:
None < Read < MediaRead < MediaWrite - Independent write channels:
Write,MediaWrite, andFileWriteform three separate write channels
FileWrite is broader than MediaWrite
FileWrite permits writes to arbitrary paths on non-system drives, as well as any path inside a media-library watch folder (including on the system drive). It constitutes the widest write channel exposed by the component, intentionally designed for arbitrary file operations via file.* (where imposing media-membership rules would prevent creating new files or folders). Security audits of third-party themes should prioritize reviewing file.* invocations.
Media path resolution
MediaRead extracts the physical container from unpack:// or supported archive:// paths, unwrapping nested unpack paths one layer at a time. foobar2000 resolves a file-relative:// physical location before the host checks its device path, directory permissions, mapped drive and UNC rules. Archive entry names are not checked as local directories. Failed resolution still returns PERMISSION_DENIED.
The physical location is used only for authorization. Reading and path echoes retain the original track path, and |subsong:N still selects that track's subsong. Library membership queries retain this index. An authorized container does not imply that every endpoint supports its contents: artwork endpoints' own portable-path restrictions, lyrics sidecar lookup and media streaming's local-file restrictions still follow their individual contracts.
This resolution applies only to MediaRead; other access levels retain their own rules. MediaRead describes parameter authorization, not whether the method has side effects: playback, playlist additions and ReplayGain scanning also use it.
Error response
{
"success": false,
"error": "file.read: path security denied for 'path': Access denied: protected system path",
"code": "PERMISSION_DENIED"
}2
3
4
5
The rejected path is never echoed back. The message names the method, the offending parameter (plus its index for array parameters, as in items[2].destination) and the policy reason, so a caller can tell which argument was refused without the host leaking a filesystem location into a payload a page may forward elsewhere.
{
"success": false,
"error": "file.read: param 'path' must be a string",
"code": "INVALID_PARAMS"
}2
3
4
5
const result = await fb2k.invoke('file.read', { path: somePath });
if (result.success === false) {
if (result.code === 'PERMISSION_DENIED') {
console.warn('Path rejected by security policy:', result.error);
} else if (result.code === 'INVALID_PARAMS') {
console.warn('Parameter rejected before the handler ran:', result.error);
}
}2
3
4
5
6
7
8
API permission matrix
Read — filesystem read (6 specs)
| API | Parameter | Array | Nested key |
|---|---|---|---|
file.copy | source | — | — |
file.copyAsync | items | yes | source |
file.exists | path | — | — |
file.getInfo | path | — | — |
file.list | path | — | — |
file.read | path | — | — |
Write — strict write destinations (1 spec)
| API | Parameter | Array | Nested key |
|---|---|---|---|
http.download | saveTo | — | — |
MediaRead — media reads (53 specs)
| API | Parameter | Array | Nested key |
|---|---|---|---|
artwork.getAvailableArtwork | path | — | — |
artwork.getAvailableTypes | path | — | — |
artwork.getBatch | paths | yes | — |
artwork.getByPath | path | — | — |
artwork.getFb2kUrlByPath | path | — | — |
artwork.getFb2kUrlByPathBatch | items | yes | path |
artwork.getFb2kUrlByPathBatch | paths | yes | — |
artwork.getFolderImages | directory | — | — |
artwork.getForTrack | path | — | — |
artwork.getLyrics | path | — | — |
artwork.getMetadata | path | — | — |
audio.analyzeBPM | path | — | — |
audio.decodePcm | path | — | — |
audio.generateFullWaveform | path | — | — |
clipboard.writeFiles | paths | yes | — |
discovery.executeContextMenuByPath | trackPath | — | — |
jitQueue.enqueueNext | url | — | — |
jitQueue.playNow | url | — | — |
jitQueue.preloadBatch | urls | yes | — |
jitSession.play | location | — | — |
jitSession.setNext | location | — | — |
library.addToPlaylist | paths | yes | — |
library.getByPath | path | — | — |
lyrics.exists | path | — | — |
lyrics.get | path | — | — |
media.getContainerInfo | path | — | — |
media.getStreamUrl | path | — | — |
metadata.probeBatchAsync | paths | yes | — |
metadata.read | path | — | — |
metadata.readBatch | paths | yes | — |
metadata.readByPath | path | — | — |
metadata.readRaw | path | — | — |
playback.playPath | path | — | — |
playback.playPaths | paths | yes | — |
playcount.get | paths | yes | — |
playcount.getBatch | paths | yes | — |
playlist.addHandles | handles | yes | path |
playlist.addPaths | paths | yes | — |
playlist.addPathsAsync | paths | yes | — |
playlist.addPathsSequential | paths | yes | — |
playlist.insertTracks | handles | yes | path |
playlist.replaceAllAndPlay | paths | yes | — |
queue.addPaths | paths | yes | — |
queue.insertNext | paths | yes | — |
rating.get | path | — | — |
replaygain.get | paths | yes | — |
replaygain.scan | paths | yes | — |
shell.openWith | path | — | — |
shell.showInExplorer | path | — | — |
titleformat.eval | path | — | — |
titleformat.evalBatch | paths | yes | — |
titleformat.evalFields | path | — | — |
titleformat.evalFieldsBatch | paths | yes | — |
MediaWrite — media mutation (10 specs)
| API | Parameter | Array | Nested key |
|---|---|---|---|
lyrics.save | path | — | — |
metadata.embedArtwork | path | — | — |
metadata.removeEmbeddedArt | path | — | — |
metadata.removeField | path | — | — |
metadata.removeTag | path | — | — |
metadata.write | path | — | — |
metadata.writeBatch | items | yes | path |
playcount.set | path | — | — |
rating.set | path | — | — |
replaygain.clear | paths | yes | — |
Nested array validation
metadata.writeBatch validates each object in items by reading the nested path key.
FileWrite — general file writes (11 specs)
| API | Parameter | Array | Nested key |
|---|---|---|---|
file.copy | destination | — | — |
file.copyAsync | items | yes | destination |
file.delete | path | — | — |
file.deleteAsync | paths | yes | — |
file.mkdir | path | — | — |
file.move | destination | — | — |
file.move | source | — | — |
file.moveAsync | items | yes | destination |
file.moveAsync | items | yes | source |
file.rename | path | — | — |
file.write | path | — | — |
file.copy validates source as Read and destination as FileWrite; file.move validates both endpoints as FileWrite. The asynchronous family follows the same split: file.copyAsync checks every items[].source as Read and every items[].destination as FileWrite, file.moveAsync checks both nested keys as FileWrite, and file.deleteAsync checks every entry of paths as FileWrite. Validation is fail-fast per call: one rejected entry fails the whole batch with PERMISSION_DENIED and no operation is dispatched.
Custom / non-decorator policy
These endpoints manage their own policy outside ordinary decorator specs:
| API | Policy notes |
|---|---|
shell.exec | No executable whitelist; optional cwd still goes through PathSecurity |
shell.spawn | No executable whitelist; absolute executable path and cwd are path-checked |
console.log | Log directory restriction, reserved device names, and .log / .txt extension allowlist |
Path security details
Common rejections
- Device paths:
\\.\...and\\?\... - Directory traversal: any path segment that is
.or..(consecutive periods within a file name, such asBonus Track..flac, are valid path characters) - Empty or relative paths (absolute paths required)
Read
System-drive protected directories include:
| Directory | Reason |
|---|---|
C:\\Windows\\ | OS files |
C:\\Program Files\\ | Installed programs |
C:\\Program Files (x86)\\ | 32-bit programs |
C:\\ProgramData\\ | System configuration data |
The foobar2000 profile directory is exempt from this list. A portable installation inside C:\Program Files\ can still read and write its own profile, including its template folders.
Outside the protected directories, Read on the system drive allows only the foobar2000 profile, the temporary directory, OneDrive, and the user's Music, Videos, Pictures, Desktop, Documents and Downloads folders.
Non-system drives are generally allowed for portable / NAS media workflows under Read.
Write
Only destinations accepted by the strict write policy succeed. In practice this is the foobar2000 profile directory and the system temporary directory.
MediaRead
The Read rules run first and the path is accepted as soon as they pass. A non-system-drive path — including a UNC / NAS share — is therefore allowed without any media-library or playlist lookup.
Media-context trust is a fallback, reached only when the Read rules reject the path (in practice, a system-drive path outside the whitelist). Any one of these admits the path:
- it is addable to the foobar2000 media library, or
- it resolves to a media library item, or
- it matches a playlist item. This lookup is served by a membership index that covers every playlist in full, so a genuine match is always found.
MediaWrite
MediaWrite does not delegate to the Read or MediaRead entry points; it runs its own chain. After the common rejections, a protected-directory blacklist check always applies, then the first of these to match admits the path:
- a strict write destination (profile / temp), or
- a path in media-library / playlist context, or
- a path under a media-library watch folder, which covers files already on disk but not yet scanned into the library, or
- a file in the same directory as a trusted context media file, which is how sidecar writes such as
.lrcare permitted.
The blacklist is what MediaWrite adds over MediaRead: a protected system directory stays blocked even when the item does appear in a library or playlist.
Being on a non-system drive does not by itself admit a MediaWrite path. The read policy allows non-system drives, but inheriting that on the write side would mean any theme could rewrite arbitrary audio files on D: or E:.
FileWrite
FileWrite runs its own chain and is not a superset of MediaWrite. After the common rejections and the protected-directory blacklist, the first of these to match admits the path: a strict write destination (profile / temp), a media-library watch folder (is_path_addable — configuration, not membership, so it admits brand-new paths and UNC folders), a non-system drive addressed by drive letter (UNC excluded), or media-library / playlist context. MediaWrite's sidecar step is absent here.
FileWrite accepts any non-system drive
This is the widest write channel currently exposed; file.delete and file.write accept arbitrary destinations on other drives such as D: or E:. This allowance is maintained to permit file.mkdir and file.write to create new paths on data volumes, which by definition cannot yet exist in a library or playlist and would otherwise be rejected by membership checks alone. Watch folders are different: since is_path_addable checks configuration rather than membership, new paths inside a configured folder are admitted without needing this allowance.
The test applies to the drive letter after canonicalization, not to the one in the path as passed in. If a user directory is redirected onto a non-system drive by a junction or symbolic link (for example C:\Users\<user> → E:\Users\<user>, common on machines whose user profiles have been relocated), its entire subtree is evaluated in its E: form and therefore falls inside this allowance — even though the caller passed a path beginning with C:. The allowance is independent of file extension. When reviewing a theme, do not rely on the literal drive letter; check how the target machine actually redirects these directories.
The counts and the per-level tables on this page are generated from the @security and @pathKey declarations in src/api/schema/*.ts.