跳到正文

路径权限模型参考 ​

涉及文件路径的 Bridge API 会先由 BridgeCore 路径安全规范(Path-security spec)在执行业务 handler 之前进行拦截校验。校验过程可能涉及文件位置解析、文件系统查询与媒体库成员资格判定;未通过校验的请求直接阻断且不进入业务处理函数。被安全策略拒绝的路径返回 PERMISSION_DENIED;参数形状或类型错误返回 INVALID_PARAMS。

计数来自 src/api/schema/*.ts 里的 @security 与 @pathKey 声明,各注册点的路径安全 spec 都由它们生成:

级别spec 条数含义
Read6普通文件系统只读校验
Write1严格写入目标(配置/临时目录策略)
MediaRead53媒体上下文只读校验
MediaWrite10媒体上下文写校验
FileWrite11通用文件写入(file.*)
合计8176 个唯一 API

六级权限模型 ​

级别说明校验逻辑
None不涉及文件路径无路径校验
Read只读文件系统操作禁止系统保护目录、设备路径与 .. 遍历
Write严格写入目标仅允许 PathSecurity 接受的配置/临时等写入目标
MediaRead读取媒体元数据/内容先按物理落点应用 Read 规则;媒体库、播放列表与监视目录信任作为回退,仍须通过落点检查
MediaWrite修改媒体文件独立校验链:保护目录黑名单,再按严格写入目标/媒体库或播放列表成员/媒体库监视目录/与受信音频同目录的伴生文件依次放行。仅仅位于非系统盘不构成放行理由
FileWrite通用文件写入(file.*)独立校验链,并非 MediaWrite 超集:黑名单后按严格写入目标/媒体库监视目录/非系统卷(含真正远程共享;回环共享按实际本地落点)/媒体库或播放列表成员依次放行——不含伴生文件步。监视目录与非系统卷两步共同让 file.mkdir 与 file.write 能创建新路径

权限层级关系

  • 普通文件系统通道:None < Read < Write
  • 媒体文件通道:None < Read < MediaRead < MediaWrite
  • 独立写通道:Write、MediaWrite 与 FileWrite 是三条平行的独立写入通道

FileWrite 比 MediaWrite 宽

FileWrite 允许写入非系统盘上的任意路径,以及媒体库监视目录内的任意路径(包括系统盘),是当前暴露面最宽的写通道。该通道专为 file.* 通用文件操作设计(而非局限于已建库的媒体文件),若套用媒体写入规则将导致无法新建文件或目录。代码审计或审查第三方主题时,应优先审计此通道的调用点。

媒体路径的解析 ​

MediaRead 先从 unpack:// 或受支持的 archive:// 路径提取物理容器;嵌套 unpack 逐层展开。 物理位置为 file-relative:// 时,由 foobar2000 解析便携路径,再检查实际落点的设备路径、 目录权限、映射盘与 UNC 规则。包内条目名不作为本机目录检查。解析失败仍以 PERMISSION_DENIED 拒绝。

物理位置只用于授权检查,读取与路径回显仍使用原曲目路径;|subsong:N 继续选择原曲目的子曲目。 媒体库成员查询保留该轨号。容器可读不代表每个接口都支持其内容:例如封面接口自身的 便携路径限制、外挂歌词候选和媒体流的本地文件限制仍按各接口说明处理。

上述解析只适用于 MediaRead,其它权限级别沿用各自规则。MediaRead 描述的是参数授权, 不代表整个方法没有副作用;播放、添加列表和 ReplayGain 扫描等方法也使用它。

错误响应 ​

json
{
  "success": false,
  "error": "file.read: path security denied for 'path': Access denied: protected system path",
  "code": "PERMISSION_DENIED"
}

被拒的路径本体不会回显。消息里只有方法名、出问题的参数(数组参数还带下标,形如 items[2].destination)和拒因,调用方据此能判断是哪个参数被拒,而宿主不会把文件系统位置泄漏到一个页面可能转发出去的 payload 里。

json
{
  "success": false,
  "error": "file.read: param 'path' must be a string",
  "code": "INVALID_PARAMS"
}
javascript
const result = await fb2k.invoke('file.read', { path: somePath });
if (result.success === false) {
  if (result.code === 'PERMISSION_DENIED') {
    console.warn('路径被安全策略拒绝:', result.error);
  } else if (result.code === 'INVALID_PARAMS') {
    console.warn('参数在进入 handler 前被拒:', result.error);
  }
}

API 权限对照表 ​

Read — 只读文件系统(6 条) ​

API参数数组嵌套键
file.copysource——
file.copyAsyncitems是source
file.existspath——
file.getInfopath——
file.listpath——
file.readpath——

Write — 严格写入目标(1 条) ​

API参数数组嵌套键
http.downloadsaveTo——

MediaRead — 读取媒体文件(53 条) ​

API参数数组嵌套键
artwork.getAvailableArtworkpath——
artwork.getAvailableTypespath——
artwork.getBatchpaths是—
artwork.getByPathpath——
artwork.getFb2kUrlByPathpath——
artwork.getFb2kUrlByPathBatchitems是path
artwork.getFb2kUrlByPathBatchpaths是—
artwork.getFolderImagesdirectory——
artwork.getForTrackpath——
artwork.getLyricspath——
artwork.getMetadatapath——
audio.analyzeBPMpath——
audio.decodePcmpath——
audio.generateFullWaveformpath——
clipboard.writeFilespaths是—
discovery.executeContextMenuByPathtrackPath——
jitQueue.enqueueNexturl——
jitQueue.playNowurl——
jitQueue.preloadBatchurls是—
jitSession.playlocation——
jitSession.setNextlocation——
library.addToPlaylistpaths是—
library.getByPathpath——
lyrics.existspath——
lyrics.getpath——
media.getContainerInfopath——
media.getStreamUrlpath——
metadata.probeBatchAsyncpaths是—
metadata.readpath——
metadata.readBatchpaths是—
metadata.readByPathpath——
metadata.readRawpath——
playback.playPathpath——
playback.playPathspaths是—
playcount.getpaths是—
playcount.getBatchpaths是—
playlist.addHandleshandles是path
playlist.addPathspaths是—
playlist.addPathsAsyncpaths是—
playlist.addPathsSequentialpaths是—
playlist.insertTrackshandles是path
playlist.replaceAllAndPlaypaths是—
queue.addPathspaths是—
queue.insertNextpaths是—
rating.getpath——
replaygain.getpaths是—
replaygain.scanpaths是—
shell.openWithpath——
shell.showInExplorerpath——
titleformat.evalpath——
titleformat.evalBatchpaths是—
titleformat.evalFieldspath——
titleformat.evalFieldsBatchpaths是—

MediaWrite — 修改媒体文件(10 条) ​

API参数数组嵌套键
lyrics.savepath——
metadata.embedArtworkpath——
metadata.removeEmbeddedArtpath——
metadata.removeFieldpath——
metadata.removeTagpath——
metadata.writepath——
metadata.writeBatchitems是path
playcount.setpath——
rating.setpath——
replaygain.clearpaths是—

嵌套数组校验

metadata.writeBatch 的 items 是对象数组,系统会提取每个元素的 path 字段进行校验。

FileWrite — 通用文件写入(11 条) ​

API参数数组嵌套键
file.copydestination——
file.copyAsyncitems是destination
file.deletepath——
file.deleteAsyncpaths是—
file.mkdirpath——
file.movedestination——
file.movesource——
file.moveAsyncitems是destination
file.moveAsyncitems是source
file.renamepath——
file.writepath——

file.copy 的 source 走 Read、destination 走 FileWrite;file.move 两端均走 FileWrite。异步族沿用同一套分档:file.copyAsync 逐条校验 items[].source(Read)与 items[].destination(FileWrite),file.moveAsync 两个嵌套键都走 FileWrite,file.deleteAsync 的 paths 逐条走 FileWrite。逐条校验是 fail-fast:任一条被拒即整批失败返回 PERMISSION_DENIED,不派工。

自定义策略 API ​

API校验方式
shell.exec无命令白名单;可选 cwd 仍走 PathSecurity
shell.spawn无可执行白名单;绝对可执行路径与 cwd 做路径校验
console.log日志目录限制 + 保留设备名过滤 + .log / .txt 扩展名白名单

路径安全规则详解 ​

通用拦截 ​

  • 设备路径:\\.\...、\\?\...
  • 目录遍历:路径中某一级恰好是 . 或 ..(文件名内部包含的连续句点,如 Bonus Track..flac,属于合法路径)
  • 空路径 / 相对路径:必须是绝对路径

Read ​

系统盘保护目录包括:

保护目录原因
C:\\Windows\\系统文件
C:\\Program Files\\程序文件
C:\\Program Files (x86)\\32 位程序文件
C:\\ProgramData\\系统配置数据

foobar2000 的 profile 目录不受上表限制。便携版装在 C:\Program Files\ 下时,仍能读写自己的 profile,包括其中的模板文件夹。

保护目录以外,系统盘在 Read 下只放行 foobar2000 profile、临时目录、OneDrive,以及用户的音乐、视频、图片、桌面、文档和下载文件夹。

非系统盘在 Read 下通常放行,以支持 NAS / 便携版场景。

Write ​

仅允许严格写策略接受的目标目录;实践上为 foobar2000 配置目录与系统临时目录。

MediaRead ​

先执行 Read 规则,一旦通过即放行。因此非系统盘路径(含 UNC / NAS 共享)无需 查询媒体库或播放列表即可通过。

媒体上下文信任只是 Read 拒绝后的回退(实际场景为白名单之外的系统盘路径)。以下 任一条件成立即放行:

  • 该路径可被加入 foobar2000 媒体库,或
  • 该路径能解析到媒体库中的曲目,或
  • 该路径匹配某个播放列表中的曲目。该判定由完整覆盖全部播放列表的成员索引 支撑,没有扫描上限,实际存在的匹配总能命中。

MediaWrite ​

MediaWrite 不复用 Read 或 MediaRead 的入口,而是走自己的校验链。在通用拒绝规则 之后,保护目录黑名单始终生效;随后按顺序命中其中任意一条即放行:

  • 严格写入目标(配置目录 / 临时目录),或
  • 位于媒体库 / 播放列表上下文中的路径,或
  • 位于媒体库监视目录之下的路径 —— 覆盖已落盘但尚未扫描入库的文件,或
  • 与受信上下文音频文件同目录的文件 —— .lrc 等附属文件正是据此放行。

MediaWrite 相对 MediaRead 增加的是黑名单:即使目标确实出现在媒体库或播放列表中, 系统保护目录仍禁止写入。

仅仅位于非系统盘不构成 MediaWrite 的放行理由。 读策略允许非系统盘,但把这条 继承到写侧就等于「任何主题都能改写 D: 或 E: 上的任意音频文件」。

FileWrite ​

FileWrite 走自己的校验链,并非 MediaWrite 的超集:通用拒绝与保护目录黑名单之后, 依次按严格写入目标(配置目录 / 临时目录)、媒体库监视目录(is_path_addable—— 判配置而非成员性,因此新建路径与 UNC 监视目录同样放行)、以盘符寻址的非系统盘 (UNC 不在此列)、媒体库 / 播放列表上下文放行;MediaWrite 的伴生文件步在 FileWrite 中不存在。

FileWrite 放行任意非系统盘

这是当前暴露面最宽的写通道,file.delete 与 file.write 会接受 D:、E: 等盘上的任意目标。保留该放行规则以允许 file.mkdir 与 file.write 在常规数据分区 创建新路径;由于新建路径预先不存在于媒体库或播放列表中,若仅按成员性校验将拒绝调用。 监视目录不同:is_path_addable 判的是配置而非成员性,监视目录内的新建路径不依 赖本放行即可通过。

判据是 canonical 解析之后的盘符,不是调用方传入路径字面的盘符。 若用户目录被 junction / 符号链接重定向到非系统盘(例如 C:\Users\<user> → E:\Users\<user>, 迁移过用户目录的机器上很常见),该目录整棵子树都以 E: 形态参与判定,从而落入本 放行面 —— 尽管传入的路径以 C: 开头。放行与文件扩展名无关。审查主题时不能只看字 面盘符,需按目标机器的实际重定向情况判断。

本页的计数与各级别的表格由 src/api/schema/*.ts 里的 @security 与 @pathKey 声明生成。