winbox —— Windows 10/11 轻量应用沙箱(可运行/安装任意 exe,含浏览器)
winbox 把"不受信任的程序"关进一个由多层独立机制叠加出来的盒子里,只用一个 Python 包交付:
纯标准库运行、自签驱动随包、一条命令完成部署、启动前必给环境检测报告。
from winbox import SandboxManager
mgr = SandboxManager() # 只探测本机,不改任何设置
with mgr.session("demo") as sb: # 启动前自动打印环境检测报告
r = sb.run(["cmd.exe", "/c", "echo hi"], timeout=10)
print(r.ok, r.exit_code, (r.stdout or "").strip())
1. 安装
环境要求:Windows 10/11 x64 + 64 位 Python ≥ 3.11。全部运行时依赖都是 Python 标准库
(ctypes / winreg / subprocess / tomllib …),不引入任何第三方包。
发行名是 winbox-sandbox,导入名是 winbox(PyPI 上 winbox 这个名字属于另一个无关项目)。
pip install winbox-sandbox # pip
uv add winbox-sandbox # uv(项目)
uv tool install winbox-sandbox # uv(全局命令)
装完自检:
python -c "import winbox; print(winbox.__version__, winbox.detect_os().display_name)"
winbox info # 环境 + 后端可用性(只读,从不提权)
只支持 Windows。在别的平台上
import winbox会立刻给出明确报错,不会拖到运行期才失败。
1.1 部署(一次管理员权限)
安装包只是把文件放到磁盘上;沙箱组件需要一次提权部署:
python -m winbox.deploy install # 一次管理员权限(UAC):把所需组件一次装齐
winbox deploy check # 部署真相(只读,不提权)
winbox deploy plan # 只看计划,不动手
install 的步骤(每步先探测再动手,重复执行不会重复安装):
preflight → layout → dokan → api_fake → account → credential → driver → trust → broker → manifest
dokan— 包内 MSI,装 Dokan 运行时(虚拟盘层);api_fake— 包内winboxfake.dll;account/credential— 专用账户 + 凭据 + deny-ACE 封印;driver— 注册/启动winboxflt(仅当有已签名镜像,包内自带);trust— 让内核镜像可加载:导入包内证书(与包内驱动镜像的签名者一致)并按需开 test-signing; test-signing 需要重启生效,所以只报needs_reboot: true,不替你重启;broker— 安装并启动winboxbroker(已在跑且管道应答 ⇒ 记为skipped)。
退出开关:--no-dokan / --no-kernel / --no-trust / --no-broker。
路径 A(自签)是默认路径:不需要购买任何证书。代价是必须关掉 Secure Boot 并接受一次 test-signing; 若受管机器不允许关 Secure Boot,则只能走"EV 证书 + Microsoft attestation"的零操作路径。
1.2 两类落盘位置(都可重定向)
| 位置 | 默认值 | 内容 |
|---|---|---|
| 安装根 | %ProgramData%\winbox\ |
驱动 / broker / Dokan 工具 / 证书 |
| 沙箱根(store root) | 源码树内为 <项目目录>\.winbox_env\;安装成包后为 %LOCALAPPDATA%\winbox\stores\ |
每个沙箱一份伪造 C:\ 骨架、档案、overlay、日志 |
沙箱根可整目录删除,下次运行自动重建。解析优先级:
显式 root_dir 参数 > 配置项 root_dir > 环境变量 WINBOX_ROOT > 上面的默认值。
想把它放到数据盘或临时目录:
$env:WINBOX_ROOT = 'D:\winbox-stores' # 本次会话的所有沙箱都落到 D 盘
winbox info # 报告里的 root= 会如实反映实际位置
2. 它是怎么工作的
| 层 | 机制 | 作用 |
|---|---|---|
| L0 资源/生命周期 | Job Object | 内存/CPU/进程数限制,退出时整棵进程树终止 |
| L1 伪造文件系统 | 每个沙箱一份 store(伪造 Windows 骨架 + 环境重定向) | 程序以为在读写 C:\,实际落在 store 内 |
L1 虚拟盘 S: |
Dokan 把 store 挂成真实盘符 | 需要真实盘符的程序也能跑 |
| L1 API 视图伪装 | winboxfake.dll 挂起态注入,改写各模块导入表 |
进程内看到的盘符/已知文件夹被改写,后加载的 DLL 同样覆盖 |
| L1′ AppContainer 低完整性盒子 | NtCreateLowBoxToken 派生真 lowbox 令牌 + 包 SID 授权仅 store |
宿主目录默认拒写(连用户 %TEMP% 也拒),同用户宿主进程不可杀 |
| L2 账户边界 | 专用账户(默认 winbox_sbx) |
免提权闭合"选择器只见宿主盘 / 下载逃逸 / 硬编码写宿主" |
| L3 内核硬界 | 自研 minifilter winboxflt(随包已签名) |
默认全闭 + 读白名单 + 盒外写拒绝(fail-closed) |
| 可选:特权 broker | winboxbroker LocalSystem 服务 + 严格 ACL 命名管道 |
免提权起 Medium 进程(Chromium broker 模型);opt-in |
| 可选:网络出口控制 | 本地代理 + 动态断网/过滤 | network=none/localhost/outbound/... |
分级(tier):appcontainer > restricted > jobonly;auto 是策略不是档位 —— 它按这个顺序选本机实际可用的最强档,
启动前报告里的 tier: auto -> X 才是这次真正会用的一档。
3. 快速开始
3.1 Python
import winbox
# 一次性运行(用完即收)
r = winbox.run_once(["cmd.exe", "/c", "ver"], tier="auto", timeout=15)
print(r.ok, r.exit_code, r.stdout)
# 会话式:多次运行、共享 store 与档案
with winbox.session("work", tier="appcontainer", cleanup_on_exit=True) as sb:
sb.run(["cmd.exe", "/c", "echo a"], timeout=10)
sb.run(["cmd.exe", "/c", "dir S:\\"], timeout=10)
print(sb.status()) # 含 backend / store_dir / api_fake 等真实状态
3.2 单类入口(推荐)
from winbox import SandboxManager
mgr = SandboxManager()
print(mgr.summary()) # 版本 / root / 各档是否可用 + 原因 / 驱动 / broker
print(mgr.component_gaps()) # 还缺哪些组件(健康机为空列表)
mgr.install() # 需要管理员:装配全部组件
with mgr.session("demo", cleanup_on_exit=True) as sb:
print(sb.run(["cmd.exe", "/c", "echo hi"], timeout=10).exit_code)
print(sb.resources()["memory"]["peak_job_bytes"]) # 该沙箱峰值内存(整棵进程树)
print(mgr.verify(live=True).summary()) # 本机验收报告(每项都实测,ok=None 表示测不到)
3.3 命令行
winbox info # 环境 + 后端可用性(只读,从不提权)
winbox run cmd.exe --wait --args /c echo hello # 跑一条命令
winbox run --low cmd.exe --wait --args /c dir # 以 Low 完整性跑
winbox status demo # 某个沙箱的状态
winbox list | prune # 列出 / 清理沙箱
winbox deploy check # 部署真相(只读)
winbox deploy install # 管理员:一次装齐
winbox broker status # broker 服务/镜像/管道真相
winbox selftest # 本机功能自检
python -m winbox … 与 winbox … 等价;源码树里的 winbox.cmd 是 Windows 便捷入口,
python main.py … 是可直接运行的顶层入口(不带参数时打印环境适配报告)。
3.4 本机功能自检
python Test\test_all.py # 源码树内:全量(非提权;需管理员的用例记为 SKIP)
python Test\test_all.py --quick # 跳过耗时的压力/泄漏用例
winbox selftest # 经 CLI 调用(会先请求一次管理员权限)
自检逐项实测本机能力:主机真值、配置、网络能力、令牌与 Job Object、伪造 C: 树与 minifilter 决策表、
三档后端、公开 API、健壮性/路径模糊、并行沙箱、资源占用统计、Dokan、账户边界、出口控制、
内核契约、broker、部署、提权策略、门面、浏览器与压力;每项要么给出可观测事实的断言,
要么带具体原因记为 SKIP(测不到绝不记为通过)。
自测套件属于源码树的一部分,不随 wheel 分发;只有源码树里(或从 sdist 解包后) 才能跑它,发行包里
winbox selftest会如实打印这一行提示而不是假装跑过。
4. 启动前必有环境检测报告
每一次 session() / run_once() / start() 都先做只读探测,再把报告打到 stdout,然后才启动:
----------- winbox preflight (before start) -----------
winbox : 1.0.2 | python 3.12.x (64-bit) | root=C:\ProgramData\winbox
host : Windows 11 (build 22631) | elevated=no
tier : auto -> appcontainer (strongest tier available on this host)
[OK] auto
[OK] appcontainer
[OK] restricted
[OK] jobonly
driver : image=test_signed ready=True running=True answering=True
broker : installed=True running=True available=True
dokan : runtime=True
blockers : none
warnings : 1
- not elevated: install / trust --apply / driver cases need one UAC prompt
-------------------------------------------------------
怎么读:
tier: auto -> X是这次真正会用的档位;自动兼容阶梯仍可能在单个进程上再调整(实际值看RunResult.backend);blockers非空 = 按当前配置这次启动不该被信任(例如显式要的档位在本机不可用、配置要求内核层但 minifilter 不应答);warnings= "能跑,但你必须知道"(未提权、驱动镜像未就绪、broker 不可用、appcontainer 不可用、Python 低于声明下限……);- 报告只写探测到的事实;测不到的项一律
SKIP/UNKNOWN并写明原因,绝不写成通过。
控制与取数:
| 想要 | 写法 |
|---|---|
| 只要数据不打印 | mgr.preflight_report(tier="restricted") → dict(report/blockers/warnings/tier/tiers/driver/broker/environment/signing) |
| 单次静音 | mgr.session("demo", report=False) |
| 整体静音(热循环) | SandboxManager(preflight=False) |
| 有 blocker 直接抛错 | mgr.preflight(strict=True) → SandboxError |
| 强制重新探测 | mgr.preflight_report(force=True) |
| 缓存时长 | SandboxManager(preflight_ttl=60.0)(秒) |
| 缺件就自动补齐 | mgr.ensure_environment()(默认跟随构造时的 ensure=True) |
初始化自足:每次启动前除了打印报告,还会逐个只读探测必需组件(Dokan / minifilter / 内核信任 / broker),
缺什么装什么;全都在就 skipped 并写明检测到什么。缺件且没有管理员权限时,返回 needs_elevation
并把那一条命令打印出来 —— 绝不静默降级。
5. 提权:三层入口都能设置
| 入口 | 不提权 | 提权 |
|---|---|---|
| CLI 全局 | --no-elevate(任意位置;提权门控的工作如实报 SKIP) |
selftest / deploy install / deploy uninstall / deploy trust --apply 自动一次 UAC;run --admin 选择性提权(默认不给 run 提权:那会改变沙箱进程的父进程) |
| 只读命令 | info / check / trust(dry-run) / list / status |
从不提权,保持"在该权限下不可用"的诚实状态 |
| Python API | SandboxManager(elevation="never")(默认):只返回 needs_elevation + 精确命令 |
SandboxManager(elevation="auto"):ensure_environment() 会自己用一次 UAC 跑 python -m winbox.deploy install |
| 有界提权执行 | — | mgr.run_elevated(argv, timeout=900) → 在提权子进程里跑给定命令并返回退出码;已提权时就地执行,不重复弹窗 |
from winbox import SandboxManager
m = SandboxManager() # 默认 never
print(m.elevation, m.as_dict()["elevation"])
print(m.ensure_environment(install=False))
# -> {'ok': False, 'skipped': True, 'detected': 'gaps not fixed because install=False',
# 'action': 'python -m winbox.deploy install'}
a = SandboxManager(elevation="auto") # 允许自己提权补齐
print(a.ensure_environment()) # 缺件时走一次 UAC,返回 elevated_install 结果
# 显式、有界地跑一条特权命令
print(m.run_elevated(["python", "-m", "winbox.deploy", "trust", "--apply"]))
非法策略值在构造时就拒绝:ValueError: unknown elevation policy 'bogus'; use 'never' or 'auto'。
注意:
SandboxConfig.elevation_policy(auto/appcontainer/restricted/jobonly)是沙箱后端档位策略, 与"本进程是否提权"是两件事。
6. 档位语义与实测边界(重要)
| 档位 | 隔离内容 | 写宿主用户目录 | 说明 |
|---|---|---|---|
jobonly |
仅 Job Object(资源/生命周期) | 不阻止 | 无文件系统边界,不要用它做数据隔离 |
restricted |
受限令牌(可 low 或 medium) | low:阻止;medium:不阻止 |
浏览器等程序需要 medium;要数据隔离就用 low 或换档 |
appcontainer |
真 lowbox + 包 SID 授权仅 store | 阻止(连 %TEMP% 也拒) |
免提权可用,是强边界档 |
user_boundary(账户边界) |
专用账户 | 阻止(宿主目录属于另一个用户) | 免提权闭合选择器/下载/硬编码写宿主 |
结论:沙箱内浏览器是否需要"写不出去",取决于你选的档位;浏览器要数据隔离请用 appcontainer 或账户边界。
7. 浏览器(含"禁止项")
import winbox
print(winbox.BROWSER_REQUIRED_FLAGS) # 壳内跑浏览器必需的参数
argv = [r"C:\Program Files\Google\Chrome\Application\chrome.exe",
"--headless=new", "--no-sandbox", "--disable-gpu",
"--virtual-time-budget=3000", "--dump-dom", "about:blank"]
with winbox.session("browser", tier="restricted") as sb:
r = sb.run(argv, timeout=120)
print(r.exit_code, (r.stdout or "")[:80])
--no-sandbox/--disable-gpu是在受限令牌内让 Chromium 真正跑起来的必要条件(否则 GPU/渲染进程起不来);- 生产部署禁止开调试通道:
--remote-debugging-port/--remote-debugging-pipe/--remote-allow-origins会把页面能力提升到"能读宿主文件系统"(一次DOM.setFileInputFiles就能把任意宿主路径交给页面读取, 既不需要文件对话框,也不需要用户手势)。用winbox.browser_arg_warnings([...])自检:
warns = winbox.browser_arg_warnings(["--headless=new", "--remote-debugging-port=9222"])
assert not warns, warns # 生产里必须为空
8. API 参考
8.1 SandboxManager(别名 Manager)—— 单类入口
| 分组 | 方法 |
|---|---|
| 身份/配置 | root account tier config default_timeout version elevation configure() as_dict() |
| 环境真相(只读) | environment() doctor() tiers() check() summary() preflight() preflight_report() planned_tier() component_gaps() ensure_environment() |
| 安装/信任(管理员) | install() uninstall() run_elevated() trust(apply=...) |
| 沙箱生命周期 | session() run_once() start() current() run() stop() cleanup() list() prune() create() __enter__/__exit__ |
| 内核与服务 | driver_status() kernel_ready() broker_status() broker_ping() broker_stress() broker_install() |
| 浏览器 | browser_config() browser_blockers() browser_arg_warnings() |
| 验收 | verify(live=...) |
| 资源统计 | resources(sandbox_id=None) |
构造函数:
SandboxManager(root=None, account=None, tier="auto", config=None, default_timeout=None,
preflight=True, preflight_ttl=60.0, ensure=True, elevation="never")
8.2 Session 与 RunResult
with winbox.session(name="demo", tier="appcontainer", config=None, cleanup_on_exit=True) as sb:
r = sb.run(["cmd.exe", "/c", "echo hi"], cwd=None, timeout=15)
print(r.ok, r.exit_code, r.stdout, r.stderr) # RunResult
print(sb.status()) # backend / store_dir / api_fake / 层状态
SandboxManager.session(...) 额外支持 report=(单次是否打印检测报告)与 auto_start=(只构造不启动)。
8.2.1 资源占用统计 resources()
with winbox.session("demo") as sb:
sb.run(["python", "-c", "buf = bytearray(64*1024*1024)"], timeout=30)
r = sb.resources() # 只读:不会启动沙箱
r["processes"] # {'total': 2, 'active': 0, 'terminated': 0, 'limit': 4}
r["cpu"] # {'user_ms': 1360.0, 'kernel_ms': 16.0, 'total_ms': 1376.0,
# 'rate_limit_percent': 50, 'job_time_limit_ms': None}
r["memory"] # {'peak_job_bytes': 90800000, 'peak_process_bytes': ..., 'working_set_bytes',
# 'private_bytes', 'page_faults', 'limit_job_bytes', 'limit_process_bytes'}
r["gpu"] # {'available': False, 'dedicated_bytes': None, 'shared_bytes': None,
# 'source': 'pdh:GPU Process Memory', 'reason': '...'}
r["disk"] # {'store_bytes', 'store_files', 'store_dirs',
# 'volume_free_bytes', 'volume_total_bytes'}
r["io"] # {'read_ops', 'write_ops', 'read_bytes', 'write_bytes'}
r["handles"], r["threads"], r["unavailable"] # 句柄数 / 线程数 / 每项测不到的原因
数据来源与边界:
- 进程数、CPU 时间、峰值内存、I/O 计数来自该沙箱自己的 Job Object,即整棵进程树的合计 —— 所以它天然按沙箱隔离,一个沙箱一个 Job;
- 单进程视图(工作集/私有字节/句柄/线程)只在子进程仍在运行时可读;退出后是
None+ 原因, 因为 pid 会被系统复用,那时读出来的是别的进程; - 显存不在 Job Object 里:走性能计数器
GPU Process Memory(pid_<pid>)。没有 WDDM 适配器、 进程当前没有 GPU 活动、或本机计数器不可用时,gpu.available=False并写明原因 —— 不用 0 冒充"没用显存"; memory_limit_mb是单进程上限(limit_process_bytes),Job 级上限(limit_job_bytes) 只有显式配置时才有值;- 硬盘是 store 整树字节/文件数 + 所在卷的空闲/总容量;
manager.resources(sandbox_id)用只读句柄统计:磁盘与state.json可读,而进程/CPU 属于创建 Job 的那个进程,若沙箱由别的进程启动则如实标为不可用并说明原因;没有活动会话时返回带原因的空结构, 不抛异常。
8.3 SandboxConfig 常用字段
from winbox import default_config
cfg = default_config("demo")
cfg.integrity = "low" # low(阻止写宿主用户目录) / medium(兼容更多程序)
cfg.elevation_policy = "appcontainer" # auto/appcontainer/restricted/jobonly(沙箱后端档位)
cfg.network = "none" # none/localhost/outbound/internet/full
cfg.memory_limit_mb = 512
cfg.cpu_rate_percent = 100
cfg.env_redirect = True # 把 C: 视图重定向到 store
cfg.api_fake = {"enabled": True}
8.4 模块级 API(与类同一实现)
import winbox
winbox.session(...) winbox.run_once(...) winbox.doctor() winbox.tiers() winbox.environment()
winbox.verify(live=True) winbox.broker_status() winbox.broker_stress() winbox.driver_status()
winbox.browser_arg_warnings([...]) winbox.browser_config()
8.5 异常
winbox.SandboxError 及其子类用于"启动/运行失败"的可捕获错误;环境不足一律走报告 + 明确命令,
不用异常掩盖真实原因。
9. 示例
| 脚本 | 讲什么 |
|---|---|
01_quickstart.py |
最小可用:建沙箱 → 跑命令 → 读 ok/exit_code/stdout |
02_tiers.py |
四个档位的可用性与实际选中档 |
03_manager_preflight.py |
单类入口:启动前检测报告 + 缺件自动补齐 |
04_files.py |
沙箱内伪造 C:/S: 与宿主侧 store 的真实位置 |
05_browser.py |
沙箱内跑 Chrome 的无头自检 + 禁止的调试通道告警 |
python examples\01_quickstart.py
脚本自带 sys.path 引导,源码树里可直接运行;安装成包后那三行可删。
10. 已知限制(诚实清单)
不得当作已通过的部分:
appcontainer档内浏览器的边界未测:本机族内 Chrome 以0xC0000142退出,需在另一台底座正常的机器上补测;- 内核闸门的"完整性判据"本身未测:低于 Medium 的调用者连控制端口都打不开(两臂均为
0x7f), 即部署属性由端口 ACL 承担;闸门内部的完整性规则未被实测证明(也未编译进出货镜像); 闸门的归属规则(非盒主不得改盒策略)已实测有效; restricted@medium/jobonly不阻止写宿主用户目录 —— 这是档位语义,不是缺陷。
路径名硬化:store 路径分量若使用 Windows 保留设备名(NUL/CON/COM1/aux.txt…)、
以点或空格结尾、或含 <>:"/\|?* 与不可见字符,Session.path() / write_file() 直接拒绝。
原因是这类名字一旦写进 store,Win32 就再也无法寻址它(DeleteFileW(123)、\\?\ 形式、
RemoveDirectoryW、NT 相对打开、8.3 短名全部失败),会变成永久垃圾,连 store 清理都带不走。
store 删除路径同时采用健壮删除(重试 + 长路径 + 逐条目兜底,删不干净时报错而不是假装干净)。
11. 许可与责任
专有许可:商用与个人使用都必须事先获得授权。 本软件(源代码、二进制/驱动、文档、示例与随包载荷)
默认不授予任何使用权 —— 无论商业用途还是个人用途,任何形式的使用都需要先取得版权持有人的书面授权。
未获授权禁止复制、安装、部署、运行、修改、翻译、反编译(法律强制允许的情形除外)、分发、再许可、转售、
以托管服务形式提供,或以任何方式使第三方获得;也不得移除版权与许可声明。完整条款见 LICENSE;
随包第三方组件(如 Dokan 运行时/驱动、Windows SDK 相关头文件)仍适用其各自许可,必须同时遵守。
授权申请与商务联系:<请填写版权持有人的联系方式>。
责任说明:沙箱是纵深防御的一部分 —— 请把"不受信任代码"当作"运行在你用户上下文里、受完整性/Job/
受限令牌约束的程序"看待;涉及宿主敏感数据的场景请配合管理员 + AppContainer/Dokan 层并定期审计。所有
"能用/不能用"都由本机实测决定并如实报告:报告里出现 SKIP/UNKNOWN 时,那是没有测到,不是通过。
本软件按"现状"提供,不附带任何担保,版权持有人不承担任何连带责任。
Metadata
Release files for winbox-sandbox 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| winbox_sandbox-1.0.2.tar.gz | 10.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| winbox_sandbox-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.0 MB
Release files / winbox_sandbox-1.0.2.tar.gz
| Download URL | winbox_sandbox-1.0.2.tar.gz |
|---|---|
| Size | 10.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d3d3f775755537d63e9cd9c1edae1aae7fd8139b6515e8a3a79e687d41dec62
|
|
BLAKE2b-256 checksum How to use checksums |
24a4e2fcfa7f9797eb3df7a32edea7983d8714a572b2cee85ded6b57d9911bb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.23
|
Release files / winbox_sandbox-1.0.2-py3-none-any.whl
| Download URL | winbox_sandbox-1.0.2-py3-none-any.whl |
|---|---|
| Size | 10.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ce7383728d8b6bffe12a0d3cab12e96e2061ffecdbd9b38d80b445475727fe90
|
|
BLAKE2b-256 checksum How to use checksums |
8b440b2b1a04fdb6a14a4b9411807d0a6f03d7c9b7661282117699027f3b54bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.23
|