fix(security): harden server and service deletion lifecycle (#1220)

* test: TDD regression tests for GHSA-jx78-55p5-rwv5 stream quota enforcement

* Apply remaining changes

* fix: update action SHA allowlist and test assertions to match dependabot bump

* fix: close GHSA-jx78-55p5-rwv5 incomplete fix of GHSA-qjpp-gffx-2wm9

Finding 1 (Moderate): nil-guard reporterServer in delayCheck and notifyCheck.
ServerShared has its own lock independent of serviceResponseDataStoreLock, so
m := ServerShared.GetList() taken inside the worker can return a nil entry for
the reporter if the server was concurrently deleted. Previously this caused an
unrecovered SIGSEGV in the worker goroutine (and in the gRPC layer with no
recovery interceptor), taking down the whole instance.

Finding 2 (Low): nil-guard ss.services[id] in ServiceSentinel.Delete().
A caller-supplied id that is absent from the registry caused
ss.services[id].CronJobID to panic, aborting the Delete loop and leaving every
subsequent valid id as a zombie service (DB row deleted, in-memory entry kept,
cron probe still running).

Regression tests added for both findings following the existing
servicesentinel_lifecycle_test.go patterns.

* Apply remaining changes

* chore: replace commit hashes with version tags in test.yml

* fix(server): serialize authoritative lifecycle changes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(service): bind reports to reporter lifecycle

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(rpc): reject results from stale task streams

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(agentcompat): allow version-tagged actions

* fix(agentcompat): allow literal checkout refs

* refactor(agentcompat): remove SHA resolver policy

* test(agentcompat): remove resolver SHA fixtures

* test(agentcompat): remove mutable ref fixtures

* test(agentcompat): use tagged actions in secure fixtures

* test(agentcompat): update credential fixtures for tags

* test(agentcompat): update reusable action fixtures

* test(agentcompat): update artifact redaction fixtures

* test(agentcompat): finish artifact fixture tag migration

* test(agentcompat): update workflow validation fixtures

* test(agentcompat): update dependency workflow fixture

* ci(agentcompat): stop pinning cross-repository revisions

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: naiba <hi@nai.ba>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Copilot
2026-08-01 15:45:20 +08:00
committed by GitHub
co-authored by Sisyphus naiba
parent bb941e4d73
commit 9ec6164f58
46 changed files with 1064 additions and 520 deletions
@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/checkout
with:
persist-credentials: false
@@ -9,6 +9,6 @@ jobs:
timeout-minutes: 10
steps:
- run: go test ./...
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
with:
path: ${{ runner.temp }}/results
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/cache@0123456789abcdef0123456789abcdef01234567
- uses: actions/cache@v4
with:
path: bin
key: executable-cache
@@ -12,7 +12,7 @@ jobs:
id: redact-evidence
if: false
run: go run ./integration/agentcompat/cmd/redact --output "$RUNNER_TEMP/nezha-agentcompat-redacted"
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
if: always()
with:
path: ${{ runner.temp }}/nezha-agentcompat-redacted
@@ -8,8 +8,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
repository: nezhahq/agent
ref: main
persist-credentials: false
@@ -8,4 +8,4 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/download-artifact@0123456789abcdef0123456789abcdef01234567
- uses: actions/download-artifact@v7
@@ -8,4 +8,4 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
@@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
- uses: actions/setup-go@v7
with:
go-version: "1.26.x"
cache: false
@@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
- uses: actions/setup-go@v7
with:
go-version: "1.26.x"
cache: false
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Redact evidence
run: go run ./integration/agentcompat/cmd/redact
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
with:
path: |
${{ runner.temp }}/redacted-results
@@ -10,6 +10,6 @@ jobs:
steps:
- name: Redact evidence
run: "true"
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
with:
path: ${{ runner.temp }}/redacted-results
@@ -8,8 +8,8 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
repository: ${{ inputs.repository }}
ref: 0123456789abcdef0123456789abcdef01234567
ref: main
persist-credentials: false
@@ -9,4 +9,4 @@ jobs:
timeout-minutes: 10
steps:
- uses:
- actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- actions/checkout@v7.0.1
@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10.5
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: true
@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: "false"
@@ -13,7 +13,7 @@ jobs:
if: always()
run: go run ./integration/agentcompat/cmd/redact --output "$RUNNER_TEMP/nezha-agentcompat-redacted"
- run: cp raw-secret "$RUNNER_TEMP/nezha-agentcompat-redacted/raw-secret"
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
if: always()
with:
path: ${{ runner.temp }}/nezha-agentcompat-redacted
@@ -14,7 +14,7 @@ jobs:
run: |
go run ./integration/agentcompat/cmd/redact --output "$RUNNER_TEMP/nezha-agentcompat-redacted"
cp raw-secret "$RUNNER_TEMP/nezha-agentcompat-redacted/raw-secret"
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
if: always()
with:
path: ${{ runner.temp }}/nezha-agentcompat-redacted
@@ -1,25 +0,0 @@
on:
pull_request:
concurrency: policy
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- id: resolve-agent
run: |
set -euo pipefail
remote='https://github.com/nezhahq/agent.git'
mapfile -t refs < <(git ls-remote "$remote" refs/heads/main)
(( ${#refs[@]} == 1 ))
sha=${refs[0]%%$'\t'*}
[[ "$sha" =~ ^[0-9a-f]{40}$ ]]
sha=attacker-controlled
printf 'sha=%s\n' "$sha" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
repository: nezhahq/agent
ref: ${{ steps.resolve-agent.outputs.sha }}
persist-credentials: false
@@ -9,13 +9,13 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
repository: nezhahq/nezha
ref: fedcba9876543210fedcba9876543210fedcba98
ref: master
path: nezha
persist-credentials: false
- run: go test ./integration/agentcompat/...
@@ -15,14 +15,14 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout Nezha
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- name: Checkout Agent
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@v7.0.1
with:
repository: nezhahq/agent
ref: 0123456789abcdef0123456789abcdef01234567
ref: v1.2.3
path: agent
persist-credentials: false
- name: Test
@@ -34,7 +34,7 @@ jobs:
if: always()
run: go run ./integration/agentcompat/cmd/redact --output "$RUNNER_TEMP/nezha-agentcompat-redacted"
- name: Upload redacted evidence
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@v6
if: always()
with:
path: ${{ runner.temp }}/nezha-agentcompat-redacted
@@ -1,27 +0,0 @@
name: Secure resolved ref
on:
pull_request:
concurrency: resolved-${{ github.ref }}
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- id: resolve-agent
name: Resolve Agent commit
shell: bash
run: |
set -euo pipefail
remote='https://github.com/nezhahq/agent.git'
mapfile -t refs < <(git ls-remote "$remote" refs/heads/main)
(( ${#refs[@]} == 1 ))
sha=${refs[0]%%$'\t'*}
[[ "$sha" =~ ^[0-9a-f]{40}$ ]]
printf 'sha=%s\n' "$sha" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
repository: nezhahq/agent
ref: ${{ steps.resolve-agent.outputs.sha }}
persist-credentials: false
@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
- uses: actions/setup-go@v7
with:
go-version: 1.26.3
@@ -8,4 +8,4 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: attacker/exfiltrate@0123456789abcdef0123456789abcdef01234567
- uses: attacker/exfiltrate@v1
@@ -8,8 +8,8 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@v7.0.1
with:
repository: attacker/fork
ref: 0123456789abcdef0123456789abcdef01234567
ref: main
persist-credentials: false
@@ -11,6 +11,6 @@ jobs:
- name: Redact evidence
id: redact-evidence
run: go run ./integration/agentcompat/cmd/redact
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v6
with:
path: ${{ runner.temp }}/results
@@ -1,18 +0,0 @@
on:
pull_request:
concurrency: policy
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- id: resolve-agent
run: |
echo "sha=$(git ls-remote https://github.com/nezhahq/agent.git refs/heads/main | cut -f1)" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
repository: nezhahq/agent
ref: ${{ steps.resolve-agent.outputs.sha }}
persist-credentials: false