Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bounty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ body:
options:
- "P0 — public repository / 公开仓库"
- "P1 — redacted sparepack, interfaces and tests only / 脱敏任务包,只给接口和测试"
- "P2 — ephemeral Codespace sandbox / 一次性 Codespace 沙箱"
- "P2 — temporary trusted Codespace access / 临时受信 Codespace 访问"
- "P3 — trust circle, NDA required / 信任圈,需 NDA"
validations:
required: true
Expand Down
2 changes: 1 addition & 1 deletion .github/scan-allow.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Findings the credential scanner reports that have been read and judged safe to publish.
#
# Format, one per line: rule-id:path or rule-id:path:line
# Format, one per line: rule-id:path:line
# `#` starts a comment. Always leave one saying why — an entry with no reasoning is
# indistinguishable from someone silencing an inconvenient check.
#
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
- name: Ledger invariants
run: npm run ledger

- name: Settlement pull request evidence
env:
GITHUB_TOKEN: ${{ github.token }}
run: npm run ledger:prs

- name: Ledger stats check
run: npm run stats -- --check

Expand All @@ -48,6 +53,24 @@ jobs:
- name: Credential scan
run: npm run scan

- name: Pull request metadata scan
if: github.event_name == 'pull_request'
env:
TITLE: ${{ github.event.pull_request.title }}
BODY: ${{ github.event.pull_request.body }}
run: |
printf '%s\n%s' "$TITLE" "$BODY" | node scripts/scan-text.mjs > pr-metadata-verdict.json
cat pr-metadata-verdict.json
node -e '
const verdict = require("./pr-metadata-verdict.json");
if (verdict.block) {
for (const item of verdict.credentials) {
console.error(`::error::${item.label} (${item.severity}) at submitted-text line ${item.line}`);
}
process.exit(1);
}
'

# Only reaches PRs to this repository. Delivery PRs live in the requester's repo,
# where this workflow has no reach — see the note in check-attestation.mjs.
- name: Attestation checkboxes
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ jobs:
- name: Scan submitted text
id: scan
env:
TITLE: ${{ github.event.issue.title }}
BODY: ${{ github.event.comment.body || github.event.issue.body }}
run: |
printf '%s' "$BODY" | node scripts/scan-text.mjs > verdict.json
printf '%s\n%s' "$TITLE" "$BODY" | node scripts/scan-text.mjs > verdict.json
cat verdict.json
{
echo "block=$(node -p "require('./verdict.json').block")"
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Six transaction types only: `grant`, `escrow`, `settle`, `refund`, `split`, `adj
`transfer` and never will be** — that is COMPLIANCE red line 5 (TP are not transferable) expressed
as a data structure instead of a promise, and `verify.mjs` treats any unrecognised type as
tampering. Balances and escrow are checked incrementally at every entry, so a history that dips
negative mid-way and recovers is invalid. Schema and the nine invariants are in `ledger/README.md`.
negative mid-way and recovers is invalid. Schema and the eleven invariants are in `ledger/README.md`.

Settling a task by hand: append the entry, run `npm test` and `npm run ledger:write`, then update
`profiles/<handle>.md`.
Expand Down
46 changes: 32 additions & 14 deletions PRIVACY-TIERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,40 @@ Some tasks genuinely need the surrounding codebase — debugging an integration

---

## P2 · Ephemeral sandbox
## P2 · Temporary trusted access

The code never touches the worker's disk.
The worker can read and copy the complete visible repository. A Codespace keeps execution
off the worker's local machine, but it does not prevent downloads or retained copies.
Revoking access stops future reads; it cannot recall code the worker already saw.

1. Requester adds the worker as a collaborator, scoped to one dedicated branch via branch protection.
1. Requester grants collaborator access to the repository and uses branch protection to limit changes to one dedicated branch. Read access still covers the repository.
2. Worker opens a **Codespace on their own GitHub account** and runs **their own** Claude Code inside it.
3. Work happens, PR goes up, requester merges.
4. Requester removes the collaborator, deletes the branch. The Codespace expires on its own.
4. Requester removes the collaborator and deletes the branch. This ends future access only.

### Why Codespaces and not a container on the requester's machine

**Neither party is the other's host.** This is the whole point.
**Neither party runs code on the other's hardware.** This protects the worker's credentials,
not the requester's source from a worker who has already been granted access.

If the sandbox ran on the requester's hardware, the requester would be root on the box where the worker's `~/.claude/.credentials.json` is mounted. That turns a privacy feature into a credential-harvesting device — strictly worse than not doing it at all. Putting the sandbox on Microsoft's infrastructure makes it a neutral third party to both sides, and personal accounts get 60 free core-hours a month.

The cost is that your code passes through GitHub's cloud. For most projects that is already true. For the ones where it is not, see P3.

The Codespace belongs to the worker's account. Branch protection limits writes, not reads,
downloads, terminal commands, or copies to another location. Use P1 or a separate repository
containing only the minimum required code unless the worker may permanently retain everything
they can see.

### Checklist for the requester

- [ ] Branch protection restricts the worker to the task branch
- [ ] The worker is trusted with a permanent copy of every visible file
- [ ] A minimum-code temporary repository was considered before granting full-repository access
- [ ] Branch protection limits changes to the task branch and is not treated as read isolation
- [ ] Secrets and unrelated sensitive files are removed before access is granted
- [ ] No secrets in the repo's Codespaces secrets for that branch
- [ ] Repository-level Actions permissions reviewed before granting access
- [ ] Calendar reminder to revoke access after merge
- [ ] Calendar reminder to revoke future access after merge

---

Expand Down Expand Up @@ -203,29 +214,36 @@ npx sparepack init

---

## P2 · 一次性沙箱
## P2 · 临时受信访问

代码不落接单者磁盘。
接单者可以读取并复制完整的可见仓库。Codespace 只是让执行环境不在接单者的本地机器上,
它不能阻止下载或保留副本。撤权只能阻止后续读取,无法收回接单者已经看过的代码。

1. 发布者把接单者加为 collaborator,用分支保护限定在一个专用分支上
1. 发布者授予接单者仓库 collaborator 权限,并用分支保护把改动限制在专用分支;读取权限仍覆盖仓库
2. 接单者在**自己的 GitHub 账号下**开 Codespace,在里面跑**他自己的** Claude Code。
3. 干活,提 PR,发布者合并。
4. 发布者移除 collaborator、删分支。Codespace 自己会过期
4. 发布者移除 collaborator、删分支。这只能终止后续访问

### 为什么用 Codespaces 而不是发布者机器上的容器

**双方都不是对方的宿主。** 这就是全部理由
**双方都不在对方的硬件上运行代码。** 这保护的是接单者的凭证,不是已经授权给接单者读取的源码

如果沙箱跑在发布者的硬件上,发布者就是那台机器的 root,而接单者的 `~/.claude/.credentials.json` 正挂在里面。那样一个隐私功能就变成了凭证收割装置,比不做还糟。把沙箱放在微软的基础设施上,对双方而言它都是中立第三方,而且个人账号每月有 60 核时免费额度。

代价是代码要过 GitHub 的云。对大多数项目来说这本来就已经是事实了。不是的那些,看 P3。

Codespace 属于接单者账号。分支保护限制的是写入,不会限制读取、下载、终端命令或复制到别处。
除非你能接受接单者永久保留所有可见内容,否则应使用 P1,或单独建立只包含最低必要代码的临时仓库。

### 发布者检查清单

- [ ] 分支保护已把接单者限制在任务分支上
- [ ] 已确认接单者可以永久持有每一个可见文件的副本
- [ ] 授予完整仓库访问前,已经考虑过只放最低必要代码的临时仓库
- [ ] 分支保护只把改动限制在任务分支,没有被当作读取隔离
- [ ] 授权前已经移除密钥和无关敏感文件
- [ ] 该分支相关的 Codespaces secrets 里没有密钥
- [ ] 授权前复查过仓库级的 Actions 权限
- [ ] 设好合并后回收权限的提醒
- [ ] 设好合并后回收后续访问权限的提醒

---

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Three things stacked, each usable on its own:
|---|---|
| [**sparepack**](https://github.com/mxx1111/sparepack) | Cuts a minimal, redacted slice out of a private repo. Allowlist only, function bodies stripped to signatures, real data swapped for fixtures, and you review it file by file before anything is written. |
| **The board** | Hands that slice to whoever turns up, under a claim protocol that runs without a human awake. |
| **The ledger** | Append-only, nine invariants, every balance replayed from history by `node ledger/verify.mjs`. Two more invariants and API-verified settlements are in [#17](https://github.com/mxx1111/spare-cycles/pull/17), awaiting its author. |
| **The ledger** | Append-only, eleven invariants, every balance replayed from history by `node ledger/verify.mjs`, with settlement PRs verified against GitHub's API. |

This started as a mutual-aid board for people whose Claude Max limit runs out by Wednesday while someone else's rolls over unused. That framing was half right. The supply of people willing to do the work is real — six deliveries, no rewrites. The rest of it did not survive contact: **every task ever posted came from one account**, and the activity profiles of the accounts that arrived look nothing like the community it was built for.

Expand Down Expand Up @@ -54,7 +54,7 @@ Every task declares a privacy tier:
|---|---|---|
| **P0 · Public** | An ordinary open-source repo | The code is already public |
| **P1 · Redacted pack** | Interfaces, acceptance tests, fake fixtures. No business logic. | Default for private projects |
| **P2 · Ephemeral sandbox** | A Codespace on the requester's repo, revoked when done. Code never touches the worker's disk. | The task genuinely needs the real codebase |
| **P2 · Temporary trusted access** | A worker-controlled Codespace with full, copyable access to the visible repository. Revocation stops future access but cannot recall copies. | The task needs the real codebase and the worker is trusted with it |
| **P3 · Trust circle** | Full access under NDA | Established relationships only |

P1 is the interesting one. The `sparepack` CLI extracts a minimal reproducible subset from your private repo — allowlist only, nothing is exposed unless you name it — strips function bodies down to signatures, swaps real data for generated fixtures, scans for credentials and PII, and makes you review the output file by file before it writes anything. The worker implements against the tests. Your business logic never leaves your machine.
Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
|---|---|
| [**sparepack**](https://github.com/mxx1111/sparepack) | 从私有仓库切出一个最小的脱敏子集。白名单制,函数体剥成签名,真实数据换成 fixture,写盘前你逐个文件确认。 |
| **任务板** | 把这个子集交给出现的任何人,认领协议不需要有人醒着。 |
| **账本** | 只追加,九条不变量,`node ledger/verify.mjs` 从历史重放每一个余额。另外两条不变量和结算的 API 实证在 [#17](https://github.com/mxx1111/spare-cycles/pull/17) 里,等它的作者。 |
| **账本** | 只追加,十一条不变量,`node ledger/verify.mjs` 从历史重放每一个余额,并通过 GitHub API 实证结算 PR。 |

这东西最初是个互助板:有的周你周三就把额度烧穿了,有的周额度剩一大半到期作废,想把这两拨人接上。这个设想对了一半。愿意干活的供给侧是真的——六次交付,零返工。剩下那一半没扛住现实:**这块板子上发过的每一个任务都来自同一个账号**,而实际到来的那些账号的活动画像,和它当初设想的社区毫无相似之处。

Expand Down Expand Up @@ -56,7 +56,7 @@
|---|---|---|
| **P0 · 公开** | 一个普通的开源仓库 | 代码本来就是公开的 |
| **P1 · 脱敏包** | 接口签名、验收测试、假数据。没有业务逻辑。 | 私有项目的默认选择 |
| **P2 · 一次性沙箱** | 发布者仓库上的一个 Codespace,做完即回收。代码不落接单者磁盘。 | 任务确实需要真实代码库 |
| **P2 · 临时受信访问** | 接单者控制的 Codespace,可完整读取和复制可见仓库。撤权只能阻止后续访问,无法收回副本。 | 任务需要真实代码库,且发布者信任接单者持有它 |
| **P3 · 信任圈** | NDA 下的完整访问 | 只限已有信任关系 |

P1 是有意思的那个。`sparepack` 这个 CLI 从你的私有仓库里切出一个最小可复现子集,白名单制,你不点名的东西一概不暴露;把函数体剥掉只留签名,真实数据换成生成的假数据,扫一遍凭证和个人信息,然后在写盘之前逼你逐个文件过一遍。接单者对着测试写实现,你的业务逻辑压根没离开过你的机器。
Expand Down
Loading