news 2026/9/9 13:21:47

Puter 文件共享权限审计指南:用 puter.fs.getShares() 查看谁能访问你的文件与目录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Puter 文件共享权限审计指南:用 puter.fs.getShares() 查看谁能访问你的文件与目录

Puter 文件共享权限审计指南:用 puter.fs.getShares() 查看谁能访问你的文件与目录

【免费下载链接】puter🌐 The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter

puter.fs.getShares()是 Puter 文件系统(FS)模块中用于“反向审计共享关系”的核心方法:给定一个你拥有或拥有manage权限的文件或目录,它会返回一份完整的授权清单,告诉你谁能到达它、以什么模式到达、授权来自哪里。本文以 getShares.md 为骨架,结合puter-jsSDK 与后端ShareController的实现细节,完整讲解其语法、返回结构、邀请(pending)语义、继承授权行为,并提供可直接运行的实战示例,帮助你精确掌握 Puter 共享模型的边界与审计能力。

适用前提:谁可以调用、能查到什么

getShares()用于列出“谁能访问某个文件或目录”,调用者必须满足以下条件之一:

  • 是该文件或目录的所有者
  • 拥有该对象的manage权限(例如你被授予了对某个共享文件夹的manage权限)。

只有具备上述身份,你才能读到该对象的共享关系清单。

应用(App)能分享什么:越权是设计上被禁止的

Puter 的共享模型强调“App 永远不会获得超过用户授予它的权限”。文档中明确了 App 可触达的边界:

  • App 可以分享自己的 AppData,以及用户明确授予它的文件
  • 分享的权限级别不超过 App 自身持有的级别——例如一个只有read权限的 App,最多只能再授予read,无法授予writemanage
  • 用户拥有但从未交给 App 的文件,App 无法触达;
  • listShared()(见 listShared.md)对 App 只展示它能触达的那部分共享关系;
  • App 创建的共享归属于背后的用户,同时携带issuedByApp标记,便于所有者通过getShares()区分“用户本人创建”与“App 代为创建”。

你能看到的共享由“谁授予”决定

getShares()返回的清单不仅包含你自己授予的共享,还包含任何持有该对象manage权限的人所授予的共享。这正是所有者查看“我信任的人又转授给了谁”的途径。

语法与参数

两种等价调用形式:

puter.fs.getShares(path); puter.fs.getShares(options);

从 SDK 源码(operations/getShares.js)看,该操作在defineOperation中声明了位置参数['path'],并在请求构造时二选一写入查询参数:优先使用uid,否则把path交给getAbsolutePathForApp解析为绝对路径,最终请求GET /share/shares?...

参数说明

参数类型必需说明
pathString二者必居其一文件或目录的路径。若为相对路径,将相对 App 的根目录解析(SDK 通过getAbsolutePathForApp完成归一化);若在调用位置参数形式时,path为第一个位置参数
options.pathString仅以 options 作为唯一入参时必需与位置参数语义相同
options.uidString二者必居其一以对象的 UID 定位条目,可替代path使用

若同时不提供uidpath,后端会直接拒绝。见后端实现 ShareController.ts:GET /share/shares校验后若target为空会抛出400 one of 'uid' or 'path' is requiredlegacyCode: 'bad_request')。此外,当以路径定位时,后端会用expandTildePath处理~起始的路径。

返回值结构:逐个共享对象的字段

getShares()返回一个Promise,解析为一个共享对象数组。前端映射发生在 shareUtil.js 的toShare函数中,它把服务端下发的行记录归一化为 SDK 公开的Share结构(类型定义见 types.js)。下表字段与该映射一一对应:

字段类型含义
uidString这条共享记录的 UID
modeString授予的访问模式(read/write/manage等)
pathString被共享条目的路径
entryUidString被共享条目本身(目录项)的 UID(服务端字段为uid_entryentryUid
isDirBoolean被共享对象是否为目录(服务端字段为is_dirisDir
issuerString | null这条共享由谁发出
holderString | null持有访问权限的人;邀请(pending)共享时为null
inheritedFromString | null该访问权限所继承自的“共享祖先”路径;共享落在对象自身时值为null
issuedByAppString | null请求该共享的 App 的 UID;由用户直接创建时值为null
modifiedNumber修改时间戳
sizeNumber | null大小(用于文件浏览器渲染)

注:toShare还会尽力携带nametypethumbnailowner等展示字段,原因是共享清单背后没有可 stat 的 fsentry,服务端直接在行记录中携带文件浏览器渲染所需的信息;这些字段在其它场景下可能缺失。当记录处于pending状态(服务端status === 'pending'pending === true)时,还会追加pending: truerecipientEmail

issuedByApp:区分“人”与“App”的授权

issuedByApp为 App 的 UID,表示这条共享是某个 App 代替其背后的用户发出的;若由用户直接操作创建,则为null。这一字段让所有者在审计时可以一眼区分两类来源——这正是文档中“App 创建的共享归属用户但携带来源标记”的设计落地。

继承授权与邀请:两个必须理解的行为

inheritedFrom 与“在祖先上托管”

若某条访问权限不是直接落在当前条目上,而是来自其某个共享祖先目录,则该记录的inheritedFrom字段会给出那个祖先的路径;若共享直接落在当前条目自身,则为null

关键管理约束有两层:

  1. 当你不是所有者时,inheritedFrompath一样会被掩码处理,防止非所有者探测完整的目录结构;
  2. 从父目录继承来的访问权限,托管在那个父目录上——因此你无法在这里(当前条目上)撤除它,因为该授权记录并不存在于当前条目。要收回继承授权,必须到授权实际所在的祖先目录上操作。

invitations:面向未确认邮箱的待认领共享

返回清单同时包含邀请(invitations)——即发送到某个邮箱地址、但该邮箱还没有对应已确认账户的共享:

  • 这类记录携带pending: true
  • holdernull
  • 目标地址存放在recipientEmail中;
  • 在收件人确认该邮箱之前,它们不授予任何实际权限
  • 如需在认领前取消,调用puter.fs.unshare()即可。

错误与隐私行为:不会确认“对象存在”

如果调用者完全看不到该条目(既非所有者也无manage权限),getShares()的拒绝方式与请求一个不存在的文件完全相同——也就是说,该 API 不会通过错误差异来向无权者确认对象是否存在,避免信息泄露。

完整实战示例

示例一:查看谁能访问一个文件

先写入文件、用邮箱发起共享,再列出所有共享并逐条打印持有者、模式与发出者:

<html> <body> <script src="https://js.puter.com/v2/"></script> <script> (async () => { await puter.fs.write('report.txt', 'Quarterly numbers'); await puter.fs.share('report.txt', 'friend@example.com', 'read'); const shares = await puter.fs.getShares('report.txt'); for (const share of shares) { puter.print(`${share.holder}: ${share.mode} (from ${share.issuer})<br>`); } })() </script> </body> </html>

输出将包含直接授予friend@example.com的共享;若该邮箱账户未确认,这条记录会以pending形式出现(holdernullrecipientEmail为目标地址)。

示例二:撤销所有人的访问

配合puter.fs.share()puter.fs.unshare(),实现“一键清理”:

const shares = await puter.fs.getShares('report.txt'); for (const share of shares) { await puter.fs.unshare('report.txt', share.holder); }

需要留意:unshare对每个被撤对象的语义在 SDK 中有所体现(见 operations/unshare.js)——它把撤销请求发往POST /share/revoke,并在响应后清空相关条目缓存(item:<path>readdir:<dirname>,见 shareUtil.js 的 invalidateShareCache)。无法对holdernull的待认领邀请直接用上面的循环撤销,应针对邀请取消场景单独处理。

示例三:按来源过滤——区分人授予与 App 授予

结合issuedByApp做更精细的审计展示:

const shares = await puter.fs.getShares('/Documents/project'); for (const share of shares) { if (share.issuedByApp) { puter.print(`App ${share.issuedByApp} 授予了 ${share.holder} ${share.mode}(由 ${share.issuer} 发出)`); } else { puter.print(`${share.issuer} 直接授予了 ${share.holder} ${share.mode}`); } }

底层实现:从 SDK 到后端的一条链路

getShares()的实现与源码证据可以归纳为一条清晰链路:

  1. SDK 层(operations/getShares.js):声明位置参数path,用URLSearchParams构造uidpath查询串,向GET /share/shares发起请求,再把响应体{ items: [...] }逐项经toShare映射为Share[]返回。
  2. 后端路由层(ShareController.ts):GET /share/shares装饰器声明了subdomain: 'api'requireVerified: true(要求已验证邮箱)与rateLimit: SHARE_LIST_LIMIT。处理器要求uidpath二选一,随后调用共享服务层的listSharesOf(actor, target),最后同样经统一的#toClientShare转换后返回{ items }信封。
  3. 服务层listSharesOf承担实际查询逻辑,相关测试(如 ShareService.test.ts、ShareConsistency.test.ts)对其按权限遍历与一致性进行了专门验证。

与它对照的两个“宏观视角”接口也值得了解(实现于同一 ShareController.ts):

  • GET /share/shared-by-me:分页列出调用者分享出去的全部内容,支持appUid过滤到某个 App 或none(用户本人创建);适用于调用者不知道要问哪个条目的场景,而GET /share/shares一次只能针对一个条目;
  • GET /share/shared-by-me/apps:列出持有调用者所建共享的 App 及计数,是查询appUid的入口。

SDK 侧对应的两种“自己视角”能力见 listSharedByMe.md 与 listShared.md。

边界与注意事项小结

  • 身份要求:必须是所有者或持有manage权限,否则按“文件不存在”拒绝;
  • 相对路径:非绝对路径会相对 App 根目录解析(SDK 层)或处理~(后端expandTildePath);
  • 范围覆盖:结果包含所有manage持有者转授的共享,不只你自己;
  • 继承 vs 直授inheritedFrom标明来源;继承授权在祖先上托管,不能在当前条目撤除;
  • 邀请语义pending: trueholder: null、地址在recipientEmail,认领前不授任何权限,可用unshare()提前取消;
  • App 边界:App 只能分享自身 AppData 与被用户授予的文件,且授予级别不超过自身所持级别,共享归用户所有并携带issuedByApp来源标记。

相关 API

  • puter.fs.share()— 授予访问权限
  • puter.fs.unshare()— 撤回访问权限
  • puter.fs.listSharedByMe()— 分页查看自己分享出去的内容
  • puter.fs.listShared()— 查看共享给我的内容

【免费下载链接】puter🌐 The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 13:20:30

论文正文AI率不高但图表说明和脚注被标红:三款免费AIGC检测工具实测

论文正文AI率不高但图表说明和脚注被标红&#xff1a;三款免费AIGC检测工具实测 在工科与经管类学位论文的查重与 AIGC 检测中&#xff0c;很多硕博同学都会遇到一种令人哭笑不得的特殊情况&#xff1a;论文正文主体论述的 AI 疑似度明明只有 5% 左右&#xff0c;但文末的图表…

作者头像 李华
网站建设 2026/9/9 13:19:33

步进、闭环、伺服电机怎么选?从原理到实战的选型指南

过去这几年&#xff0c;我前前后后帮朋友和客户选过不下几十套电机方案&#xff0c;聊得最多的就一个问题&#xff1a;步进、闭环、伺服到底怎么选&#xff1f;为什么别人用闭环步进就能搞定的活&#xff0c;我这边怎么调都不对&#xff0c;非得乖乖上伺服&#xff1f;每次我掏…

作者头像 李华
网站建设 2026/9/9 13:19:22

opencode 完全指南:从安装配置到 Skills 与 Playwright 实战

opencode 最近在开发圈里热度涨得很快&#xff0c;身边不少朋友都在问它跟 Claude Code、Codex 到底有什么区别&#xff0c;值不值得切过来。我用了一段时间之后&#xff0c;最大的感受是&#xff1a;这玩意儿更像一个“开放版本”的终端 AI 编程代理&#xff0c;模型可以自己接…

作者头像 李华
网站建设 2026/9/9 13:18:13

Windows UI自动化必会工具:Inspect元素定位实战指南

做Windows桌面应用自动化的人应该都有过这种经历&#xff1a;界面上一个按钮死活定位不到&#xff0c;代码逻辑看起来全对&#xff0c;但一跑自动化脚本就扑空。这种时候我一般会先打开Inspect&#xff0c;对着目标控件看一眼属性&#xff0c;问题往往立刻就清楚了。Inspect是微…

作者头像 李华
网站建设 2026/9/9 13:17:39

程序员省时指南:从环境配置到AI辅助,把时间还给代码

很多人一提起“节约时间打代码”&#xff0c;第一反应是学一堆快捷键、装一堆效率插件、把键盘敲出火星子。但我做了这么多年开发&#xff0c;越来越确信一件事&#xff1a;真正吃掉你编程时间的&#xff0c;往往不是敲键盘那几下&#xff0c;而是敲键盘之前和之后那些看不见的…

作者头像 李华