Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8429edd
feat(git): add short-lived Gitoxide admission helper
zhiiw Aug 23, 2026
612b301
fix(git): classify unsupported repository formats
zhiiw Aug 23, 2026
613f982
feat(git): bind helper artifacts to opaque capabilities
zhiiw Aug 23, 2026
e860f02
feat(git): own short-lived helper invocations
zhiiw Aug 23, 2026
79236ec
test(gitoxide): isolate parallel repository fixtures
zhiiw Aug 24, 2026
a70b1c5
feat(git): issue repository admission capabilities
zhiiw Aug 23, 2026
d47fd36
feat(git): import admitted source heads
zhiiw Aug 23, 2026
a7f36d9
build(git): lock source import dependency
zhiiw Aug 23, 2026
2e414d9
fix(git): bound managed tree traversal
zhiiw Aug 24, 2026
80375bc
fix(runtime-host): preserve tree policy failures
zhiiw Aug 24, 2026
21fa049
style(gitoxide): match pinned Rust formatting
zhiiw Aug 24, 2026
28a6b22
fix(gitoxide): make source import restartable
zhiiw Aug 24, 2026
1377437
fix(gitoxide): close helper admission gaps
zhiiw Aug 24, 2026
73f2f64
fix(gitoxide): harden source import ownership
zhiiw Aug 24, 2026
92f7e42
style(gitoxide): apply pinned Rust formatting
zhiiw Aug 24, 2026
9d7aaa9
test(gitoxide): prove import timeout behavior
zhiiw Aug 24, 2026
ea36994
fix(gitoxide): verify imported object identity
zhiiw Aug 24, 2026
b0c79b2
style(gitoxide): apply pinned Rust formatting
zhiiw Aug 24, 2026
8abe835
test(gitoxide): replace read-only object fixtures
zhiiw Aug 24, 2026
90b0518
fix(gitoxide): close source import identity gaps
zhiiw Aug 24, 2026
3c6ab08
fix(gitoxide): implement Git tree ordering
zhiiw Aug 24, 2026
9e3163a
fix(gitoxide): bound repository metadata admission
zhiiw Aug 24, 2026
b1604d3
style(gitoxide): apply pinned Rust formatting
zhiiw Aug 24, 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
89 changes: 89 additions & 0 deletions .github/workflows/gitoxide-helper-admission.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

name: Gitoxide helper admission

on:
pull_request:
paths:
- '.github/workflows/gitoxide-helper-admission.yml'
- 'native/gitoxide-helper/**'
- 'packages/runtime-host/src/server/gitoxide-helper-*.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-helper-*.test.ts'
- 'packages/runtime-host/src/server/gitoxide-repository-admission-authority-internal.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-repository-admission-authority-internal.test.ts'
- 'docs/architecture/gitoxide-*.md'
push:
branches:
- main
paths:
- '.github/workflows/gitoxide-helper-admission.yml'
- 'native/gitoxide-helper/**'
- 'packages/runtime-host/src/server/gitoxide-helper-*.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-helper-*.test.ts'
- 'packages/runtime-host/src/server/gitoxide-repository-admission-authority-internal.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-repository-admission-authority-internal.test.ts'
- 'docs/architecture/gitoxide-*.md'

permissions:
contents: read

concurrency:
group: gitoxide-helper-admission-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24.18.1
cache: npm
- name: Check Rust formatting
working-directory: native/gitoxide-helper
run: cargo fmt --check
- name: Test the short-lived Gitoxide helper
working-directory: native/gitoxide-helper
run: cargo test --locked
- name: Install JavaScript dependencies without packaging hooks
run: npm ci --ignore-scripts
- name: Build the helper invocation owner
run: >-
npm --workspace @maka/core run build &&
npm --workspace @maka/storage run build &&
npm --workspace @maka/runtime run build &&
npm --workspace @maka/runtime-host run build
- name: Test the real helper invocation contract
env:
MAKA_GITOXIDE_HELPER_PATH: ${{ github.workspace }}/native/gitoxide-helper/target/debug/maka-gitoxide-helper${{ runner.os == 'Windows' && '.exe' || '' }}
run: >-
node --test
packages/runtime-host/dist/__tests__/gitoxide-helper-artifact-authority-internal.test.js
packages/runtime-host/dist/__tests__/gitoxide-helper-invocation-internal.test.js
packages/runtime-host/dist/__tests__/gitoxide-repository-admission-authority-internal.test.js
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ docs/assets/
apps/desktop/tests/real-window-smoke/
deepseek.key

# Built only by the dedicated Gitoxide helper lane; normal workspace tests do not use Cargo.
/native/gitoxide-helper/target/

# Generated Computer Use executor binary; provenance metadata stays tracked.
apps/desktop/resources/bin/
# Rebuilt from experiments/windows-sandbox by scripts/package-windows-x64.mjs.
Expand Down
118 changes: 118 additions & 0 deletions docs/architecture/gitoxide-helper-artifact-authority-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Gitoxide helper artifact authority v1

状态:stacked 验证切片;尚无正式 release issuer、Desktop/CLI/Runtime Host 生产消费者,必须保持
Draft。

## 1. 主要不变量

本切片只证明:

> 普通 caller 不能用自报的 executable path 或 SHA-256 获得 Gitoxide helper 调用资格;只有内部
> release owner 签发、与 owner token 绑定的 artifact claim,在 exact platform、architecture、
> protocol、size 与 SHA-256 校验通过后,才能转换为另一个指定 owner 可消费的 opaque invocation
> capability。artifact 在 admission 后变化时,调用前重验必须 fail closed。

它不证明平台签名、安装目录保护、helper spawn、repository observation、T1 admission、managed
workspace 或 crash recovery。

## 2. Owner 与 API 权限

```text
未来的 packaged-release owner
└─ issueGitoxideHelperReleaseArtifactClaimInternal(ownerToken, exact artifact identity)
↓ opaque release claim
artifact authority
└─ exact file/platform/protocol verification
↓ opaque invocation capability
未来的 invocation owner
└─ verifyGitoxideHelperArtifactForInvocationInternal(ownerToken, capability)
```

- claim 与 capability 的状态存放在模块私有 `WeakMap` 中;对象表面不包含 path、digest 或 size。
- claim 必须由相同的 release owner token 消费;capability 必须由签发时指定的 invocation owner token
消费。
- 相关 internal API 不从 `@maka/runtime-host/server` 导出。
- 旧的 caller-provided `{ executablePath, expectedSha256 }` 不能成为这条链的 authority。

当前没有 production release owner。`issueGitoxideHelperReleaseArtifactClaimInternal()` 只是未来受信
packaging owner 的接缝,不是签名信任根;在该 owner 落地前,本切片不能转 Ready。

## 3. 校验边界

一次 artifact 校验包含:

1. 输入 claim 的 protocol/platform/architecture/size/digest 形状检查;
2. 拒绝 claimed path 任意组件中的 symlink 或 Windows junction;
3. 打开 canonical regular file,并限制 helper artifact 最大为 256 MiB;
4. 在同一 handle 上进行 64 KiB 有界缓冲的 SHA-256 流式读取;
5. 比较读取前后 handle identity/size/timestamps;
6. 比较读取后 path identity 与已打开 handle;
7. 比较 exact byte count 与 digest。

admission 与每次 invocation resolve 都执行这套校验。它可以识别校验之前或校验期间的替换,不会把
相邻 manifest 当作自证信任根;但校验完成后必须关闭 handle,而 Node 只能按 path spawn,所以这里不把
“刚验证的 bytes”表述成“实际执行的 bytes”。

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

| 项目 | v1 合同 |
| --- | --- |
| owner | Runtime Host 内部 artifact authority |
| 原子性边界 | 单个打开 file handle 的一次 identity + streaming digest observation |
| durable state | 无;claim/capability 仅存在于进程内 |
| 非法/伪造 claim | `gitoxide_helper_release_claim_invalid` |
| 平台或架构不匹配 | `gitoxide_helper_release_claim_unsupported` |
| path/symlink/读取失败 | `gitoxide_helper_artifact_invalid` |
| size/digest/identity 漂移 | `gitoxide_helper_artifact_identity_mismatch` |
| 错误 owner/伪造 capability | `gitoxide_helper_invocation_capability_invalid` |
| rollback | 只读校验,无副作用,无需回滚 |

## 5. 明确不承诺的威胁模型

本切片没有声称抵抗拥有同一 OS 用户文件写权限的主动攻击者。特别是:

- 它尚未验证 macOS code signature、Windows Authenticode 或 Linux 发布清单的受信签名;
- 它尚未把 helper 放进由正式安装器保护的只读目录;
- invocation 已接入 path-based spawn,但不能消除“最后一次 handle 校验完成后、exec 开始前”的替换
窗口;重复 rehash 只能缩小窗口,不能形成 executable CAS,因此本 Draft 明确保留该限制。

正式生产接入前,必须由 packaged-release owner 提供信任根,并明确三平台安装目录与签名能力。不能
通过给本 API 再传一个裸 expected digest 来绕过这一门槛。

## 6. 平台能力矩阵

| 平台 | 当前持续验证 | 尚未承诺 |
| --- | --- | --- |
| Linux | regular-file identity、digest、symlink path rejection | package signature、protected install root、spawn identity |
| macOS | 同 Linux | code-sign verification、notarized artifact binding、spawn identity |
| Windows | regular-file identity、digest、junction path rejection | Authenticode binding、ACL-protected install root、spawn identity |

## 7. 后续切片

后续只能按下面顺序推进:

1. 发布/安装 owner 把受信 helper identity 绑定到 signed product artifact;
2. 短生命周期 invocation owner 消费 opaque capability 并运行 strict helper protocol;该 stacked
Draft 的合同见 `gitoxide-helper-invocation-owner-v1.zh-CN.md`;
3. repository observation 再转换为 T1 前的 opaque admission capability。

在第 1 项完成以前,不接 Desktop/CLI,也不恢复旧 Git CLI adapter。
102 changes: 102 additions & 0 deletions docs/architecture/gitoxide-helper-invocation-owner-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Gitoxide short-lived invocation owner v1

状态:stacked Draft;真实 Rust helper 的三平台 contract 进入 CI,但仍无正式 release issuer、
Desktop/CLI/managed-workspace 生产消费者。

## 1. 主要不变量

本切片只证明:

> Runtime Host 只能通过 owner-bound opaque artifact capability 启动一个刚完成 artifact observation 的
> Gitoxide helper path;
> invocation 使用固定 strict JSON request、最小环境、有界 stdin/stdout/stderr、固定超时与取消边界;
> exit 0/1/2 必须分别匹配 inspected/operational failure/policy rejection 的 exact response shape,任意
> 不一致均 fail closed。

它不签发 repository admission capability,不写 SQLite/T1,不创建 Git artifact,也不接 Desktop/CLI。

## 2. Owner 与调用链

```text
opaque GitoxideHelperInvocationCapability
↓ invocation owner token 验证 + artifact bytes 重验
fixed argv [] / minimal env / no shell
↓ 64 KiB strict JSON request
one short-lived Rust helper
↓ bounded stdout/stderr + exact exit/response decoder
typed observation | typed policy rejection | stable error
```

caller 不能提供 executable path、argv、environment、protocol version、timeout 或 output limit。可变
业务输入只包括 operation 所需的 absolute repository path、显式 policy 与 AbortSignal;repository path
在 spawn 前 canonicalize。

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

| 项目 | v1 合同 |
| --- | --- |
| owner | 单次 Runtime Host invocation owner |
| 原子性边界 | artifact revalidation 后启动的一个 helper process 与其 exact response |
| 成功 | exit 0 + exact SHA-1 `repository_inspected` |
| policy rejection | exit 2 + exact `unsupported_object_format` |
| repository/helper failure | exit 1 + allowlisted stable helper reason |
| timeout | inspect 为 5 秒、source import 为 10 分钟;到期后 force-kill process tree,`gitoxide_helper_invocation_timed_out` |
| cancellation | preflight 或运行中 fail closed,`gitoxide_helper_invocation_aborted` |
| resource failure | repository open 前的本地 metadata 总量 1 MiB、Gitoxide object allocation 64 MiB、stdout 64 KiB、stderr 16 KiB;超限 fail closed 或 force-kill |
| malformed protocol | exit code、JSON shape、OID 或字段不一致均拒绝 |
| rollback | inspect 无 durable side effect;import 只允许 fresh destination,但 partial artifact cleanup 留给未来 storage owner |

Rust helper v1 不启动 descendants;Runtime 仍使用共享 process-tree terminator 处理 timeout、abort 和
output overflow,不允许常驻或 detached helper。

## 4. 配置与数据边界

- argv 固定为空,禁止 caller 注入 helper option;
- `shell: false`,不会经过 shell parsing;
- child `PATH` 为空,只保留 Windows loader 与临时目录所需的最少环境变量;
- Rust 侧在 `gix::open()` 前先有界解析 `.git`/`commondir` 路径文件并统计实际会读取的
`config`/`config.worktree`,总量超过 1 MiB 时返回
`repository_metadata_limit_exceeded`;随后使用 `gix::open::Options::isolated()`、
`lossy_config(true)` 与 `strict_config(true)`;
- request 最大 64 KiB;stdout 最大 64 KiB;stderr 最大 16 KiB;
- SHA-1 OID 必须是 40 位小写十六进制;SHA-256/未知格式只返回 rejection,禁止 fallback。

## 5. 平台证据

同一个 workflow 使用 release toolchain 的 Node 24.18.1,在 Linux、macOS、Windows 上:

1. 编译并测试 Rust helper;
2. 构建 Runtime Host;
3. 通过真实 helper executable 验证 SHA-1 success、SHA-256/未知格式 rejection、unborn SHA-1
failure、checksum mismatch 和 portable managed-tree policy。

该证据只覆盖 helper 协议、进程终止与 fresh-only import,不包含平台安装签名或恶意同用户替换。
当前实现会在 spawn 前完成 bytes/identity observation,但 Node 的 path-based spawn 不能把已打开并验证的
handle 直接作为 executable,因此 observation 与 exec 之间仍有 TOCTOU。正式 packaged-release owner
必须依赖平台签名和受保护安装目录;本 Draft 不声称抵抗拥有同用户写权限的攻击者。

## 6. 下一切片

同一 consolidated PR 的后续 authority layer 已把 exact repository observation 转换成 owner-bound
opaque admission capability,并用它驱动 fresh-only source import;合同见
`gitoxide-repository-admission-capability-v1.zh-CN.md`。仍不在本 PR 实现 durable T1、worktree
projection、candidate、recovery owner 或 ref CAS。
Loading