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

vbox

package module
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 22 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 不读你的配置,也不替你决定策略。

它能给你什么

  • 一张有序规则表,首命中生效:行序就是策略,没有覆盖层、没有「更具体的规则胜」; 新规则插表头。
  • 内核执行,不是模拟: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

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

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

规则表

三种效果前缀:

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

模式:裸路径覆盖整棵子树(/srv/work 覆盖 /srv/work/a/b);* 匹配段内任意字符、 ? 匹配单字符、** 匹配零或多段(所以 /etc/ssh/** 连 /etc/ssh 自身也匹配)。 表从头扫到尾,首命中生效。

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

两个容易踩的点

行序陷阱:被压住的行是安静的。 如果靠前的 rw: 行已经覆盖了靠后的 deny: / ro: 目标,后者永远轮不到求值,执行端会直接放行。装配期用 IneffectiveRows(rules) 拿到 「被上方 rw 行完全覆盖」的行下标——判定保守(只报能静态证明的包含关系),编译本身 不因死行失败。注入 Manager.SetLogf 时,执行路径也会打一条 shadowed by an earlier 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) 按 first-wins 快照逆序写出(SBPL 后规则胜 ⟺ 首命中胜);ro 行同时显式下沉写拒,可写根内的只读洞在内核真实生效
Linux bubblewrap 整机只读视图 + 白名单写绑定;可实例化目标的 ro/rw 洞按行序回补;需要 bwrap 与 user namespace 可用
Windows 受限令牌 + 显式拒绝 ACE 句柄版 ACL(\\?\ 长路径、Get/SetSecurityInfo);终局 rw 目标跳过 ACE,deny 与独立 ro 行落拒绝 ACE;嵌套只读洞只在工具层生效
其他 无内核沙箱 明确报错,不静默裸跑
网络规则落地

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

平台 姿态(Default) 逐条规则
macOS 内核落实(deny = 全断) 仅 loopback 逐条落实(Seatbelt remote tcp);其余 deny 行拒启
Linux 内核落实(deny = --unshare-net 全断,新 netns 的 loopback 未配置,回环同样不可达) 不可表达
Windows 仅 open;deny 姿态整体拒启 不可表达

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

  • deny 行:跳过它沙箱就比策略宽——启动前拒绝(fail-closed)。
  • allow 行:在 deny 姿态下被吸收(目标在该姿态的沙箱里本不可达,收窄与 fail-closed 同向),记日志不拒启;在 open 姿态下本就是 no-op(无 deny 行 时不收紧)。
  • tool: 前缀:tool:deny:169.254.169.254:* 声明该行仅由调用方的工具层 (进程内 HTTP/MCP 客户端等)执行,内核落地一概跳过、不参与「能否落实」判定 ——用于平台无法落实但工具层可执行的规则。判定入口(Match/Resolve)不看 ToolOnly,工具层照常命中。

启动前预检用 CheckNetPolicy(platform, rules):逐条返回 landed / absorbed(含 no-op 区分)分类,遇到「落实不了、跳过又会变宽」的行报错——调用方可在配置/ 授权那一刻就失败,而不是埋到 exec 期。

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 只接收快照、 做判定并落地。参考集成:aic-pod 的 libs/host 作为外部使用者负责配置与授权,每次调用编译快照交给 vbox。

测试

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 各一张有序表,从前向后扫描、首命中生效(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

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 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:host 字符串相等(双方已归一) 且端口匹配(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 返回【永不生效】的行下标:first-wins 下,一旦某条更靠前的 rw 行(外层)覆盖了后续 deny/ro 行(内层)的全部匹配路径,内层行永远轮不到 求值。最常见的踩法是把 deny 写在 rw 之后——执行端按 first-wins 放行(Windows 只留一句 "deny ACE skip rw-hole" 日志),使用者很难察觉自己的 deny 形同虚设。

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

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

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

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 归一(与 ParseEntry 同口径)。

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 批量校验条目合法性(set_config 显式校验用;首个非法条目即报错)。

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 批量校验 net/ssh 规则行(加载/入表即报错;首个非法条目即报错)。 接受 tool: 前缀(ParseNetRule 口径)。

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 // 生效效果(命中行效果或兜底)
	// 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, error)

ParseFSRule 解析一条 fs 规则行:剥取行首效果前缀(deny:/ro:/rw:)+ 全域模式禁写护栏(放行类禁字面全域、家目录根与整盘根;deny 禁字面全域—— 全域姿态只用 fs_policy 表达)。

func (Effect) String

func (e Effect) String() string

type Entry

type Entry struct {
	Host string
	Port string
}

Entry 归一化目标条目(Host 小写/IP 归一,Port 数字串或 *)。

func ParseEntry

func ParseEntry(s string) (Entry, error)

ParseEntry 解析并归一化目标条目:bare host → host:*;剥 user@ 前缀; host 小写/去尾点、IP 经 netip 归一;port 须为 1-65535 或 *。 IPv6 须带括号([::1]:22);不带括号的多冒号形态按 bare host 处理(port=*)。 通配 host(含 "*")整条拒绝——归一后字符串相等匹配下 * 条目恒惰性(永不匹配), 静默接受会让用户以为生效。

func ParseTargetRule

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

ParseTargetRule 解析 net/ssh 规则行:剥 allow:/deny: 前缀后按 Entry 归一。 Entry 解析本身拒绝通配 host,allow:* / deny:* 等全域行在此自然报错(指向 *_policy)。 tool: 前缀请用 ParseNetRule。

func (Entry) String

func (e Entry) String() string

String 返回规范形态 host:port。

type FSRuleSet

type FSRuleSet struct {
	Rules        []Rule // 有序:从头扫、首命中生效;新规则插表头(同类插同类段头)
	DefaultWrite Effect // 未命中写兜底(EffDeny | EffRW = fs_policy);读默认开放
}

FSRuleSet 文件规则集:有序表,从头扫、首命中生效。

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 NetRowLanding added in v0.3.2

type NetRowLanding struct {
	Rule NetRule
	// Landed true = 由内核沙箱落实(姿态或逐条)。
	Landed bool
	// Noop true = 本行被落地姿态吸收且语义不变(open 姿态下的 allow、deny 姿态
	// 下的 deny、与可落地 deny 行不相交的 allow)——既未放宽也未收紧。
	// Landed=false 且 Noop=false 时本行语义被收窄(目标在沙箱内本不可达),
	// Reason 必读。
	Noop   bool
	Reason string
}

NetRowLanding 单条 net 规则在指定平台上的内核落地分类(CheckNetPolicy 的输出)。

func CheckNetPolicy added in v0.3.2

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

CheckNetPolicy 按平台能力对整表 net 规则做启动前分类:逐条给出 landed / absorbed 结论;遇到第一条「落实不了、跳过又会比策略更宽」的规则即整体失败 (error 与 validateProcessPolicy 同口径)。ToolOnly 行不参与内核落地,直接 跳过(不出现在返回值里,也不触发失败)。

落地能力矩阵(逐条规则):darwin 落实 loopback 逐条(Seatbelt remote tcp); linux 只落实姿态(deny = --unshare-net 全断,沙箱 netns 的 loopback 未配置, 回环亦不可达);windows 无网络能力(deny 姿态整体报错)。未被姿态覆盖又不 可逐条落实的行:deny 行一律报错(跳过即变宽);allow 行在 deny 姿态下被吸收 (目标本不可达,收窄方向与 fail-closed 同向),在 open 姿态下报错(有 deny 行 时它承担成洞语义)。

darwin 的 loopback 逐条落地不是真正的「逐行」:Seatbelt 输出把所有 loopback 拼写(localhost/127.0.0.1/::1…)统一坍缩为 localhost:port,按端口首行生效, 且首个端口 * 行之后整表截断(seatbeltNetForms 同口径)。本函数按该内核表 模拟分类:同端口后续行效果相同为 no-op 重复;效果相反时 deny 行报错(内核 落不下 = 比策略更宽),allow 行被吸收(收窄)。已知残余:跨拼写的 「allow 具体端口 + deny 通配端口」组合(逐行口径覆盖不到),配置请统一用 localhost 拼写。

platform 取 runtime.GOOS 风格值(darwin/linux/windows/…);未列名平台按 「无逐条能力」处理,后端可用性由 planConfined 兜底。

type NetRule

type NetRule struct {
	HostPort string
	Allow    bool
	// ToolOnly 声明本行仅由调用方的工具层执行(进程内 HTTP/MCP 客户端等):
	// 内核沙箱落地(CheckNetPolicy / validateProcessPolicy / Seatbelt 输出)
	// 一律跳过,不参与「能否落实」的判定。用于本平台无法内核落实、但工具层
	// 可执行的规则(典型:linux/windows 上的选择性 deny)。判定入口
	//(Match/Resolve)不看 ToolOnly——工具层照常命中。
	ToolOnly bool
}

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

func ParseNetRule added in v0.3.2

func ParseNetRule(raw string) (NetRule, error)

ParseNetRule 解析 net/ssh 规则行:可选 tool: 前缀 + allow:/deny: + 目标。 tool: 声明该行仅工具层生效(见 NetRule.ToolOnly)。

func (NetRule) String added in v0.3.2

func (r NetRule) String() string

String 返回规范形态(含 tool: 前缀),供日志/回执展示。

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); 规则行解析失败跳过(编译期/入表校验应已拦截,运行时宁缺毋滥)。 端口匹配取保守交集语义:规则为 * 或相等即命中;查询为 * 时重叠即命中。

func (NetRuleSet) Resolve

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

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

Jump to

Keyboard shortcuts

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