elastic / elastic/elastic-agent
[upgrade-lifecycle] Upgrade panic when package manifest is absent in checkUpgrade
- Dominant language
- Go
- Stars
- 275
- Forks
- 264
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 312
Description
## Findings
### 1. Nil manifest dereference in FIPS compatibility check causes upgrade panic (**HIGH**, **P1**)
**Platform(s):** Linux, Windows, macOS (shared upgrade path)
**Location**
- `internal/pkg/agent/application/upgrade/upgrade.go:269-275`
- `internal/pkg/agent/application/upgrade/upgrade.go:439-446`
- `internal/pkg/agent/application/upgrade/step_unpack.go:90-117`
- `internal/pkg/agent/application/upgrade/step_unpack.go:622-647`
**Evidence**
`checkUpgrade` unconditionally dereferences `metadata.manifest.Package.Fips`:
```go
if currentVersion.fips && !metadata.manifest.Package.Fips { ... }
if !currentVersion.fips && metadata.manifest.Package.Fips { ... }
```
`Upgrade` calls `checkUpgrade` immediately after `getPackageMetadata`.
`getPackageMetadata` does **not** require a manifest to exist (it only rejects empty `VersionedHome` when `manifest != nil`), and tar metadata parsing explicitly allows no manifest (`ret.manifest` remains nil).
This means a valid artifact with commit hash but no manifest reaches `checkUpgrade` and panics on nil pointer dereference.
**What is wrong**
The upgrade path supports metadata without a manifest, but `checkUpgrade` assumes manifest is always present.
**Why it matters**
A normal user upgrade path (especially legacy/older artifact formats) can crash the agent during upgrade orchestration, leaving upgrade incomplete and requiring manual recovery/rollback handling.
**Suggested fix direction**
1. Make `checkUpgrade` nil-safe:
- If `metadata.manifest == nil`, skip FIPS compatibility checks (or apply a documented default policy), and continue version/hash checks.
2. Add explicit unit coverage for nil-manifest behavior.
**Test code to add (failing before fix)**
- File: `internal/pkg/agent/application/upgrade/upgrade_test.go` (extend `TestCheckUpgrade`)
- Add cases where `metadata.manifest == nil` for both FIPS and non-FIPS current versions.
- Assert `checkUpgrade` does not panic and returns deterministic error/allow behavior.
Example shape:
```go
assert.NotPanics(t, func() {
err := checkUpgrade(log, current, newVersion, packageMetadata{manifest:nil, hash:"abcdef"})
// assert expected err based on chosen nil-manifest policy
})
```
## Priority ranking
1. **P1 (common upgrade path risk):** Nil-manifest panic in `checkUpgrade` (all platforms).
## Upgrade paths audited and found safe in this run
- Marker write/read and cleanup flow around `step_mark.go`, `marker_watcher.go`, `cleanup.go`.
- Watcher startup/rollback integration path in `internal/pkg/agent/cmd/watch.go` and `watch_impl.go` (excluding already-tracked clock-skew grace issue).
- Rollback orchestration paths in `rollback.go` and platform variants; no additional verified HIGH defects found in this pass.
---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Sweeper: Upgrade and Rollback Lifecycle](https://github.com/elastic/elastic-agent/actions/runs/27199248520)
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Jun 16, 2026, 10:26 AM UTC
Contributor guide
Research direction
Start with checkUpgrade and getPackageMetadata in internal/pkg/agent/application/upgrade/upgrade.go, then inspect manifest handling in step_unpack.go. Extend internal/pkg/agent/application/upgrade/upgrade_test.go and TestCheckUpgrade with nil-manifest cases for FIPS and non-FIPS versions. Done means the cases no longer panic and return the agreed deterministic result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100