Skip to main content

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.3  |  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. 已知限制(诚实清单)

不得当作已通过的部分:

  1. appcontainer 档内浏览器的边界未测:本机族内 Chrome 以 0xC0000142 退出,需在另一台底座正常的机器上补测;
  2. 内核闸门的"完整性判据"本身未测:低于 Medium 的调用者连控制端口都打不开(两臂均为 0x7f), 即部署属性由端口 ACL 承担;闸门内部的完整性规则未被实测证明(也未编译进出货镜像); 闸门的归属规则(非盒主不得改盒策略)已实测有效;
  3. 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.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for winbox-sandbox 1.0.3
File Size Uploaded
winbox_sandbox-1.0.3.tar.gz 10.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for winbox-sandbox 1.0.3
File Interpreter ABI Platform
winbox_sandbox-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 21.0 MB

Release files / winbox_sandbox-1.0.3.tar.gz

Download URL winbox_sandbox-1.0.3.tar.gz
Size 10.6 MB
Tags Source
SHA-256 checksum
How to use checksums
fb580af486711487d6ca889ce6ca5f1517a5998f4343e47835cb9afd301b6b4a
BLAKE2b-256 checksum
How to use checksums
5bb6dc53b5463ae4567fc9ee7126c6a91ab14a70ddf6467af70af557713ad74a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.23

Release files / winbox_sandbox-1.0.3-py3-none-any.whl

Download URL winbox_sandbox-1.0.3-py3-none-any.whl
Size 10.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
43fd585abe9788548f1fefab73f95e8fe640e5d1b4b8d11c21bedbb02f0231ef
BLAKE2b-256 checksum
How to use checksums
74cbf84a3a539e1da07b8cb9683ab1055cccdd3d8c6dd74107821a30557f727b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.23

Release history Release notifications | RSS feed

This release

1.0.3 This release

2 release files

1.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page