Help us shape the future of Go! Take the 2026 Go Developer Survey

vbox

package module
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 31 Imported by: 0

README

vbox

Go 原生子进程沙箱。 在真实宿主上启动一个子进程,它「能读什么、能写什么、能连哪里」由 一张有序规则表表达,并交给内核兜底执行。

中文 | English

code, err := mgr.RunProcess(ctx, vbox.StartOptions{
	Exec:    []string{"/usr/bin/curl", "-sS", "https://api.example.com/x"},
	Workdir: "/srv/work",
	Policy:  policy, // 有序 fs/net 规则快照
}, nil, os.Stdout, os.Stderr)

vbox 只做一件事:拿到你给的规则快照,让操作系统为一个子进程落实它。配置解析、审批、 授权生命周期、审计都在调用方——vbox 不读你的配置,也不替你决定策略。

本文描述现行规则契约;Manager 快照缓存与 darwin deny 姿态内建出站代理已经实现, 实机验收与后续工作见 docs/development/todo.md。

它能给你什么

  • 一张有序规则表,level + 原表序 生效:高 level 先匹配,同 level 按原表首命中; 没有「更具体的规则胜」或隐式覆盖层,Class 不推导 level。
  • 内核执行,不是模拟:macOS Seatbelt(SBPL)、Linux bubblewrap、Windows 受限令牌 + 显式拒绝 ACE。纯 Go、无 cgo(只依赖 golang.org/x/sys 与 x/text)。
  • fail-closed:本平台无法表达的规则在启动前就被拒绝;没有可用沙箱后端时报错且 命令不执行——绝不静默裸跑。
  • 自带进程托管:context 取消整棵进程树、输出捕获(文本或原始字节)、环境清洗、 资源限制、可选的阶段计时日志。
  • 只判定不启动:同一张表能当纯函数回答「这个进程能不能写 /srv/work/out.txt」, 便于预检与测试。

安装

go get github.com/veypi/vbox

所需 Go 版本见 go.mod。

快速开始

rules, err := vbox.CompileFSRules([]string{
	"ro:~/.ssh/known_hosts", // 只读
	"deny:**/.ssh/**",       // 读写双拒(上面那行已把 known_hosts 变成例外)
	"rw:/srv/work/**",       // 读写
}, vbox.ClassCfg)
if err != nil {
	return err // 本平台不可表达:拒绝,而不是放宽
}

policy := vbox.Policy{
	FS: vbox.NewFSRuleSet(rules, vbox.EffDeny), // 未命中写兜底 = deny(读默认开放)
	Net: vbox.NetRuleSet{
		Rules:   []vbox.NetRule{{HostPort: "api.example.com:443", Allow: true}},
		Default: false, // deny 姿态;逐条规则的落地口径见「网络规则落地」
	},
}

mgr := vbox.NewManager()
defer mgr.Close(ctx) // 终止所有在跑的子进程

code, err := mgr.RunProcess(ctx, vbox.StartOptions{
	Exec:    []string{"/usr/bin/curl", "-sS", "https://api.example.com/x"},
	Workdir: "/srv/work",
	Policy:  policy,
}, nil, os.Stdout, os.Stderr)

只想判定、不启动进程时,直接问规则集:

d := policy.FS.Match("/srv/work/out.txt", vbox.OpWrite)
// d.Allow、d.Matched(命中行号,-1 = 走兜底)、d.Effect、d.Level

allow := policy.Net.Match("api.example.com:443")

Match 跟随末段符号链接(open/connect 语义);MatchNoFollow 用于 unlink/rename (作用于链接本身)。MatchPath 接收已归一化的路径,不碰宿主文件系统。

规则表

行首每个 ! 增加一级 level(0–255),随后是三种效果前缀;例如 !!deny:/secret 是 level 2 的拒绝行。Class 不推导 level。

三种效果前缀:

前缀 含义
deny: 读写双拒
ro: 读放行、写拒
rw: 读写放行

模式:裸路径覆盖整棵子树(/srv/work 覆盖 /srv/work/a/b);* 匹配段内任意字符、 ? 匹配单字符、** 匹配零或多段(所以 /etc/ssh/** 连 /etc/ssh 自身也匹配)。 匹配先比较 level:高 level 优先;同 level 按原表顺序扫描,首命中生效。

Class(ClassTemp / ClassCfg / ClassBuiltin / ClassConvenience)只用于调用方把 不同来源的行拼成稳定顺序(temp grant → 配置 → 出厂内建 → 便利根);判定本身不看 Class,也不从 Class 推导 level。

两个容易踩的点

优先级陷阱:被压住的行是安静的。 如果更高优先级的 rw: 行已经覆盖了其他 deny: / ro: 目标,后者永远轮不到求值,执行端会直接放行。高 level 行优先,同 level 前行优先。 装配期用 IneffectiveRows(rules) 拿到「被更高优先级 rw 行完全覆盖」的行下标—— 判定保守(只报能静态证明的包含关系),编译本身不因死行失败。注入 Manager.SetLogf 时, 执行路径也会打一条 shadowed by a higher-priority rw row 日志。

规则表请用 NewFSRuleSet / CompileFSRules 构造,不要用结构体字面量。 字面构造的 FSRuleSet 必须自带 canonical 模式:Match 入口只归一化路径侧,模式用了别的拼写就会 两侧错位、行形同虚设——在 Windows 上这会把可写根误判成只读目标,子进程连自己的工作区都 写不了。两个构造入口都会替你归一模式(CompileFSRules 另外做变量展开,并把本机无法 执行的规则直接拒绝)。

平台支持

平台 状态
macOS(arm64 / amd64) 支持:Seatbelt 后端;CI 构建 + 全量测试
Linux(amd64 / arm64) 支持:bubblewrap 后端(需 bwrap 与可用 user namespace);CI 构建 + 全量测试
Windows(amd64 / arm64) 支持:受限令牌 + 显式拒绝 ACE;CI 构建,ACL 用例需实机
freebsd / netbsd / openbsd / dragonfly / solaris / illumos / aix / android 未承诺:可编译,无内核沙箱后端(fail-closed),未做行为验证
plan9 / js / wasip1 不支持:缺进程/信号/沙箱语义,构建在包边界显式失败

未列出的平台按「未承诺」处理。同一张表在各平台如何落地:

平台 机制 说明
macOS Seatbelt(SBPL profile) 按 level + 原表序 的统一优先级逆序写出(SBPL 后规则胜);ro 行同时显式下沉写拒,可写根内的只读洞在内核真实生效
Linux bubblewrap 整机只读视图 + 终局可写根绑定;可实例化目标的 ro/rw 洞按终局判定回补;需要 bwrap 与 user namespace 可用
Windows 受限令牌 + 显式拒绝 ACE 句柄版 ACL(\\?\ 长路径、Get/SetSecurityInfo);终局 rw 目标跳过 ACE,deny 与独立 ro 行落拒绝 ACE;有效嵌套只读洞无法完整落实时明确报不支持
其他 无内核沙箱 明确报错,不静默裸跑
网络规则落地

net 域的落地能力与 fs 不同,按「姿态 + 逐条」分开说:

平台 姿态(Default) 逐条规则
macOS deny 由 Seatbelt 全断姿态 + 内建出站代理共同落实;open 保持原行为 loopback 由 Seatbelt 逐条落实;deny 下非 loopback 规则由内建 HTTP/SOCKS5 代理按完整快照落实;open 下不可落实的选择性 deny 仍拒启
Linux 内核落实(deny = --unshare-net 全断,新 netns 的 loopback 未配置,回环同样不可达) 不可表达
Windows 仅 open;deny 姿态整体拒启 不可表达

不可内核落实的行按方向处理,永不比策略更宽:

  • deny 行:跳过它沙箱就比策略宽——启动前拒绝(fail-closed)。
  • allow 行:Linux deny 姿态下可被吸收(目标在该姿态的沙箱里本不可达,收窄与 fail-closed 同向);darwin deny 姿态下非 loopback allow 改由代理真实执行,不再 被当成不可达行吸收。open 姿态下无法落地时,省略 allow 只会收窄。darwin 的 loopback 有效胜出行则由内核真实落地。
  • 没有执行层标记:规则只描述子进程的 FS/NET 目标,不接受执行层前缀或字段。 当前后端无法完整落实有效规则时,明确返回「不支持」并拒绝启动;不过滤规则、 不改道,也不以其他执行层代替本库落地。

net 规则模式支持严格子域通配 *.example.com(不匹配裸域或 IP);localhost、 .localhost 子名与回环 IP 归一为同一类,*.localhost 匹配该类。

启动前预检用 CheckNetPolicy(platform, rules):逐条返回 Kernel / Proxy / Absorbed / Reserved 分类。语法错误与 UnsupportedError 是两类错误;能力不支持 只拒绝依赖该能力的进程启动,不改变规则语义。静态预检与 darwin Seatbelt 输出共用 compileNetPlan。

darwin 内建出站代理

仅在 confined && darwin && NetRuleSet.Default == false 时启动;open 与 NoSandbox 不创建 endpoint、不注入代理环境。代理监听两个仅 loopback 的端口,分别提供 HTTP CONNECT / plain HTTP absolute-form 与 SOCKS5 CONNECT。每个 endpoint 使用独立随机 认证 token;只转发 TCP,不终止 TLS、不支持 UDP。

  • 启动计划为两个 transport 端口生成最高优先级 Reserved loopback 放行;最终目标仍按 本次完整规则快照判定,loopback 直连继续由 Seatbelt 规则决定。
  • HTTP_PROXY / HTTPS_PROXY / ALL_PROXY 及小写形式由 vbox 最后强制设置,ALL_PROXY 使用 socks5h://;NO_PROXY / no_proxy 合并并包含 127.0.0.1,localhost,::1, 调用方 Env 不能覆盖内部 endpoint。
  • NetRuleSet.UpstreamProxy 可显式指定 http://、socks5:// 或 socks5h:// 上游; 该值进入 netKey,绝不隐式继承宿主 ambient proxy。
  • 域名先解析再按每个具体 IP 判定;内网、回环、link-local、metadata、本机接口等地址 不接受域名行放行,只能由匹配的 IP/loopback 行放行。下游连接只使用已授权 IP,避免 上游再次解析域名造成 DNS 重绑定。
  • endpoint 按完整 netKey 在 Manager 内单飞复用,进程结束 release;默认空闲宽限期 30s,活跃请求/隧道不被空闲驱逐,Manager.Close 关闭 listener、连接与 handler。

代理 token 不是同 UID 恶意进程之间的隔离边界;能读取其他进程环境的同 UID 进程仍可能 获得它。需要更强隔离时必须使用独立 OS 用户或独立 broker。

Windows 侧为拒绝 ACE 的幂等对账保留一份状态文件(默认 {UserConfigDir}/vbox/sandbox/deny_acl_state.json,可用 Manager.SetStateDir 指定), 并使用确定性派生的能力 SID —— 因此 ACE 跨调用/重启稳定,宿主机其他进程不受影响。

可写根授不上 ACL 只降级该根,不中断 exec。 一个可写根可能授权时还不存在(spawn 时由存在性过滤),也可能存在却授不上——典型是属主为 Administrators / SYSTEM 而 当前用户只有 Modify(Modify 不含改安全描述符的权限)。此时该根在沙箱内只读,日志 按目录告警一次(固定标记 write-root unavailable in sandbox),同一次执行的其余可写 根与进程本身照常;每次调用仍会重试授权,ACL 修好或目录重建后自动恢复可写。授权侧 可以在授权那一刻用 CheckFSGrantTarget(abs)(要求原生态路径)预判同一件事。 反方向不降级:deny 目标无法落实时仍然拒绝执行——沙箱绝不允许比策略更宽。

进程托管

RunProcess 是唯一入口(Manager.Close 统一终止在跑进程):

  • 环境:默认在清洗后的宿主环境上应用 Env 覆盖;ReplaceEnv 让 Env 成为完整 环境(空列表 = 空环境)。
  • Argv0:只改展示名,不参与路径查找或权限身份;Unix 沙箱包装不支持时明确报错。
  • RawOutput:保留 stdout/stderr 原始字节与写错误,关闭 Windows 文本转码与行缓冲, 供协议管道与二进制命令使用;默认文本模式只刷新自己创建的包装器,不关闭调用方的 writer。
  • Workdir:只设 cwd,不授予目录权限。
  • 取消与期限:全部由 ctx 承担;取消会终止整个进程树(Windows 走 Job Object)。
  • 日志:SetLogf 注入沙箱/执行阶段计时日志(含上面那条死行提示);不注入则全链静默。
  • 状态目录:SetStateDir(dir) 指定持久状态目录(Windows deny ACE 对账状态文件; 默认 {UserConfigDir}/vbox/sandbox)。

不在这里的东西

配置解析、默认规则源、会话授权、grant 生命周期、审批、命令注册和调度都不在本库。 vbox 只接收 FS/NET 快照、做判定并落实原生进程沙箱,不描述或依赖任何特定调用方。

测试

go test ./...
go vet ./...

沙箱用例依赖真实平台能力:macOS 需要 Seatbelt、Linux 需要 bwrap + user namespace、 Windows 的 ACL 用例需要真实令牌与对象句柄。缺少能力时用例会带原因自己跳过—— 跨平台编译通过不等于实机行为已验证。

文档、版本与许可

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 各一张有序表,按 level + 原表序求值:高 level 优先, 同 level 首命中生效。行首可用 ! 增加 level,随后是效果前缀:文件域 ro:/rw:/deny:,网络域 allow:/deny:(host:port)。Class 只用于调用方拼接, 不推导 level,也不参与判定。
  • 判定:纯函数匹配(MatchPath / Match / MatchNoFollow / NetRuleSet.Resolve), 不访问宿主文件系统,也不读配置。
  • 落地:把编译好的快照交给内核机制执行;当前平台无法表达的规则在启动前拒绝 (fail-closed),绝不静默降级为裸跑。RunProcess 是唯一进程入口。

配置解析、默认规则源、会话授权、grant 生命周期与审批策略都属调用方(宿主集成层); vbox 每次调用接收一份编译好的快照,不维护授权状态。Windows 侧仅为拒绝 ACE 的 幂等对账保留一份状态文件(默认 `{UserConfigDir}/vbox/sandbox/deny_acl_state.json`, 可用 Manager.SetStateDir 改)。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Canonical

func Canonical(p string) string

Canonical 展开符号链接到真实文件系统身份:EvalSymlinks 逐级向父目录回退 (目标不存在时展开最近存在祖先,剩余路径原样拼接——写新文件场景)。 输出为 /c/ 规范形(posix 为 posix 形)。 递归到文件系统根基(/ 或 C:\)时直接返回原路径:根基无可展开项,且 TrimSuffix 根基分隔符得空串/盘符,继续递归会退化成相对路径。

func CanonicalNetHost added in v0.3.4

func CanonicalNetHost(host string) string

CanonicalNetHost 归一化主机名,并把 localhost、.localhost 子名和回环 IP 归入同一个 loopback 类。规则模式 *.localhost 保留为该类的通配形式。

func CanonicalNoFollow

func CanonicalNoFollow(p string) string

CanonicalNoFollow 展开父目录符号链接、保留末段字面形(unlink/rename 语义: 系统调用作用于链接本身,判定必须同口径)。父链复用 Canonical(含最近存在 祖先回退);末段为根/退化形态时退回跟随版。输出同为 /c/ 规范形。

func CanonicalPattern

func CanonicalPattern(pat string) string

CanonicalPattern 对 glob 模式的字面前缀段(首个含通配符段之前)做 canonical 展开:/var/tmp/x/** → /private/var/tmp/x/**(macOS /var symlink)。 纯字面模式(无通配符):整串展开;** 开头的模式无前缀可展开(原样返回)。

func CheckFSGrantTarget added in v0.3.1

func CheckFSGrantTarget(string) error

CheckFSGrantTarget(linux):恒 nil——bwrap 在挂载层落实可写根(bindIfExists), 不存在「能不能授上写权限」这一前置条件;不存在的根本就被静默跳过。

已知不对称:unix 上给一个当前用户不可写的目录 grant fs 同样是空授权(工具 层与子进程都写不了),但判据与 Windows 不同——unix 上不存在「能改 ACL 就能 在沙箱内写」的等价权限,且「目录不可写、其中文件可写」的授权仍有意义 (改文件内容不需要目录写权限),故不在此做硬判定。

func DedupClean

func DedupClean(roots []string) []string

DedupClean 去空去重,保序。

func DualForms

func DualForms(p string) []string

DualForms 返回路径的 canonical 形与原始字面形(去尾斜杠;相同则只给一个)。 沙箱(seatbelt)按系统调用实际传入的路径串匹配规则:macOS 的 /tmp、/etc、 $TMPDIR(/var/folders/…) 都是 symlink 前缀,只留 canonical 形会让字面拼写的 访问(含路径解析的 metadata 读)被拒(2026-09-22)。

func DualList

func DualList(paths []string) []string

DualList 对一组路径逐个展开 DualForms(去重交给 DedupClean)。

func EntryMatch

func EntryMatch(r, q Entry) bool

EntryMatch 判定规则模式 r 是否覆盖具体目标 q,并应用端口保守交集语义。

func ExpandVars

func ExpandVars(s string) (string, bool)

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

func HostPathToOS(p string) string

hostPathToOS 把 /c/ 规范形(及旧输入形)转回 OS 原生路径(windows;posix 恒等——"C:" 是合法相对文件名,不做盘符归一)。windows 上 /tmp 是虚拟别名: cygwin 式映射 os.TempDir()(与宿主路径口径同源—— /tmp 查询 canonical 后落进临时目录便利根,无需字面 /tmp 规则行)。 导出供沙箱名单派生(OS 边界单一转换点)与嵌入方使用。

func IneffectiveRows

func IneffectiveRows(rules []Rule) []int

IneffectiveRows 返回【永不生效】的行下标:一条更高优先级的 rw 行覆盖 deny/ro 行的全部匹配路径时,后者永远轮不到求值。优先级为高 level 在前, 同 level 才比较原表序。最常见的踩法是把 deny 写在 rw 之后——执行端按优先级放行(Windows 只留一句 "deny ACE skip rw-hole" 日志),使用者很难察觉自己的 deny 形同虚设。

判定保守(宁可漏报不误报),只在能静态确定包含关系时报告:

  • 两行模式相同;
  • 外层以 /** 结尾:内层等于其前缀或落在其下(** 含零段,故含根目录);
  • 外层无通配元字符 `* ?`(裸路径覆盖子树):内层落在其子树下。

外层模式自身含通配元字符且非 /** 尾形态时不判定(包含关系不可静态确定)。 编译不因这些行失败——整表仍合法,只是内层永不命中;调用方可在装配配置时 用它提示或拒绝(见 README「规则表」)。

func IsUnsupported added in v0.3.4

func IsUnsupported(err error) bool

IsUnsupported reports whether err describes a backend capability gap.

func JoinPattern

func JoinPattern(root, sub string) string

JoinPattern 拼接「根 + 子模式」(根为 "/" 时不产生 "//"——matchPattern 按 / 分段, 双斜杠会引入空段使模式整体失配)。空 root 返回空串(调用方丢弃)—— 2026-09-22:GOCACHE/XDG_CACHE_HOME 未设置 → 空缓存根曾把名单退化成全匹配。

func MatchPattern

func MatchPattern(pattern, cpath string) bool

MatchPattern 判定 canonical 路径是否命中 glob 模式(自 fsauth match.go 移植, 语义不变):

  • 支持跨段 `**`(匹配零或多段);段内 `*`(任意非分隔符字符)、`?`(单字符); `[` 按字面匹配(不支持字符类;与 CanonicalPattern 的字面前缀检测集同口径);
  • 裸路径模式(无通配符)覆盖子树:pattern == path 或 path 在 pattern/ 之下;
  • 斜杠语义:模式与路径统一 filepath.ToSlash(windows 反斜杠归一);
  • 大小写按平台折叠:darwin/windows 文件系统默认不敏感 → fold,linux 敏感;
  • 与 rg 的 glob 语义独立实现、不共代码(rg 明确不支持 `**`——两者勿混用)。

func NormalizeHost

func NormalizeHost(host string) string

NormalizeHost 归一化主机名:小写、去尾点、IP 经 netip 归一。

func ScrubEnv

func ScrubEnv(env []string) []string

ScrubEnv 剥离名字命中敏感标记的环境变量,返回可传给沙箱子进程的环境。 PATH/HOME/LANG 等工具链必需变量天然不命中标记,无需显式保留表。

func SetSysProcAttr

func SetSysProcAttr(cmd *exec.Cmd)

SetSysProcAttr 设置子进程属性:独立进程组(killEntry 按进程组终止)。

func ValidateEntries

func ValidateEntries(list []string) error

ValidateEntries 批量校验具体目标(不接受通配或 level)。

func ValidateFSGrantTarget

func ValidateFSGrantTarget(abs string) error

ValidateFSGrantTarget 校验 grant fs 目标(具体绝对路径,非模式)—— 与规则行同护栏:全域/家根/盘根不可授。

func ValidateFSRules

func ValidateFSRules(list []string) error

ValidateFSRules validates the complete compilation, including variable expansion.

func ValidateTargetRules

func ValidateTargetRules(list []string) error

ValidateTargetRules 批量校验完整规则行(加载/入表即报错)。

func ValidateUpstreamProxy added in v0.3.4

func ValidateUpstreamProxy(raw string) error

ValidateUpstreamProxy reports whether raw is a supported explicit upstream proxy URL.

Types

type Class

type Class uint8

Class 规则行类别——仅参与调用方的行序拼接(§9.10:temp → cfg/permanent → builtin deny → 便利根),判定时不看。

const (
	ClassTemp        Class = iota // session grant(构建时插表头)
	ClassCfg                      // cfg / permanent
	ClassBuiltin                  // 出厂内建(deny 表)
	ClassConvenience              // 便利根(workspace / 会话目录等)
)

func (Class) String

func (c Class) String() string

type Decision

type Decision struct {
	Allow   bool   // 本操作是否放行
	Matched int    // 命中行序号(0 起);-1 = 未命中(兜底生效)
	Effect  Effect // 生效效果(命中行效果或兜底)
	Level   uint8  // 命中行 level;兜底为 0
	// 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 规则行效果。

const (
	EffDeny Effect = iota // 读写双拒
	EffRO                 // 读放写拒
	EffRW                 // 读写
)

func ParseEffect

func ParseEffect(s string) (Effect, bool)

ParseEffect 解析效果词(deny/ro/rw),非法返回 ok=false。

func ParseFSRule

func ParseFSRule(raw string) (Effect, string, uint8, error)

ParseFSRule 解析一条 fs 规则行:剥取行首 level(!)与效果前缀 (deny:/ro:/rw:),再执行全域模式禁写护栏。level 超过 255 直接报错。

func (Effect) String

func (e Effect) String() string

type Entry

type Entry struct {
	Host string
	Port string
}

Entry 归一化目标条目(Host 小写/IP/loopback 归一,Port 数字串或 *)。 规则模式侧允许最左侧完整标签通配(*.example.com)。

func ParseEntry

func ParseEntry(s string) (Entry, error)

ParseEntry 解析一个具体目标:禁止通配、level、空白与控制字符。 bare host 的端口为 *;user@ 前缀用于 SSH 目标。多冒号形态必须是合法 IPv6。

func ParseRuleEntry added in v0.3.4

func ParseRuleEntry(s string) (Entry, error)

ParseRuleEntry 解析权限表中的目标模式。只有最左侧完整标签可以是 *; 不接受 level 或效果前缀,那些由 ParseNetRule 处理。

func ParseTargetRule

func ParseTargetRule(raw string) (allow bool, e Entry, err error)

ParseTargetRule 解析具体目标规则,不接受 level 或通配。

func (Entry) String

func (e Entry) String() string

String 返回规范形态 host:port。

type FSRuleSet

type FSRuleSet struct {
	Rules        []Rule // 原表序在同 level 内保留;Class 不改变优先级
	DefaultWrite Effect // 未命中写兜底(EffDeny | EffRW = fs_policy);读默认开放
	// Err 是快照来源的配置错误。非 nil 时读写都 fail-closed,且原生进程
	// 必须在任何 OS 规则落地前拒绝启动;不能用空规则代替这个状态。
	Err error
}

FSRuleSet 文件规则集:高 level 优先,同 level 按原表序首命中。

func NewFSRuleSet

func NewFSRuleSet(rules []Rule, defaultWrite Effect) FSRuleSet

NewFSRuleSet 构造规则集并对每行模式做 CanonicalPattern 归一(符号链接前缀 展开)。直接字面构造结构体时模式必须自行保证 canonical——查询路径在 Match 入口被 canonical 化,模式若不是 canonical,在 macOS /home(firmlink)、/tmp 等 symlink 前缀下两侧错位(规则形同虚设)。grant/cfg 来源的行经 CompileFSRule 编译已含此归一;程序化构造的表请用本函数。

func (FSRuleSet) Match

func (s FSRuleSet) Match(path string, op FileOp) Decision

Match 判定 canonical 化后的 path 上 op 是否放行(跟随末段符号链接—— open/connect 语义)。

func (FSRuleSet) MatchNoFollow

func (s FSRuleSet) MatchNoFollow(path string, op FileOp) Decision

MatchNoFollow 是 unlink/rename 语义的判定入口:末段符号链接不跟随(rm/mv 作用于链接本身,不触达目标),父链展开、规则表判定不变。rm/mv 类操作必须 走此入口——否则可写根内的外向链接会被无关目标路径的策略拒掉 (2026-09-22 实测 .venv 删除被阻的修复,迁移不得丢)。

func (FSRuleSet) MatchPath

func (s FSRuleSet) MatchPath(cpath string, op FileOp) Decision

MatchPath evaluates an already normalized path without consulting the host filesystem.

func (FSRuleSet) Resolve

func (s FSRuleSet) Resolve(path string) Decision

Resolve 返回路径的表判定(诊断/ explain 用):命中行序号与效果。

type FileOp

type FileOp uint8

FileOp 文件操作类别。

const (
	OpRead  FileOp = iota
	OpWrite        // 含创建/修改/删除/重命名/追加——unlink/rename 判定走 MatchNoFollow
)

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

func NewManager

func NewManager() *Manager

func (*Manager) Close

func (m *Manager) Close(ctx context.Context) error

Close 终止全部仍在运行的子进程并等待退出。

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) SetLogf

func (m *Manager) SetLogf(fn func(string, ...any))

SetLogf 注入沙箱/执行阶段计时日志(pod 侧接 host 的 OnLog;nil 忽略)。 不注入则全链路静默(测试与库内直用)。

func (*Manager) SetNoSandbox

func (m *Manager) SetNoSandbox(value bool)

func (*Manager) SetStateDir

func (m *Manager) SetStateDir(dir string)

SetStateDir 指定沙箱持久状态目录(Windows 的 deny ACE 对账状态文件所在目录; 默认 `{UserConfigDir}/vbox/sandbox`)。该状态是机器级的(同一台机器只有一份 ACE 对账记录),进程内多次设置以后者为准;只影响之后的对账写入与读取。

type NetCaps added in v0.3.4

type NetCaps struct {
	DenyPosture   bool
	LoopbackRules bool
	EgressProxy   bool
}

NetCaps describes the network enforcement capabilities of one backend.

type NetRowLanding added in v0.3.2

type NetRowLanding struct {
	Rule     NetRule
	Kernel   bool // 由内核逐条规则落实
	Proxy    bool // 由出站代理按完整快照落实
	Absorbed bool // 死行、冗余行,或省略后只会收窄的规则
	Reserved bool // 内部 transport 保留项,不对应调用方规则
	Reason   string
}

NetRowLanding 描述一条原始规则在启动计划中的落地方式。

func CheckNetPolicy added in v0.3.2

func CheckNetPolicy(platform string, s NetRuleSet) ([]NetRowLanding, error)

CheckNetPolicy 用平台的真实 NetCaps 预检完整快照,返回与原始行序一致的 Kernel / Proxy / Absorbed / Reserved 诊断。语法错误与 UnsupportedError 分开; 一个仍然有效的 deny 无法落实时拒绝启动,allow 省略后只会收窄时可以吸收。

darwin 的 loopback 投影按 level + 原表序求每个具体端口及其他端口代表集的 真实胜者;所有胜者进入内核计划,非胜者吸收。静态预检和 Seatbelt 输出共用 compileNetPlan,不再把混合拼写或冲突本身当作错误。

type NetRule

type NetRule struct {
	HostPort string
	Allow    bool
	Level    uint8
}

NetRule 网络规则行。HostPort 形如 "example.com:443",端口可 *。

func ParseNetRule added in v0.3.2

func ParseNetRule(raw string) (NetRule, error)

ParseNetRule 解析完整 net/ssh 规则:行首 ! 增加 level,随后是 allow:/deny: 与目标模式。level 最多 255;旧配置不做兼容解析。

func (NetRule) String added in v0.3.2

func (r NetRule) String() string

String 返回包含 level 的规范形态,供日志/回执展示。

type NetRuleSet

type NetRuleSet struct {
	Rules   []NetRule
	Default bool // 未命中兜底 = net_policy open/deny
	// UpstreamProxy is the optional explicit upstream HTTP/SOCKS5 proxy URL.
	// It is part of the policy snapshot; ambient process proxy variables are ignored.
	UpstreamProxy string
}

NetRuleSet 网络规则集:高 level 优先,同 level 按原表序首命中。

func (NetRuleSet) Match

func (s NetRuleSet) Match(hostport string) bool

Match 判定目标是否放行。非法目标或规则行均 fail-closed。 端口匹配取保守交集语义:规则为 * 或相等即命中;查询为 * 时重叠即命中。

func (NetRuleSet) Resolve

func (s NetRuleSet) Resolve(hostport string) (allow bool, matched int)

Resolve reports the highest-priority matching row, or -1 for the default/invalid target.

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
	Level   uint8 // 高 level 优先;同 level 保持原表序,Class 不推导 Level
	// 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 判定时不看。

func CompileFSRules

func CompileFSRules(raws []string, class Class) ([]Rule, error)

CompileFSRules compiles an ordered table. Both resolved and literal path spellings belong to the same row and retain its priority. Invalid rows reject the entire table; dropping a deny row would widen permissions.

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 是免沙箱执行标记(契约见 aic-pod/docs/reference/host-sandbox.md),合法来源:
	//   - 内部管控调用方(ssh/scp、browser,由执行环境自身管控);
	//   - 外部请求显式携带 nosandbox(发送前审批;审批本身不免沙箱,
	//     仅放行该标记);
	//   - 全局 no_sandbox 配置(Manager.NoSandbox)。
	NoSandbox bool
	Policy    Policy // 文件与网络权限的唯一输入
}

type UnsupportedError added in v0.3.4

type UnsupportedError struct{ Reason string }

UnsupportedError reports a valid policy that the selected backend cannot enforce faithfully. It is distinct from a rule syntax error.

func (*UnsupportedError) Error added in v0.3.4

func (e *UnsupportedError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL