Skip to content

权限与围栏 ​

这套系统里最要紧的部分。分三层看。

text
应用想干一件事
   │
   ├─ ① 围栏(Node 权限模型)  ── 内核层面拦,越权直接崩
   ├─ ② 授权表(能力 → 范围)  ── 查这个应用有没有这项能力
   └─ ③ UAC 审批(问用户)      ── 没有就弹框问

① 能力即字符串 ​

text
fs.read:/sdcard/**

三段:能力 + : + 范围。

授权记录是三元组:

text
应用 = demo
能力 = fs.read
范围 = /sdcard/**

同一个应用、同一个能力、同一个范围 —— 只问一次。

② 能力清单 ​

文件类 ​

能力说明
fs.read读文件内容
fs.list列目录
fs.stat取元数据(大小、mtime、类型)
fs.watch监听文件变化
fs.write写文件
fs.mkdir建目录
fs.remove删除
fs.copy复制 / 移动

读能力家族互覆盖。 一条 fs.read:/data/** 同时覆盖 fs.list / fs.stat / fs.watch。 理由很实际:fs.list 本来就需要读目录,fs.stat 本来就需要读 inode, fs.watch 本来就需要读目录项 —— 让用户为同一件事点三次是设计失败。

进程 / 网络 / 系统 ​

能力风险说明
proc.spawn高起子进程。范围是可执行文件路径
net.fetch中联网。范围是主机名
sys.info低读系统信息,不含敏感内容

交互类 ​

能力风险说明
dialog.open低文件选择器 / 保存框 / 消息框
clipboard.write低写剪贴板
clipboard.read中读剪贴板 —— 里面可能刚复制过密码、验证码或私钥

③ UAC 的两种答案 ​

这一点很重要,容易搞混:

UAC 审批对话框
问什么「允许这个应用读 /sdcard 吗?」「你要打开哪个文件?」
答案枚举:允许 / 拒绝 / 始终允许数据:路径、文件名、按钮 id
谁决定用户用户,但选的是内容

应用永远不能指定自己想去哪。 对话框的起始目录 startIn 有白名单 (沙箱 data → cwd → 家目录),也没有 defaultPath 这个参数。

用户选中一个路径后,系统会为那一个路径自动记一条授权 —— 这就是「点了一个文件」与「知道应用要拿它做什么」之间的分界。 高危路径(私钥之类)不在自动授权范围内,仍要走审批。

风险分级 ​

等级能力处置
低sys.info fs.read fs.list fs.stat fs.watch dialog.open clipboard.write弹一次框,用户点「始终允许」后不再问
中fs.write fs.mkdir fs.remove fs.copy net.fetch clipboard.read每次都问
高proc.spawn需要先开限时管理员模式

dialog.open 定为低风险的理由写在了代码里:由用户在桌面上亲自选择,应用无法指定路径。

管理员模式 ​

高危操作(起子进程)需要限时提权:

  • 通过挑战口令开启,有效期 30 分钟,自动失效
  • 桌面顶部常驻红条 —— 提权期间用户必须看得见
  • 生效期间高危操作不再逐个弹框

围栏 ​

Node 应用跑在 Node 权限模型(--experimental-permission)里:

bash
node --experimental-permission \
     --allow-fs-read=./ \
     --allow-fs-write=./data \
     --allow-child-process \
     app.js

内核生成这些参数时会自动放行 @runx/sdk 与 @runx/host 所在的目录 —— 否则应用连自己的 SDK 都读不到。

@runx-node-safety 的策略文件里有一段专门的说明:

js
} else if (cap === 'dialog.open' || cap === 'clipboard.read' || cap === 'clipboard.write') {
  notes.push(cap + ' 由审批层(broker)控制,Node 权限模型没有对应开关');
}

因为这些能力走的是应用 → 网关 → 审批层的链路,根本不碰 Node 的文件 API。

一句话总结 ​

应用能做什么,由它申请什么决定; 它能碰哪些路径,由用户批准什么决定; 它碰不到什么,由围栏在内核层面兜底。

MIT 开源 · RunX OS