mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-27 18:42:41 +00:00
ci: encrypt the test backend's keys that the Claude jobs carry over, to a key only those jobs hold
The iOS and desktop app jobs saved their stopped test backend (server keys and databases) as a public artifact, so that the Claude job could restart it with the same addresses: anyone signed in to GitHub could download it for a day and act as the test directory or relay while the Claude job ran. backend-state.sh now packs it as CMS AuthEnvelopedData (AES-256-GCM, RSA-OAEP key transport) to the committed backend-state.crt; the Claude job decrypts it with the environment secret BACKEND_STATE_KEY, given only to that step, so the backend it restarts and the Claude session don't inherit it. Without the secret the Claude job stops at that step, before the Claude session.
This commit is contained in:
1 parent
57cde366ff
commit
65a8b7d2f5
7 files changed
+100
-14
No files matched your search
@@ -306,14 +306,14 @@ jobs:
|
|||||||
# the app points at this backend's directory, so the Claude job starts this backend again rather than a new one
|
# the app points at this backend's directory, so the Claude job starts this backend again rather than a new one
|
||||||
- name: Pack backend state
|
- name: Pack backend state
|
||||||
if: ${{ !cancelled() && steps.backend.outcome == 'success' && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
if: ${{ !cancelled() && steps.backend.outcome == 'success' && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
||||||
run: tar -czf "$RUNNER_TEMP/backend-state.tar.gz" --exclude=./bin -C "$RUNNER_TEMP/backend" .
|
run: scripts/ci/backend-state.sh pack "$RUNNER_TEMP/backend" "$RUNNER_TEMP/backend-state.cms"
|
||||||
|
|
||||||
- name: Upload backend state
|
- name: Upload backend state
|
||||||
if: ${{ !cancelled() && steps.backend.outcome == 'success' && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
if: ${{ !cancelled() && steps.backend.outcome == 'success' && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: desktop-backend-state
|
name: desktop-backend-state
|
||||||
path: ${{ runner.temp }}/backend-state.tar.gz
|
path: ${{ runner.temp }}/backend-state.cms
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -405,14 +405,18 @@ jobs:
|
|||||||
name: desktop-backend-state
|
name: desktop-backend-state
|
||||||
path: ${{ runner.temp }}
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
|
# only this step gets the key, so nothing the backend or Claude starts later inherits it
|
||||||
|
- name: Unpack backend state
|
||||||
|
if: needs.plan.outputs.claude-backend == 'yes'
|
||||||
|
env:
|
||||||
|
BACKEND_STATE_KEY: ${{ secrets.BACKEND_STATE_KEY }}
|
||||||
|
run: scripts/ci/backend-state.sh unpack "$RUNNER_TEMP/backend-state.cms" "$RUNNER_TEMP/backend"
|
||||||
|
|
||||||
- name: Start the app job's test backend again
|
- name: Start the app job's test backend again
|
||||||
id: backend
|
id: backend
|
||||||
if: needs.plan.outputs.claude-backend == 'yes'
|
if: needs.plan.outputs.claude-backend == 'yes'
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
run: |
|
run: scripts/ci/desktop-linux/backend.sh resume ~/backend/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
|
||||||
mkdir -p "$RUNNER_TEMP/backend"
|
|
||||||
tar -xzf "$RUNNER_TEMP/backend-state.tar.gz" -C "$RUNNER_TEMP/backend"
|
|
||||||
scripts/ci/desktop-linux/backend.sh resume ~/backend/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
|
|
||||||
|
|
||||||
# ui goes on every shell's PATH, where Claude's Bash tool finds it whatever environment it builds
|
# ui goes on every shell's PATH, where Claude's Bash tool finds it whatever environment it builds
|
||||||
- name: Install Claude Code and the ui command
|
- name: Install Claude Code and the ui command
|
||||||
|
|||||||
@@ -288,14 +288,14 @@ jobs:
|
|||||||
# the app points at this backend's directory, so the Claude job starts this backend again rather than a new one
|
# the app points at this backend's directory, so the Claude job starts this backend again rather than a new one
|
||||||
- name: Pack backend state
|
- name: Pack backend state
|
||||||
if: ${{ !cancelled() && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
if: ${{ !cancelled() && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
||||||
run: tar -czf "$RUNNER_TEMP/backend-state.tar.gz" --exclude=./bin -C "$RUNNER_TEMP/backend" .
|
run: scripts/ci/backend-state.sh pack "$RUNNER_TEMP/backend" "$RUNNER_TEMP/backend-state.cms"
|
||||||
|
|
||||||
- name: Upload backend state
|
- name: Upload backend state
|
||||||
if: ${{ !cancelled() && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
if: ${{ !cancelled() && steps.stop-backend.outcome == 'success' && needs.plan.outputs.claude-backend == 'yes' }}
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ios-sim-backend-state
|
name: ios-sim-backend-state
|
||||||
path: ${{ runner.temp }}/backend-state.tar.gz
|
path: ${{ runner.temp }}/backend-state.cms
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -377,14 +377,18 @@ jobs:
|
|||||||
name: ios-sim-backend-state
|
name: ios-sim-backend-state
|
||||||
path: ${{ runner.temp }}
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
|
# only this step gets the key, so nothing the backend or Claude starts later inherits it
|
||||||
|
- name: Unpack backend state
|
||||||
|
if: needs.plan.outputs.claude-backend == 'yes'
|
||||||
|
env:
|
||||||
|
BACKEND_STATE_KEY: ${{ secrets.BACKEND_STATE_KEY }}
|
||||||
|
run: scripts/ci/backend-state.sh unpack "$RUNNER_TEMP/backend-state.cms" "$RUNNER_TEMP/backend"
|
||||||
|
|
||||||
- name: Start the app job's test backend again
|
- name: Start the app job's test backend again
|
||||||
id: backend
|
id: backend
|
||||||
if: needs.plan.outputs.claude-backend == 'yes'
|
if: needs.plan.outputs.claude-backend == 'yes'
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
run: |
|
run: scripts/ci/ios-sim/backend.sh resume ~/Downloads/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
|
||||||
mkdir -p "$RUNNER_TEMP/backend"
|
|
||||||
tar -xzf "$RUNNER_TEMP/backend-state.tar.gz" -C "$RUNNER_TEMP/backend"
|
|
||||||
scripts/ci/ios-sim/backend.sh resume ~/Downloads/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
|
|
||||||
|
|
||||||
- name: Install AXe
|
- name: Install AXe
|
||||||
run: scripts/ci/ios-sim/install-axe.sh
|
run: scripts/ci/ios-sim/install-axe.sh
|
||||||
|
|||||||
@@ -36,3 +36,11 @@ A newer run of a workflow on the same branch cancels the older one.
|
|||||||
## The Claude job
|
## The Claude job
|
||||||
|
|
||||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. The environment's branch rule alone decides which branches may use it: on a branch it doesn't allow, a run that asks for Claude fails at the Claude job's environment check, and the scripted test runs as usual. Each run spends the subscription's usage limits; the job summary gives its tokens and cost estimated at API prices.
|
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. The environment's branch rule alone decides which branches may use it: on a branch it doesn't allow, a run that asks for Claude fails at the Claude job's environment check, and the scripted test runs as usual. Each run spends the subscription's usage limits; the job summary gives its tokens and cost estimated at API prices.
|
||||||
|
|
||||||
|
A Claude job that starts the app job's test backend again (iOS and desktop, when `claude-scope` says `backend: yes`) also needs the environment's secret `BACKEND_STATE_KEY`, the private key of `backend-state.crt`, to decrypt that backend's keys and databases (`backend-state.sh`). A new pair replaces both:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 -subj /CN=simplex-chat-ci-backend-state -keyout key.pem -out scripts/ci/backend-state.crt
|
||||||
|
gh secret set BACKEND_STATE_KEY --env app-sim-claude --repo simplex-chat/simplex-chat < key.pem
|
||||||
|
rm key.pem
|
||||||
|
```
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Carries the test backend's stopped work dir, its server keys and databases, from the app job to the Claude job as an
|
||||||
|
# artifact, which anyone signed in to GitHub can download from this public repository, so it is encrypted to
|
||||||
|
# backend-state.crt, whose private key only the Claude job holds (the secret BACKEND_STATE_KEY in app-sim-claude).
|
||||||
|
# Usage:
|
||||||
|
# scripts/ci/backend-state.sh pack <work dir> <file> writes the work dir without its binaries, encrypted, to <file>
|
||||||
|
# scripts/ci/backend-state.sh unpack <file> <work dir> decrypts <file> into <work dir> with the key in BACKEND_STATE_KEY
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cert=$(dirname "$0")/backend-state.crt
|
||||||
|
|
||||||
|
# OpenSSL 3 on both runners: macOS's own openssl is LibreSSL
|
||||||
|
if [ "$(uname)" = Darwin ]; then
|
||||||
|
openssl=$(brew --prefix openssl@3)/bin/openssl
|
||||||
|
else
|
||||||
|
openssl=openssl
|
||||||
|
fi
|
||||||
|
|
||||||
|
pack() {
|
||||||
|
tar -czf - --exclude=./bin -C "$1" . |
|
||||||
|
"$openssl" cms -encrypt -binary -aes-256-gcm -recip "$cert" -keyopt rsa_padding_mode:oaep -outform DER -out "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack() {
|
||||||
|
: "${BACKEND_STATE_KEY:?not set: the environment app-sim-claude needs this secret}"
|
||||||
|
mkdir -p "$2"
|
||||||
|
"$openssl" cms -decrypt -binary -inform DER -in "$1" -inkey <(printf '%s\n' "$BACKEND_STATE_KEY") |
|
||||||
|
tar -xzf - -C "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
pack) pack "$2" "$3" ;;
|
||||||
|
unpack) unpack "$2" "$3" ;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 pack <work dir> <file> | unpack <file> <work dir>"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -41,7 +41,7 @@ It writes the directory's address to `<dir>/directory-link` and the SMP server's
|
|||||||
|
|
||||||
## The Claude job
|
## The Claude job
|
||||||
|
|
||||||
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's distributable, one per branch at a time. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too. The job starts the test backend when `claude-scope` says `backend: yes`, as it does on this branch; a run that asks for Claude must then ask for the backend too, or the plan job fails. The app job built the distributable pointing at its own backend's directory, which a new backend would not have, so the app job saves that backend's stopped work dir (the artifact `desktop-backend-state`, without the binaries) and the Claude job starts it again with `backend.sh resume`: the same servers' keys and databases, so the same addresses. That artifact holds the test backend's databases and server keys, unencrypted, and like every artifact of this public repository anyone signed in to GitHub can download it while it is kept (a day); with it, someone could act as the test directory or relay while the Claude job runs and show the session crafted results. This is an accepted risk: these are throwaway test identities, the session has no shell and no token in reach of its tools, so what is at stake is the test's integrity, and encrypting the state would need a key only the two jobs hold, a second secret, which this repository's rules for the Claude job don't allow. With the backend it sets the app up at the chat list (`smoke-test.sh … setup`) and gives Claude `claude-search.md`, the online search test, in place of the onboarding path in `claude-test.md`: the directory search with its consent, Open, the load row and the right-click menu (part A) and the Search key, which searches the directory only and keeps "Connect to …" (part B), in both layouts, then part C, which opens `ci_channel` with its row's Open, without an alert, joins it, and checks that later searches show it as a chat (no row and no note for a page of chats already listed, its chat row among the results of the chat list and of New chat, opening without an alert). The job installs Claude Code at the version the iOS workflow pins too, and runs `claude-haiku-5-5` with `--max-turns 120`, `--max-budget-usd 3` and the iOS sessions' other flags (`--restricted`, `--permission-prompts none`, `--effort high`). Its Bash runs only single `ui` commands (`claude-guard.py`, a PreToolUse hook: Claude Code runs read-only commands without asking in every permission mode). Before anything the session wrote is printed or uploaded, `claude-test.sh` checks it for the token; if it's there, the output, transcript, Claude Code's stderr and the guard log are deleted and the job fails. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`; `report.md`, the transcript, the guard's log and `claude-usage.json` are in `desktop-claude-test`.
|
It needs the GitHub environment `app-sim-claude` with the secret `CLAUDE_CODE_OAUTH_TOKEN`, a Claude subscription token. Which branches may use it is decided only by that environment's branch rule in the repository settings: on a branch the rule doesn't allow, a requested Claude run fails at the environment check. It runs once the app job has passed, on that job's distributable, one per branch at a time. The token goes only to the step that runs `claude`; `claude-test.sh` passes it to the `claude` process alone, and every `ui` call unsets it too. The job starts the test backend when `claude-scope` says `backend: yes`, as it does on this branch; a run that asks for Claude must then ask for the backend too, or the plan job fails. The app job built the distributable pointing at its own backend's directory, which a new backend would not have, so the app job saves that backend's stopped work dir (the artifact `desktop-backend-state`, without the binaries) and the Claude job starts it again with `backend.sh resume`: the same servers' keys and databases, so the same addresses. Anyone signed in to GitHub can download this public repository's artifacts, and with those keys could act as the test directory or relay while the Claude job runs and show the session crafted results, so `scripts/ci/backend-state.sh` encrypts the work dir to `scripts/ci/backend-state.crt`. Its private key is the environment's second secret, `BACKEND_STATE_KEY`, which only the step that unpacks the state gets, so neither the backend it restarts nor the Claude session inherits it (`scripts/ci/README.md` says how to make a new one). With the backend it sets the app up at the chat list (`smoke-test.sh … setup`) and gives Claude `claude-search.md`, the online search test, in place of the onboarding path in `claude-test.md`: the directory search with its consent, Open, the load row and the right-click menu (part A) and the Search key, which searches the directory only and keeps "Connect to …" (part B), in both layouts, then part C, which opens `ci_channel` with its row's Open, without an alert, joins it, and checks that later searches show it as a chat (no row and no note for a page of chats already listed, its chat row among the results of the chat list and of New chat, opening without an alert). The job installs Claude Code at the version the iOS workflow pins too, and runs `claude-haiku-5-5` with `--max-turns 120`, `--max-budget-usd 3` and the iOS sessions' other flags (`--restricted`, `--permission-prompts none`, `--effort high`). Its Bash runs only single `ui` commands (`claude-guard.py`, a PreToolUse hook: Claude Code runs read-only commands without asking in every permission mode). Before anything the session wrote is printed or uploaded, `claude-test.sh` checks it for the token; if it's there, the output, transcript, Claude Code's stderr and the guard log are deleted and the job fails. It passes when Claude's final message ends with `CLAUDE-TEST: PASS`; `report.md`, the transcript, the guard's log and `claude-usage.json` are in `desktop-claude-test`.
|
||||||
|
|
||||||
## Cost
|
## Cost
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ The macOS jobs run on `macos-26`, the image with Xcode 26 and the iPhone 17 simu
|
|||||||
|
|
||||||
## The Claude session
|
## The Claude session
|
||||||
|
|
||||||
It runs in its own job, `claude`, once the app job has passed: the job installs that job's build (the artifact `ios-sim-app`, kept a day) in a new simulator, runs the smoke test to reach the sessions' starting state, then one session per layout listed in `claude-scope`, with Claude Code installed from npm at the version the desktop workflow pins too. Only this job references the environment `app-sim-claude`, which holds the secret `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) and admits the branches its rule allows; one runs per branch at a time. Only the Claude step gets the token, and `claude-test.sh` passes it to the `claude` processes alone. The job checks out without persisting its GitHub token and installs a pinned AXe (`install-axe.sh`), since it holds the Claude token. It starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since the smoke plan doesn't use the backend. The app job built the app pointing at its own backend's directory, which a new backend would not have, so the app job saves that backend's stopped work dir (the artifact `ios-sim-backend-state`, without the binaries) and the Claude job starts it again with `backend.sh resume`: the same servers' keys and databases, so the same addresses. That artifact holds the test backend's databases and server keys, unencrypted, and like every artifact of this public repository anyone signed in to GitHub can download it while it is kept (a day); with it, someone could act as the test directory or relay while the Claude job runs and show the session crafted results. This is an accepted risk: these are throwaway test identities, the session has no shell and no token in reach of its tools, so what is at stake is the test's integrity, and encrypting the state would need a key only the two jobs hold, a second secret, which this repository's rules for the Claude job don't allow.
|
It runs in its own job, `claude`, once the app job has passed: the job installs that job's build (the artifact `ios-sim-app`, kept a day) in a new simulator, runs the smoke test to reach the sessions' starting state, then one session per layout listed in `claude-scope`, with Claude Code installed from npm at the version the desktop workflow pins too. Only this job references the environment `app-sim-claude`, which holds the secret `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) and admits the branches its rule allows; one runs per branch at a time. Only the Claude step gets the token, and `claude-test.sh` passes it to the `claude` processes alone. The job checks out without persisting its GitHub token and installs a pinned AXe (`install-axe.sh`), since it holds the Claude token. It starts the test backend when `claude-scope` says `backend: yes`; a run that asks for Claude must then ask for the backend too, or the plan job fails. On master it says `no`, since the smoke plan doesn't use the backend. The app job built the app pointing at its own backend's directory, which a new backend would not have, so the app job saves that backend's stopped work dir (the artifact `ios-sim-backend-state`, without the binaries) and the Claude job starts it again with `backend.sh resume`: the same servers' keys and databases, so the same addresses. Anyone signed in to GitHub can download this public repository's artifacts, and with those keys could act as the test directory or relay while the Claude job runs and show the session crafted results, so `scripts/ci/backend-state.sh` encrypts the work dir to `scripts/ci/backend-state.crt`. Its private key is the environment's second secret, `BACKEND_STATE_KEY`, which only the step that unpacks the state gets, so neither the backend it restarts nor the Claude session inherits it (`scripts/ci/README.md` says how to make a new one).
|
||||||
|
|
||||||
Each session's final message ends with `CLAUDE-TEST: PASS`, or `CLAUDE-TEST: FAIL` and the failed cases; the step fails unless every session passes.
|
Each session's final message ends with `CLAUDE-TEST: PASS`, or `CLAUDE-TEST: FAIL` and the failed cases; the step fails unless every session passes.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user