Documentation
¶
Overview ¶
本文件是进程托管层:Manager 负责进程启动(沙箱包装由 sandbox_*.go 提供)、 取消与期限(ctx)、输出分流、在跑进程登记与 Close 统一回收。
Native processes enforce an ordered file/network policy snapshot. Cwd grants no access. Unsupported restrictions fail before execution.
Package vbox 是原生子进程沙箱库:规则模型 + 纯判定 + OS 落地(Seatbelt / bubblewrap / 受限令牌 + ACL)+ 进程生命周期。
它只做三件事:
- 规则模型:FS 与 Net 各一张有序表,从前向后扫描、首命中生效(first-wins)。 行首是效果前缀:文件域 ro:/rw:/deny:,网络域 allow:/deny:(host:port)。 行为完全由行序决定,没有「谁压谁」的特殊规则;Class 只用于调用方拼接行序, 判定时不看。
- 判定:纯函数匹配(MatchPath / Match / MatchNoFollow / NetRuleSet.Resolve), 不访问宿主文件系统,也不读配置。
- 落地:把编译好的快照交给内核机制执行;当前平台无法表达的规则在启动前拒绝 (fail-closed),绝不静默降级为裸跑。RunProcess 是唯一进程入口。
配置解析、默认规则源、会话授权、grant 生命周期与审批策略都属调用方(宿主集成层); vbox 每次调用接收一份编译好的快照,不维护授权状态。Windows 侧仅为拒绝 ACE 的 幂等对账保留一份状态文件(默认 `{UserConfigDir}/vbox/sandbox/deny_acl_state.json`, 可用 Manager.SetStateDir 改)。
Index ¶
- func Canonical(p string) string
- func CanonicalNoFollow(p string) string
- func CanonicalPattern(pat string) string
- func DedupClean(roots []string) []string
- func DualForms(p string) []string
- func DualList(paths []string) []string
- func EntryMatch(r, q Entry) bool
- func ExpandVars(s string) (string, bool)
- func HostPathToOS(p string) string
- func IneffectiveRows(rules []Rule) []int
- func JoinPattern(root, sub string) string
- func MatchPattern(pattern, cpath string) bool
- func NormalizeHost(host string) string
- func ScrubEnv(env []string) []string
- func SetSysProcAttr(cmd *exec.Cmd)
- func ValidateEntries(list []string) error
- func ValidateFSGrantTarget(abs string) error
- func ValidateFSRules(list []string) error
- func ValidateTargetRules(list []string) error
- type Class
- type Decision
- type DeniedError
- type Effect
- type Entry
- type FSRuleSet
- type FileOp
- type Manager
- type NetRule
- type NetRuleSet
- type Policy
- type Resolution
- type Rule
- type StartOptions
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Canonical ¶
Canonical 展开符号链接到真实文件系统身份:EvalSymlinks 逐级向父目录回退 (目标不存在时展开最近存在祖先,剩余路径原样拼接——写新文件场景)。 输出为 /c/ 规范形(posix 为 posix 形)。 递归到文件系统根基(/ 或 C:\)时直接返回原路径:根基无可展开项,且 TrimSuffix 根基分隔符得空串/盘符,继续递归会退化成相对路径。
func CanonicalNoFollow ¶
CanonicalNoFollow 展开父目录符号链接、保留末段字面形(unlink/rename 语义: 系统调用作用于链接本身,判定必须同口径)。父链复用 Canonical(含最近存在 祖先回退);末段为根/退化形态时退回跟随版。输出同为 /c/ 规范形。
func CanonicalPattern ¶
CanonicalPattern 对 glob 模式的字面前缀段(首个含通配符段之前)做 canonical 展开:/var/tmp/x/** → /private/var/tmp/x/**(macOS /var symlink)。 纯字面模式(无通配符):整串展开;** 开头的模式无前缀可展开(原样返回)。
func DualForms ¶
DualForms 返回路径的 canonical 形与原始字面形(去尾斜杠;相同则只给一个)。 沙箱(seatbelt)按系统调用实际传入的路径串匹配规则:macOS 的 /tmp、/etc、 $TMPDIR(/var/folders/…) 都是 symlink 前缀,只留 canonical 形会让字面拼写的 访问(含路径解析的 metadata 读)被拒(2026-09-22)。
func EntryMatch ¶
EntryMatch 判定规则条目 r 是否覆盖查询条目 q:host 字符串相等(双方已归一) 且端口匹配(r 为 * 或相等;q 为 * 时取保守交集——重叠即命中)。
func ExpandVars ¶
ExpandVars 展开模式中的 ~(~ 与 ~/ 前缀)与环境变量:$VAR/${VAR}(unix)、 %VAR%(windows 形态,跨平台都展开——用户 cfg 条目可能引用任一形态)、特殊记号 $UserConfigDir(Go 的 os.UserConfigDir,无对应环境变量)。 任一变量的展开失败(未定义变量、无家目录)使整条模式失配(ok=false)—— 初始名单已按平台分表,此规则只防御用户 cfg 条目(写了本平台不存在的变量时宁缺毋滥)。 例外:HOME 与 ~ 同义(unix 惯例变量,Windows 原生只有 USERPROFILE,GUI/schtasks 启动的进程没有 HOME)——缺失/为空时回退 os.UserHomeDir(),否则内置规则表在 Windows 上整表编译失败(2026-10-04 win 实机:rule N: cannot expand "$HOME/<内置条目>/**")。
func HostPathToOS ¶
hostPathToOS 把 /c/ 规范形(及旧输入形)转回 OS 原生路径(windows;posix 恒等——"C:" 是合法相对文件名,不做盘符归一)。windows 上 /tmp 是虚拟别名: cygwin 式映射 os.TempDir()(与宿主路径口径同源—— /tmp 查询 canonical 后落进临时目录便利根,无需字面 /tmp 规则行)。 导出供沙箱名单派生(OS 边界单一转换点)与嵌入方使用。
func IneffectiveRows ¶
IneffectiveRows 返回【永不生效】的行下标:first-wins 下,一旦某条更靠前的 rw 行(外层)覆盖了后续 deny/ro 行(内层)的全部匹配路径,内层行永远轮不到 求值。最常见的踩法是把 deny 写在 rw 之后——执行端按 first-wins 放行(Windows 只留一句 "deny ACE skip rw-hole" 日志),使用者很难察觉自己的 deny 形同虚设。
判定保守(宁可漏报不误报),只在能静态确定包含关系时报告:
- 两行模式相同;
- 外层以 /** 结尾:内层等于其前缀或落在其下(** 含零段,故含根目录);
- 外层无通配元字符 `* ?`(裸路径覆盖子树):内层落在其子树下。
外层模式自身含通配元字符且非 /** 尾形态时不判定(包含关系不可静态确定)。 编译不因这些行失败——整表仍合法,只是内层永不命中;调用方可在装配配置时 用它提示或拒绝(见 README「规则表」)。
func JoinPattern ¶
JoinPattern 拼接「根 + 子模式」(根为 "/" 时不产生 "//"——matchPattern 按 / 分段, 双斜杠会引入空段使模式整体失配)。空 root 返回空串(调用方丢弃)—— 2026-09-22:GOCACHE/XDG_CACHE_HOME 未设置 → 空缓存根曾把名单退化成全匹配。
func MatchPattern ¶
MatchPattern 判定 canonical 路径是否命中 glob 模式(自 fsauth match.go 移植, 语义不变):
- 支持跨段 `**`(匹配零或多段);段内 `*`(任意非分隔符字符)、`?`(单字符); `[` 按字面匹配(不支持字符类;与 CanonicalPattern 的字面前缀检测集同口径);
- 裸路径模式(无通配符)覆盖子树:pattern == path 或 path 在 pattern/ 之下;
- 斜杠语义:模式与路径统一 filepath.ToSlash(windows 反斜杠归一);
- 大小写按平台折叠:darwin/windows 文件系统默认不敏感 → fold,linux 敏感;
- 与 rg 的 glob 语义独立实现、不共代码(rg 明确不支持 `**`——两者勿混用)。
func NormalizeHost ¶
NormalizeHost 归一化主机名:小写、去尾点、IP 经 netip 归一(与 ParseEntry 同口径)。
func SetSysProcAttr ¶
SetSysProcAttr 设置子进程属性:独立进程组(killEntry 按进程组终止)。
func ValidateEntries ¶
ValidateEntries 批量校验条目合法性(set_config 显式校验用;首个非法条目即报错)。
func ValidateFSGrantTarget ¶
ValidateFSGrantTarget 校验 grant fs 目标(具体绝对路径,非模式)—— 与规则行同护栏:全域/家根/盘根不可授。
func ValidateFSRules ¶
ValidateFSRules validates the complete compilation, including variable expansion.
func ValidateTargetRules ¶
ValidateTargetRules 批量校验 net/ssh 规则行(加载/入表即报错;首个非法条目即报错)。
Types ¶
type Class ¶
type Class uint8
Class 规则行类别——仅参与调用方的行序拼接(§9.10:temp → cfg/permanent → builtin deny → 便利根),判定时不看。
type Decision ¶
type Decision struct {
Allow bool // 本操作是否放行
Matched int // 命中行序号(0 起);-1 = 未命中(兜底生效)
Effect Effect // 生效效果(命中行效果或兜底)
// Reason 动态行(Rule.Resolve)给出的说明(拒绝原因等);静态命中行与
// 兜底判定的 Reason 为空,调用方自建文案。
Reason string
}
Decision 判定结果。
type DeniedError ¶
type DeniedError struct{ Reason string }
DeniedError 策略明确拒绝(对应协议层 state=rejected):沙箱无法落实规则时 启动前拒绝执行(fail-closed),不产生裸跑。调用方按类型识别以保持拒绝语义。
func (*DeniedError) Error ¶
func (e *DeniedError) Error() string
type Effect ¶
type Effect uint8
Effect 规则行效果。
func ParseEffect ¶
ParseEffect 解析效果词(deny/ro/rw),非法返回 ok=false。
func ParseFSRule ¶
ParseFSRule 解析一条 fs 规则行:剥取行首效果前缀(deny:/ro:/rw:)+ 全域模式禁写护栏(放行类禁字面全域、家目录根与整盘根;deny 禁字面全域—— 全域姿态只用 fs_policy 表达)。
type Entry ¶
Entry 归一化目标条目(Host 小写/IP 归一,Port 数字串或 *)。
func ParseEntry ¶
ParseEntry 解析并归一化目标条目:bare host → host:*;剥 user@ 前缀; host 小写/去尾点、IP 经 netip 归一;port 须为 1-65535 或 *。 IPv6 须带括号([::1]:22);不带括号的多冒号形态按 bare host 处理(port=*)。 通配 host(含 "*")整条拒绝——归一后字符串相等匹配下 * 条目恒惰性(永不匹配), 静默接受会让用户以为生效。
func ParseTargetRule ¶
ParseTargetRule 解析 net/ssh 规则行:剥 allow:/deny: 前缀后按 Entry 归一。 Entry 解析本身拒绝通配 host,allow:* / deny:* 等全域行在此自然报错(指向 *_policy)。
type FSRuleSet ¶
type FSRuleSet struct {
Rules []Rule // 有序:从头扫、首命中生效;新规则插表头(同类插同类段头)
DefaultWrite Effect // 未命中写兜底(EffDeny | EffRW = fs_policy);读默认开放
}
FSRuleSet 文件规则集:有序表,从头扫、首命中生效。
func NewFSRuleSet ¶
NewFSRuleSet 构造规则集并对每行模式做 CanonicalPattern 归一(符号链接前缀 展开)。直接字面构造结构体时模式必须自行保证 canonical——查询路径在 Match 入口被 canonical 化,模式若不是 canonical,在 macOS /home(firmlink)、/tmp 等 symlink 前缀下两侧错位(规则形同虚设)。grant/cfg 来源的行经 CompileFSRule 编译已含此归一;程序化构造的表请用本函数。
func (FSRuleSet) MatchNoFollow ¶
MatchNoFollow 是 unlink/rename 语义的判定入口:末段符号链接不跟随(rm/mv 作用于链接本身,不触达目标),父链展开、规则表判定不变。rm/mv 类操作必须 走此入口——否则可写根内的外向链接会被无关目标路径的策略拒掉 (2026-09-22 实测 .venv 删除被阻的修复,迁移不得丢)。
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
func NewManager ¶
func NewManager() *Manager
func (*Manager) RunProcess ¶
func (m *Manager) RunProcess(ctx context.Context, opts StartOptions, stdin io.Reader, stdout, stderr io.Writer) (int, error)
RunProcess runs exactly one child process under the caller's context. stdio 全接通:stdin 供管道/载荷输入,stdout/stderr 分流写入各自 writer (双流日志契约——stderr 不混入 stdout;nil 表示丢弃对应流)。 运行期限与取消全部由 ctx 承担(exec 外层墙钟/取消句柄)。
func (*Manager) SetNoSandbox ¶
func (*Manager) SetStateDir ¶
SetStateDir 指定沙箱持久状态目录(Windows 的 deny ACE 对账状态文件所在目录; 默认 `{UserConfigDir}/vbox/sandbox`)。该状态是机器级的(同一台机器只有一份 ACE 对账记录),进程内多次设置以后者为准;只影响之后的对账写入与读取。
type NetRuleSet ¶
type NetRuleSet struct {
Rules []NetRule // 有序:从头扫、首命中生效;新规则插表头
Default bool // 未命中兜底 = net_policy open/deny
}
NetRuleSet 网络规则集:有序表,从头扫、首命中生效;新规则插表头。
func (NetRuleSet) Match ¶
func (s NetRuleSet) Match(hostport string) bool
Match 判定目标是否放行(first-wins)。目标非法(解析失败)→ false(fail-closed); 规则行解析失败跳过(编译期/入表校验应已拦截,运行时宁缺毋滥)。 端口匹配取保守交集语义:规则为 * 或相等即命中;查询为 * 时重叠即命中。
type Policy ¶
type Policy struct {
FS FSRuleSet
Net NetRuleSet
}
Policy 是一次原生进程调用的完整权限快照。规则首命中生效; 配置、会话授权和默认规则由调用方组合,平台后端只负责落实。
type Resolution ¶ added in v0.3.0
type Resolution struct {
// Effect 生效效果。
Effect Effect
// Reason 可选:给调用方的说明(如拒绝原因)。判定不看它,只用于报错/
// 日志——静态行的说明由调用方自建,动态行的情形只有回调知道(例如
// 「公开包是发布物,不可写且 grant 不可授」与「本人私有包,写需 grant」
// 的区别)。
Reason string
// Handled false = 回调不表态,落回 Rule.Effect。
Handled bool
}
Resolution 是动态行回调(Rule.Resolve)的判定结果。
type Rule ¶
type Rule struct {
Pattern string
Effect Effect
Class Class
// Resolve 可选:行命中时由回调按本次「路径 + 操作」现算效果,把「随目标
// 数量增长的一次性枚举行」换成「一行 + 按需判定」(如云 /skills/{id} 的
// 可见性:只查当前这一个 id,不枚举全库)。Handled=false = 回调不表态,
// 落回 Effect(静态部分仍可读,诊断/表渲染不受影响)。行序语义不变:前面
// 的行仍先命中、且命中本行时不再查后续行。
//
// 仅进程内匹配(Match/MatchPath/MatchNoFollow)调用回调;OS 级沙箱计划
// 只认静态 Effect,带 Resolve 的行交给计划生成会被显式拒绝
//(validateProcessPolicy)——动态判定无法下推到 seatbelt/bwrap/ACL。
Resolve func(path string, op FileOp) Resolution
}
Rule 规则行。Pattern:段内 *(任意非分隔符字符)、跨层 **(零或多段)、 ?(单字符);裸路径(无通配符)覆盖子树。Class 判定时不看。
type StartOptions ¶
type StartOptions struct {
Workdir string // 进程 cwd(空 = 继承),不授予目录权限
Exec []string // argv:Exec[0] = 程序名
// Argv0 changes argv[0] only. Confined Unix launchers reject overrides.
Argv0 *string
// Env 追加/覆盖环境变量(KEY=VALUE,同名以本字段为准):在沙箱 env
// 清洗之后应用——skill service 的 SKILLPROC_SOCKET 等协议变量经此
// 下发,保证沙箱内外都存活。ReplaceEnv=false 时 nil = 继承现状。
Env []string
// ReplaceEnv makes Env authoritative, including an intentionally empty environment.
ReplaceEnv bool
// RawOutput preserves bytes and write errors without line buffering or
// Windows code-page conversion. Protocol pipes and binary tools require it.
RawOutput bool
// NoSandbox 是免沙箱执行标记(hosts-vsh-redesign §3.3),合法来源:
// - 内部管控调用方(ssh/scp、browser,由执行环境自身管控);
// - 外部请求显式携带 nosandbox(发送前审批;审批本身不免沙箱,
// 仅放行该标记);
// - 全局 no_sandbox 配置(Manager.NoSandbox)。
NoSandbox bool
Policy Policy // 文件与网络权限的唯一输入
}