From 0cb700742922f4ccc61dbdce95028e7ab2a355bc Mon Sep 17 00:00:00 2001 From: mesalogo Date: Tue, 25 Aug 2026 22:02:47 +0800 Subject: [PATCH] fix: unify GoodBuddy release signing identity Use one generic GoodBuddy Ed25519 identity for the Agent bundle, bundled Runtime, outer package, and signed catalog while preserving each layer's existing signature domain. Rename the production Environment contract to GOODBUDDY_SIGNING_*, register the matching production public key, and keep desktop and branch CI workflows isolated from the private key. --- .github/workflows/agent-release.yml | 12 +++++------ AGENTS.md | 10 +++++----- BUILD.md | 14 ++++++------- build/agent-bundle.cjs | 20 ++++++++++--------- build/remote-runtime-bundle.cjs | 10 +++++----- .../remote-host-and-goodbuddy-agent-design.md | 2 +- resources/agent-release-keys.json | 19 ++---------------- tests/agent-bundle.test.ts | 8 ++++---- tests/agent-release-workflow.test.ts | 4 ++-- tests/agents-workflow.test.ts | 2 +- tests/packages-workflow.test.ts | 2 +- tests/remote-runtime-bundle.test.ts | 4 ++-- 12 files changed, 47 insertions(+), 60 deletions(-) diff --git a/.github/workflows/agent-release.yml b/.github/workflows/agent-release.yml index d0deaf58..c0b0a357 100644 --- a/.github/workflows/agent-release.yml +++ b/.github/workflows/agent-release.yml @@ -189,10 +189,10 @@ jobs: } NODE - - name: Preflight production Agent signing identity + - name: Preflight production GoodBuddy signing identity shell: bash env: - GOODBUDDY_AGENT_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_AGENT_SIGNING_KEY_ID }} + GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }} run: | set -euo pipefail node build/agent-bundle.cjs preflight @@ -202,8 +202,8 @@ jobs: env: AGENT_ARCH: ${{ matrix.arch }} AGENT_VERSION: ${{ needs.authorize.outputs.version }} - GOODBUDDY_AGENT_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_AGENT_SIGNING_KEY_ID }} - GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY }} + GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }} + GOODBUDDY_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_SIGNING_PRIVATE_KEY }} NODE_ARCHIVE: ${{ steps.inputs.outputs.node-archive }} OPENCODE_ARCHIVE: ${{ steps.inputs.outputs.opencode-archive }} run: | @@ -344,8 +344,8 @@ jobs: env: AGENT_VERSION: ${{ needs.authorize.outputs.version }} PREVIOUS_ARGUMENTS: ${{ steps.previous.outputs.arguments }} - GOODBUDDY_AGENT_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_AGENT_SIGNING_KEY_ID }} - GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY }} + GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }} + GOODBUDDY_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_SIGNING_PRIVATE_KEY }} run: | set -euo pipefail generated_at="$(git show -s --format=%cI "$GITHUB_SHA")" diff --git a/AGENTS.md b/AGENTS.md index be5b80e7..f1c87c85 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -227,12 +227,12 @@ Release note: 修复左上角项目设置与消息通道项目设置不一致的 the checked-in public key registry. - `.github/workflows/agent-release.yml` is the only production compound Agent publication path. It requires an annotated immutable Agent tag, the protected - `agent-signing` Environment, native x64/arm64 builds, one production Agent - signing identity for every layer of the compound package and catalog, a - non-Latest GitHub Agent Release, and synchronized immutable Beijing OSS - objects plus the final signed latest-catalog pointer. + `agent-signing` Environment, native x64/arm64 builds, one production + GoodBuddy signing identity for every layer of the compound package and + catalog, a non-Latest GitHub Agent Release, and synchronized immutable + Beijing OSS objects plus the final signed latest-catalog pointer. - `.github/workflows/packages.yml` and `release:package` build only desktop - products. Missing Agent packages, remote Runtime inputs, or Agent signing + products. Missing Agent packages, remote Runtime inputs, or GoodBuddy signing credentials must never block an ordinary desktop package or release. - Keep electron-builder invocations on `--publish never`. Main-branch builds run validation and build the production bundle without running the native diff --git a/BUILD.md b/BUILD.md index 36ec3ca1..4f8edda8 100644 --- a/BUILD.md +++ b/BUILD.md @@ -197,15 +197,15 @@ Linux x64/arm64 复合包。每个 job 从 `agent-runtime-lock.json` 解析官 发布路径:只接受指向受保护 `main` 历史的 annotated `agent-v${agent-runtime-lock.agentVersion}` 标签,在 `agent-signing` Environment 中 原生构建两种架构,生成并签名累计目录,然后同步到非 Latest 的 GitHub Agent Release -与北京 OSS。该 Environment 只保护一组 GoodBuddy Agent 发布变量与 Secret: +与北京 OSS。该 Environment 只保护一组 GoodBuddy 通用发布变量与 Secret: -- `GOODBUDDY_AGENT_SIGNING_KEY_ID` / - `GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY` +- `GOODBUDDY_SIGNING_KEY_ID` / + `GOODBUDDY_SIGNING_PRIVATE_KEY` -该身份统一签署内部 Agent、固定 Runtime manifest、外层 `.gbagent` 描述符和累计目录, -各层通过既有签名域区分用途,不再要求内部 Runtime 单独配置生产身份。私钥只注入签名 -步骤;预检只读取 key ID 并确认公钥已在 `resources/agent-release-keys.json` -注册为 production 且未撤销。 +该 GoodBuddy 身份统一签署内部 Agent、固定 Runtime manifest、外层 `.gbagent` +描述符和累计目录,各层通过既有签名域区分用途,不再要求内部 Runtime 单独配置生产 +身份。私钥只注入签名步骤;预检只读取 key ID 并确认公钥已在 +`resources/agent-release-keys.json` 注册为 production 且未撤销。 累计目录使用 `agent-catalog.json` 与 `agent-catalog.sig`。每个条目绑定 Agent 版本、 最低桌面版本、Agent 协议、远端 OpenCode 版本/digest、架构、文件名、大小、SHA-256 diff --git a/build/agent-bundle.cjs b/build/agent-bundle.cjs index 888a1034..19c8419d 100644 --- a/build/agent-bundle.cjs +++ b/build/agent-bundle.cjs @@ -47,9 +47,9 @@ const manifestFileName = 'manifest.json' const signatureFileName = 'manifest.sig' const koffiPackageName = 'koffi' const signingPrivateKeyEnvironment = - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' const signingKeyIdEnvironment = - 'GOODBUDDY_AGENT_SIGNING_KEY_ID' + 'GOODBUDDY_SIGNING_KEY_ID' const semanticVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+((?:[0-9a-zA-Z-]+)(?:\.[0-9a-zA-Z-]+)*))?$/u @@ -372,7 +372,7 @@ function productionSigningKey(environment = process.env) { const privateKey = environment[signingPrivateKeyEnvironment] if (!keyId || !privateKey) { throw new Error( - `${signingKeyIdEnvironment} and ${signingPrivateKeyEnvironment} are required for production Agent signing` + `${signingKeyIdEnvironment} and ${signingPrivateKeyEnvironment} are required for production GoodBuddy signing` ) } return { keyId, privateKey } @@ -385,11 +385,11 @@ function preflightProductionSigningKey(options = {}) { const registry = options.registry ?? readTrustedKeyRegistry(projectRoot) return preflightRegisteredProductionKey({ - component: 'Agent', + component: 'GoodBuddy', keyId, registry, missingKeyIdMessage: - `${signingKeyIdEnvironment} is not configured; provision the production Agent signing key ID before building a release` + `${signingKeyIdEnvironment} is not configured; provision the production GoodBuddy signing key ID before building a release` }) } @@ -427,7 +427,7 @@ function trustedKeyForManifest( (candidate) => candidate.keyId === key.keyId ) if (revocation) { - throw new Error(`Agent signing key is revoked: ${key.keyId}`) + throw new Error(`GoodBuddy signing key is revoked: ${key.keyId}`) } return key } @@ -814,7 +814,9 @@ function buildAgentBundle(options) { publicKeySpkiBase64(signingIdentity.privateKey) !== signingRecord.publicKeySpkiBase64 ) { - throw new Error('Agent signing private key does not match the trusted key') + throw new Error( + 'GoodBuddy signing private key does not match the trusted key' + ) } if ( !options.testSigningIdentity && @@ -1105,7 +1107,7 @@ function parseArguments(argv) { } if (command === 'preflight') { if (Object.keys(options).length > 0) { - throw new Error('Agent signing preflight does not accept arguments') + throw new Error('GoodBuddy signing preflight does not accept arguments') } return { command } } @@ -1118,7 +1120,7 @@ function main(argv = process.argv.slice(2)) { if (options.command === 'preflight') { const signingRecord = preflightProductionSigningKey() console.log( - `Production Agent signing registry preflight passed: ${signingRecord.keyId}` + `Production GoodBuddy signing registry preflight passed: ${signingRecord.keyId}` ) return } diff --git a/build/remote-runtime-bundle.cjs b/build/remote-runtime-bundle.cjs index 0737ddb6..94073d96 100644 --- a/build/remote-runtime-bundle.cjs +++ b/build/remote-runtime-bundle.cjs @@ -44,9 +44,9 @@ const supportedArchitectures = Object.freeze(['x64', 'arm64']) const manifestFileName = 'manifest.json' const signatureFileName = 'manifest.sig' const signingPrivateKeyEnvironment = - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' const signingKeyIdEnvironment = - 'GOODBUDDY_AGENT_SIGNING_KEY_ID' + 'GOODBUDDY_SIGNING_KEY_ID' const signatureDomain = Buffer.from( 'GoodBuddy Remote Runtime Bundle Manifest Signature v1\0', 'utf8' @@ -318,11 +318,11 @@ function preflightProductionSigningKey(options = {}) { const registry = options.registry ?? readTrustedKeyRegistry(projectRoot) return preflightRegisteredProductionKey({ - component: 'Agent', + component: 'GoodBuddy', keyId, registry, missingKeyIdMessage: - `${signingKeyIdEnvironment} is not configured; provision the production Agent signing key ID before building a release` + `${signingKeyIdEnvironment} is not configured; provision the production GoodBuddy signing key ID before building a release` }) } @@ -1051,7 +1051,7 @@ function main(argv = process.argv.slice(2)) { if (options.command === 'preflight') { const record = preflightProductionSigningKey() console.log( - `Production Agent signing preflight passed for bundled Runtime: ${record.keyId}` + `Production GoodBuddy signing preflight passed for bundled Runtime: ${record.keyId}` ) return } diff --git a/docs/architecture/remote-host-and-goodbuddy-agent-design.md b/docs/architecture/remote-host-and-goodbuddy-agent-design.md index e99a9f8c..51cbe238 100644 --- a/docs/architecture/remote-host-and-goodbuddy-agent-design.md +++ b/docs/architecture/remote-host-and-goodbuddy-agent-design.md @@ -216,7 +216,7 @@ Renderer 暴露固定方法名、数字 RPC code 和有界 service code,不转 `.remote-runtime-resources` 或任何可安装 Linux 远端 payload。 - `build/agent-package.cjs` 在对应原生 Linux 架构组装确定性 `.gbagent`,外层签名覆盖 描述符和每个内部文件身份;内部 Agent 与 Runtime 仍分别使用既有签名 manifest 和 lock - 校验,但所有 production 层统一使用一组 GoodBuddy Agent 发布身份并通过签名域区分用途, + 校验,但所有 production 层统一使用一组 GoodBuddy 通用发布身份并通过签名域区分用途, 不要求内部 Runtime 单独配置密钥。`build/agent-catalog.cjs` 为双架构包生成签名累计目录, 拒绝同一版本/架构改变字节。 - `.github/workflows/agents.yml` 只使用进程内临时测试 key 做分支/PR 原生验证,不发布。 diff --git a/resources/agent-release-keys.json b/resources/agent-release-keys.json index 60fdfae5..f0255474 100644 --- a/resources/agent-release-keys.json +++ b/resources/agent-release-keys.json @@ -2,23 +2,8 @@ "formatVersion": 1, "keys": [ { - "keyId": "e2e-20260823-020914-ce95d6f6", - "publicKeySpkiBase64": "MCowBQYDK2VwAyEA8w12MNcxOrU18MArvcuJmVkGKzJsa/r93q14HGX7DUU=", - "environment": "production" - }, - { - "keyId": "goodbuddy-e2e-agent-8b0b39d2e63d7cf163c23bb7", - "publicKeySpkiBase64": "MCowBQYDK2VwAyEAx5lS8/stctKe/ri3kSNLR3nmkTV1eTpS0c1TYtJuHoM=", - "environment": "production" - }, - { - "keyId": "goodbuddy-e2e-v8-de8355f998071143411bc075", - "publicKeySpkiBase64": "MCowBQYDK2VwAyEAY7rOjqPT3FAw2+F5roTqyssz4Zzzx0iJhk6tGhCVivc=", - "environment": "production" - }, - { - "keyId": "goodbuddy-e2e-v12-b5adef071b79b4fdec57ea98", - "publicKeySpkiBase64": "MCowBQYDK2VwAyEATKD9wXjCeC35SMDrczzpou5PusRkxqSgNb5Se/jYOFU=", + "keyId": "goodbuddy-signing-2026-08", + "publicKeySpkiBase64": "MCowBQYDK2VwAyEAfUkXqa3WYwb1QevpHW/XWNhGo59dETJYjrJONx6SKEQ=", "environment": "production" } ], diff --git a/tests/agent-bundle.test.ts b/tests/agent-bundle.test.ts index c7902aa3..5c80c947 100644 --- a/tests/agent-bundle.test.ts +++ b/tests/agent-bundle.test.ts @@ -512,7 +512,7 @@ describe('Agent production input handling', () => { it('requires CI-only production key variables and redacts them', () => { expect(() => agentBundle.productionSigningKey({}) - ).toThrow('required for production Agent signing') + ).toThrow('required for production GoodBuddy signing') const secret = [ '-----BEGIN PRIVATE KEY-----', @@ -521,8 +521,8 @@ describe('Agent production input handling', () => { ].join('\n') const error = `signing_key=${secret} key id prod-secret-id` const redacted = agentBundle.redactSecrets(error, { - GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY: secret, - GOODBUDDY_AGENT_SIGNING_KEY_ID: 'prod-secret-id' + GOODBUDDY_SIGNING_PRIVATE_KEY: secret, + GOODBUDDY_SIGNING_KEY_ID: 'prod-secret-id' }) expect(redacted).not.toContain('fixture-secret-material') expect(redacted).not.toContain('prod-secret-id') @@ -536,7 +536,7 @@ describe('Agent production input handling', () => { registry }) ).toThrow( - 'Production Agent public signing key ID "production-release-key" is absent from resources/agent-release-keys.json' + 'Production GoodBuddy public signing key ID "production-release-key" is absent from resources/agent-release-keys.json' ) }) diff --git a/tests/agent-release-workflow.test.ts b/tests/agent-release-workflow.test.ts index 4eaffa17..5d429d38 100644 --- a/tests/agent-release-workflow.test.ts +++ b/tests/agent-release-workflow.test.ts @@ -97,7 +97,7 @@ describe('independent Agent release workflow', () => { ) expect(buildScripts).toContain('--ignore-scripts') expect(build).toContain( - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' ) expect(workflow).not.toContain( 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_' @@ -169,7 +169,7 @@ describe('independent Agent release workflow', () => { it('keeps private keys out of authorization and public publication jobs', () => { expect(serializedJob('authorize')).not.toContain('secrets.') expect(serializedJob('publish')).not.toContain( - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' ) }) }) diff --git a/tests/agents-workflow.test.ts b/tests/agents-workflow.test.ts index e815e57a..3e6dfe48 100644 --- a/tests/agents-workflow.test.ts +++ b/tests/agents-workflow.test.ts @@ -162,7 +162,7 @@ describe('GoodBuddy Agents workflow', () => { ) expect(workflow).not.toContain('secrets.') expect(workflow).not.toContain( - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' ) expect(ciBundleSource).toContain( "generateKeyPairSync('ed25519')" diff --git a/tests/packages-workflow.test.ts b/tests/packages-workflow.test.ts index 35ba2f73..8c504aec 100644 --- a/tests/packages-workflow.test.ts +++ b/tests/packages-workflow.test.ts @@ -76,7 +76,7 @@ describe('desktop packages workflow', () => { expect(workflow).not.toContain('agent-signing:') expect(workflow).not.toContain('remote-runtime-signing:') expect(workflow).not.toContain( - 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_SIGNING_PRIVATE_KEY' ) }) diff --git a/tests/remote-runtime-bundle.test.ts b/tests/remote-runtime-bundle.test.ts index 2ed62e5e..32a3b77a 100644 --- a/tests/remote-runtime-bundle.test.ts +++ b/tests/remote-runtime-bundle.test.ts @@ -315,7 +315,7 @@ describe('Remote Runtime bundle tooling', () => { runtimeBundle.preflightProductionSigningKey({ projectRoot: process.cwd(), environment: { - GOODBUDDY_AGENT_SIGNING_KEY_ID: + GOODBUDDY_SIGNING_KEY_ID: 'runtime-fixture' }, registry: productionRegistry @@ -326,7 +326,7 @@ describe('Remote Runtime bundle tooling', () => { }) }) - it('signs the bundled Runtime with the shared Agent identity', () => { + it('signs the bundled Runtime with the shared GoodBuddy identity', () => { const productionRegistry: AgentReleaseKeyRegistry = { ...registry, keys: registry.keys.map((key) => ({