Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
04c4c77
feat(runtime-host): constrain managed npm provisioning
zhiiw Aug 8, 2026
1a5af1e
fix(runtime-host): defer npm admission to runtime attestation
zhiiw Aug 8, 2026
2c37025
fix(runtime-host): reap descendants after producer root exit
zhiiw Aug 12, 2026
2772169
feat(runtime-host): attest bundled npm runtime
zhiiw Aug 8, 2026
2e2838e
test(runtime-host): verify real bundled npm installs
zhiiw Aug 9, 2026
d525404
fix(runtime-host): keep Windows npm home path bounded
zhiiw Aug 9, 2026
57f9fb9
fix(release): canonicalize bundled npm verifier paths
zhiiw Aug 9, 2026
f14e621
style(runtime-host): format rebased producer boundary
zhiiw Aug 16, 2026
e1b9786
fix(release): verify legacy installers against legacy resources
zhiiw Aug 16, 2026
9f1418b
fix(release): exclude generated npm bin links
zhiiw Aug 16, 2026
4e97dc7
feat(storage): bind dependency environments to managed execution
zhiiw Aug 9, 2026
06959fe
fix(storage): reject stale managed source baselines
zhiiw Aug 9, 2026
327f7c1
docs(storage): define source reopen validation
zhiiw Aug 9, 2026
1a409cb
fix(storage): make managed dependency admission cancellable
zhiiw Aug 9, 2026
01fde4f
fix(storage): replace aborted dependency publications
zhiiw Aug 9, 2026
141f2d2
fix(storage): cancel managed baseline admission
zhiiw Aug 9, 2026
1875477
fix(storage): close managed execution admission gaps
zhiiw Aug 9, 2026
e08cc56
test(runtime-host): complete managed owner fake
zhiiw Aug 9, 2026
51a73b6
fix(storage): align workspace authority with interactive roots
zhiiw Aug 16, 2026
6683551
test(storage): enforce managed source drift rejection
zhiiw Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ apps/desktop/resources/bin/
# Rebuilt from experiments/windows-sandbox by scripts/package-windows-x64.mjs.
apps/desktop/resources/windows-sandbox/
apps/desktop/bundled-git.json
apps/desktop/.generated/bundled-npm/

# Generated desktop release inputs and outputs.
apps/desktop/resources/tools/
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export default {
'dist/renderer/computer-use-overlay/**',
],
extraResources: [
{
from: '.generated/bundled-npm/npm',
to: 'npm',
},
{
from: '.generated/bundled-npm/bundled-npm.json',
to: 'bundled-npm.json',
},
{
from: 'bundled-tools.json',
to: 'bundled-tools.json',
Expand Down Expand Up @@ -105,6 +113,10 @@ export default {
from: '../../LICENSE',
to: 'licenses/maka/LICENSE',
},
{
from: '../../node_modules/npm/LICENSE',
to: 'licenses/npm-cli/LICENSE',
},
{
from: '../../NOTICE',
to: 'licenses/maka/NOTICE',
Expand Down
129 changes: 129 additions & 0 deletions docs/architecture/bundled-npm-runtime-attestation-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
document_status: implementation-contract
status: draft-stacked-foundation
date: 2026-08-08
milestone: M1.3
stack_base: managed-dependency-producer-boundary-v1
---

# Bundled npm Runtime Attestation v1

## 1. 本 PR 只证明一个主要不变量

> 在调用方已经取得“来自 Maka 已签名发布物”的 resources-root authority 后,固定 npm producer 只能使用其中完整清单验证通过、且绑定当前受支持 Host Node 的 npm 运行闭包;调用者不能通过伪造结构体、传入任意 executable 或在签发后替换 npm 文件来取得执行权。

本 PR 的 owner 是 Runtime Host package 内部的 bundled npm attestation 模块。它拥有 npm 运行树 manifest 的解码、完整文件清单校验、Host Node 版本与 executable identity、不可伪造 capability 的签发和每次调用前的重新验证。

这里必须区分两层证明:外层应用签名与平台发布链提供 **provenance trust**,本模块的 manifest 提供 **runtime integrity**。manifest 与 npm tree 位于同一资源目录,攻击者若能同时替换二者并重算摘要,本模块本身无法识别;它绝不是自足的密码学信任根。PR 3 的 API 合同因此有一个显式前置条件:`resourcesRoot` 必须已经由后续 packaged-process owner 认证。本 PR 单独只能证明“受权目录在 admission 与每次 invocation 时没有发生未声明变化”,不能证明任意目录来自 Maka。

本 PR 不包含 Desktop/CLI/Runtime Host composition 的生产 consumer。attestation resolver、capability issuer 和固定 npm provision 入口不通过 `@maka/runtime-host/server` 公共 barrel 暴露;PR 4 必须在一个固定 `resourcesPath` 的 composition owner 中把三者接通。因此本 PR 保持 Draft,不能单独宣称 M1.3 已可用。

## 2. 为什么只 bundled npm,不再 bundled 一份 Node

Maka 已经由 Electron 或当前受控 Runtime Host 携带 Node。再打包第二份 Node 会增加包体、补丁与许可证维护面,并制造“两套 Node authority”。v1 直接绑定当前 Host runtime:

```text
process.execPath canonical path + SHA-256
+ process.versions.node
+ process.versions.modules (ABI)
+ platform / arch
+ 完整 npm tree manifest
= ManagedNpmRuntimeCapability
```

Node 支持范围采用有限 allowlist;未知未来 major 默认拒绝,必须经过 permission-model 兼容验证后显式加入。当前允许:

- Node 22.22.2 及同 major 后续版本;
- Node 24.15.0 及同 major 后续版本;
- Node 26.x;
- 其他 major 全部拒绝。

## 3. 发布闭包与供应链

发布准备从锁定的 `npm@12.0.2` 生成一个 Maka-owned runtime tree,并替换 npm 自带闭包中的四个已知脆弱版本:

| package | npm 原版本 | 发布版本 | 证据 |
| --- | --- | --- | --- |
| `tar` | 7.5.19 | 7.5.22 | GHSA-r292-9mhp-454m |
| `brace-expansion` | 5.0.7 | 5.0.9 | GHSA-mh99-v99m-4gvg;GHSA-rgw5-rvv9-x895 |
| `ip-address` | 10.2.0 | 10.4.0 | 三条 manifest 中固定的 GHSA |
| `undici` | 6.27.0 | 6.28.0 | 三条 manifest 中固定的 GHSA |

准备过程拒绝 symlink/junction,只接受 regular file/directory,输出:

```text
apps/desktop/.generated/bundled-npm/
npm/** 完整 npm runtime tree
bundled-npm.json 每个文件的 path、bytes、sha256
audit/package-lock.json 独立 production audit 视图
```

release gate 对独立 audit lock 执行 `npm audit --omit=dev --audit-level=high`。当前实际生成闭包约 14.6 MB,audit 为 0 vulnerabilities。生成目录不进入 Git;每次打包重新生成并验证。

## 4. 权限边界

`ManagedNpmRuntimeCapability` 的 TypeScript 形状不是权限。真实权限由 Runtime Host 模块内的 `WeakMap` 记录:只有 internal issuer 产生的对象才能通过消费 gate。结构相同的普通对象必须被拒绝。

签发器同样不属于公共 package API。否则任意调用者可以为自建目录生成“合法” capability,变成自认证。PR 4 的 composition owner 只能以打包应用的固定 resources root 调用 internal resolver,不能接受用户或 operation 传入的路径。

每次 npm invocation 前必须重新验证:

1. Host executable canonical path 未变;
2. Host executable digest 未变;
3. npm runtime 仍只含 regular files/directories;
4. 实际文件集合、大小与 SHA-256 完全匹配 manifest;
5. npm `package.json` 仍是 `npm@12.0.2`、`Artistic-2.0`。

任一项失败都在 spawn 前 fail closed。

## 5. 原子性、失败状态与回滚

本 PR 不写用户 workspace,也不产生 durable T1/T2。它的原子边界是“通过全部验证后签发 capability”;验证中途失败不产生 capability。

稳定失败分类:

- `bundled_npm_unavailable`:资源或 Host executable 不可读;
- `bundled_npm_manifest_invalid`:manifest 形状、路径或范围非法;
- `bundled_npm_platform_mismatch`:platform/arch 不匹配;
- `bundled_npm_integrity_mismatch`:文件集合、内容、版本或许可证不匹配;
- `bundled_npm_node_unsupported`:Host Node 不在验证 allowlist。

回滚本 PR 只需移除 npm release resources、manifest preparation 与 internal attestation 模块;PR 1 storage authority 和 PR 2 producer lifecycle 不需要回滚。没有兼容旧 manifest 的承诺:本能力尚无生产 consumer,格式变化应明确断代而不是建设迁移层。

## 6. 平台能力矩阵

| 能力 | Linux | macOS | Windows |
| --- | --- | --- | --- |
| regular-file tree inventory | 支持 | 支持 | 支持 |
| symlink/reparse input | 拒绝 | 拒绝 | 拒绝 junction/reparse |
| Host executable digest binding | 支持 | 支持;签名仍由外层 app 发布链保证 | 支持;Authenticode 仍由外层 app 发布链保证 |
| 每 invocation tree revalidation | 支持 | 支持 | 支持 |
| npm producer permission profile | Node permission model | Node permission model | Node permission model |

manifest 与 npm tree 一起受最终应用签名/发布物保护。macOS 的 trust root 是通过 Gatekeeper/代码签名发布的 app bundle;Windows 的 trust root 是 Authenticode 签名的安装包与已安装应用。Linux v1 没有统一的平台签名验证 API,因此只承诺由官方发布/更新链安装后的完整性检查,不把任意本机目录提升为可信发布物。manifest hash 本身不是独立信任根;如果恶意本机进程已经能替换已安装应用资源、伪造父进程或直接运行修改后的 Maka 代码,本层不声称独立抵抗该攻击。

同理,后续父子进程 bootstrap 只负责把已经取得的 application authority 传给 detached Host,防止普通 CLI 参数或 ambient path 被误当成发布资源;它不是 macOS code-signing/Windows Authenticode 的替代物,也不抵御能够任意创建 Electron 父进程和 fd channel 的同用户恶意进程。若产品威胁模型将该攻击者纳入边界,必须另行引入平台签名验证 owner,不能继续给 bootstrap 增加可伪造字段。

## 7. Crash / tamper matrix

| 时点 | 结果 |
| --- | --- |
| 准备 runtime tree 中途退出 | 生成目录不进入发布物;下一次 preparation 全量重建 |
| manifest 写入前退出 | release verifier 因 manifest 缺失失败 |
| manifest 与 tree 不一致 | runtime admission 拒绝 |
| capability 签发后 npm 文件被修改 | 下一次 invocation 在 spawn 前拒绝 |
| capability 被结构化伪造 | WeakMap gate 拒绝 |
| Host Node 被替换 | canonical path/digest revalidation 拒绝 |
| platform/arch 不一致 | admission 拒绝 |

## 8. PR 4 的硬前置

PR 4 才能增加首个生产 consumer,并必须同时证明:

1. Desktop/Runtime Host 只从固定 packaged `resourcesPath` 解析 npm;
2. storage authority、producer owner、runtime capability 由同一 composition 生命周期持有;
3. production-shaped 测试使用实际生成的 npm tree,从 hermetic loopback registry 安装一个真实 tarball package,验证解包与 `.bin` 生成后再完成依赖环境 acquire;
4. runtime identity 写入 dependency environment identity,不能由调用者自报;
5. shutdown 顺序先停止新 acquire,再 drain producer,最后关闭 storage authority。

在这五项完成以前,PR 1–3 都只是可独立审查的 stacked foundation,不是用户能力。
157 changes: 157 additions & 0 deletions docs/architecture/managed-dependency-producer-boundary-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
document_status: implementation-contract
status: draft-stacked-foundation
date: 2026-08-08
milestone: M1.3
stack_base: managed-dependency-storage-authority-v1
---

# Managed Dependency Producer Boundary v1

## 1. 本 PR 只证明一个主要不变量

> 在 storage authority 复制 producer 输出以前,固定 npm producer 必须只在一次性 Maka-owned staging 中运行;它不能继承 host secrets、不能创建 child process、不能执行 lifecycle script,并且只有在根进程退出、输出 drain、最终 inventory 与 observed-limit 验证全部完成后才允许 `provision()` resolve。

本 PR 的候选 owner 是 Runtime Host 模块内部的 `runManagedNpmDependencyProvision()`。它拥有固定 npm argv、hermetic environment、Node permission profile、staging project layout、manifest/lockfile admission、timeout/abort、process lifecycle、bounded diagnostics,以及运行中和终态 filesystem inventory。

`runManagedNpmDependencyProvision()` 与低层 `runManagedDependencyProducerProcessInternal()` 均不通过 Runtime Host server barrel 暴露。PR 2 没有 runtime attestation owner,因此任何 raw executable path 都不能成为 production 输入。PR 3 必须先把经过 manifest/digest 验证的、有限 Node major allowlist 内的 bundled runtime capability 与该模块共同落地,之后才能公开固定 npm 入口。后续 PR 不能绕过该入口自行 spawn package manager。

本 PR 不包含:

- bundled npm/Node 文件树、manifest、digest、license 或 release packaging;
- Desktop、CLI、ManagedWorkspaceOwner 或 Runtime Host composition 接线;
- dependency lease 到 Read/Glob/Grep worker 的 logical binding;
- Shell/Build、Write/Edit 或 workspace mutation;
- 用户 PATH 上的 npm fallback;
- production network broker。

因此本 PR 必须保持 Draft。它与 PR 1、PR 3 一样没有独立用户能力;PR 3 完成 runtime attestation 前,当前固定 npm 入口也不能成为生产 API。只有 PR 4 的 production consumer 和端到端测试成立后,整个 stack 才能按顺序转 Ready。

## 2. 固定 producer 协议

v1 只接受:

```text
package manager: npm 12.0.2
manifest: packageManager == npm@12.0.2
lockfile: non-workspace package-lock v3
resolved URL: https://registry.npmjs.org/**
integrity: sha1/sha256/sha384/sha512 SRI
link dependency: rejected
hasInstallScript: rejected
package entries: <= 25,000
manifest bytes: <= 1 MiB
lockfile bytes: <= 64 MiB
```

固定 invocation:

```text
verified-node
--permission
--allow-fs-read=<verified npm runtime root>
--allow-fs-read=<owned staging project>
--allow-fs-write=<owned staging project>
<verified npm-cli.js>
ci
--ignore-scripts
--no-audit
--no-fund
--package-lock=true
--cache=<owned scratch/cache>
--userconfig=<owned scratch/home/npmrc>
--globalconfig=<owned scratch/home/global-npmrc>
```

没有 `--allow-child-process`,因此 production npm root process 不能创建 descendant。`PATH`、`NODE_OPTIONS`、proxy、credential、registry token 和任意 host environment 都不继承;HOME、npm config、temp 与 Node compile cache 全部指向同一次 staging 的 scratch。

PR 3 必须提供经过完整 manifest/digest 验证的 Node executable、npm runtime root 与 npm CLI,并使用有限 Node major allowlist。它必须在同一 Runtime Host package 内完成不可伪造 capability 的发行与消费,再由 server barrel 公开组合后的入口;不能仅把 raw path 验证留给调用者。它也不能覆盖 argv、env、timeout 或 observed limit。

## 3. Owner、时序和失败状态

```text
validate identity + manifest + lockfile
-> canonicalize exact output/scratch/runtime paths
-> create owned scratch children with exclusive creation
-> write exact npm configs + manifest + lockfile
-> spawn one detached root process with no child-process capability
-> monitor whole staging project every 100 ms
-> abort / timeout / invalid tree / observed limit: terminate process tree and await exit + I/O drain
-> normal root exit
-> await stdout/stderr drain
-> final complete inventory
-> exit code == 0
-> provision resolves
-> PR 1 storage authority may deep-copy producer output
```

稳定失败原因:

```text
aborted
timeout
filesystem_limit_exceeded
filesystem_invalid
output_drain_incomplete
process_failed
```

失败不会发布 artifact 或 receipt。transaction root 仍由 PR 1 storage authority 拥有并在 producer rejection 后删除;PR 2 不新增第二个 durable owner、数据库或 cleanup journal。

## 4. Filesystem inventory 与 `.bin`

默认 soft observed limit 固定为 2 GiB 和 250,000 entries,不能由 PR 3/4 调高。它通过 100 ms polling 与最终 inventory 保证“超限结果绝不进入 artifact publication”,但不是 OS/filesystem 强制的峰值磁盘 quota;producer 可在相邻 observation 之间短暂超写。若产品要求防止磁盘被瞬时写满,必须另建 OS quota、受控写入 broker 或等价平台 owner,不能把 polling 描述为 disk-safety boundary。

空文件、目录、普通文件和合法 symlink 都计入 observed entry limit;普通文件 size 与 symlink target bytes 计入 observed byte limit。进程退出后必须再做一次完整终检,避免短命 producer 在 monitor tick 前超限后退出。

Linux/macOS 允许 npm 的典型相对 `.bin` symlink,例如:

```text
node_modules/.bin/tool -> ../package/bin/tool.js
```

target 必须按 link 所在目录解析后仍位于 staging project 内。absolute/escaping symlink fail closed。Windows 的 npm shim 应为普通 `.cmd/.ps1` 文件;symlink、junction/reparse point 在 inventory 或后续 PR 1 artifact seal 中拒绝。

## 5. 平台能力矩阵

| 平台 | child process | timeout/abort | `.bin` | 保证 |
|---|---|---|---|---|
| Linux | PR 3 attested Node permission 禁止;异常终止用 process group + descendant scan | root 存活时先收割树,再 reject | contained relative symlink | production-shaped POSIX 测试必须执行 |
| macOS | PR 3 attested Node permission 禁止;异常终止用 process group + descendant scan | root 存活时先收割树,再 reject | contained relative symlink | `/var` alias 由 canonical path 处理 |
| Windows | PR 3 attested Node permission 禁止;异常终止用 `taskkill /T /F` | root 存活时先收割树,再 reject | 普通 npm shim;reparse 拒绝 | 无 Job Object;禁止任意可生 descendant 的 producer |

Node permission model 与 PR 3 runtime attestation 必须共同存在,才构成 production root-only 证明;二者都不是可选 hardening。内部任意 argv primitive 不承诺在 root 已退出后回收 detached descendant,且不得成为 production consumer。若未来 package manager 必须启动 child process,必须定义新的 capability/policy identity,并为 POSIX 引入 cgroup/subreaper 或为 Windows 引入 Job Object 等真实平台 owner;不能在 `hermetic_dependency_builder_v1` 下静默加入 `--allow-child-process`。

## 6. Network 边界与尚未闭环的证明

本 PR 固定官方 registry config,并在 spawn 前拒绝非官方 `resolved` URL;无 lifecycle script、无 child process、无 host proxy/credential 环境。它没有单独提供一个 OS 级 host allowlist。

因此 `registry_https_only` 的完整证明依赖 PR 3 对 bundled npm runtime tree 的不可变验证,以及 PR 4 的 production composition/egress 决策。本 PR 不能单独被描述为已经提供强网络 sandbox。若 PR 4 要求网络层也成为强制 host allowlist,应增加 host-owned registry fetch broker 或等价执行边界,并产生新的 production-shaped 网络对抗测试;不能仅靠文案把 npm config 当作 OS enforcement。

## 7. Crash 与对抗矩阵

| 场景 | 唯一合法结果 |
|---|---|
| manifest/lockfile 不满足固定 policy | spawn 前拒绝 |
| scratch child 被 symlink/junction 预占 | spawn 前拒绝;outside 不写入 |
| npm 尝试创建 child process | Node permission 拒绝;不产生 descendant side effect |
| caller abort | tree 完全退出且 I/O drain 后返回 `aborted` |
| timeout | tree 完全退出且 I/O drain 后返回 `timeout` |
| observed byte/entry limit 超限 | tree 完全退出后返回 `filesystem_limit_exceeded`;超限内容不发布,但不承诺峰值磁盘占用 |
| escaping/unsupported entry | tree 完全退出后返回 `filesystem_invalid` |
| root exit 非零 | bounded stderr/stdout tail 随 `process_failed` 返回 |
| output 在 deadline 前未 drain | 返回 `output_drain_incomplete`;该状态禁止发布。PR 3 通过 attested no-child runtime 使 descendant 形状不可达 |
| producer 成功后 storage deep-copy 前 host 崩溃 | PR 1 启动清理 staging,无 artifact/receipt |

child-process crash test 只能证明进程生命周期,不是断电测试。PR 2 不写 durable fact,所以没有 schema migration 或数据库 recovery 语义。

## 8. Extraction ledger

| 旧集成提交 | PR 2 处理 |
|---|---|
| `89c9e0a3c feat(runtime-host): ship verified bundled npm environments` | 只提取固定 npm argv/env、input validation 与 process-owner 轮廓;runtime manifest/release 归 PR 3 |
| `8150e90a7 fix(runtime-host): constrain bundled npm provisioning` | 重写 timeout/abort、quota、scratch 与 `.bin` policy;不迁移仅 `child.kill()` 的旧生命周期 |
| `27f8f6b8e fix(release): verify shipped bundled npm closure` | 不属于 PR 2;全部留给 PR 3 |
| `9a42a761c fix(runtime-host): transport packaged dependency authority` | 不属于 PR 2;production composition 留给 PR 4 |

本 PR 从 PR 1 head 平铺增加 producer owner,没有 cherry-pick 上述跨边界提交。
Loading
Loading