12 KiB
Post-Slack Merge Blockers — Remediation Plan
Status: Active
Created: 2026-03-13
Branch: feature/beta-release
Context: Slack notification provider is functionally complete. Four blockers remain before merge.
1. Executive Summary
| # | Blocker | Severity | Effort | Fix Available |
|---|---|---|---|---|
| 1 | Patch coverage short by 15 backend lines (Slack commit) | Medium | ~1 hr | Yes — add unit tests |
| 2 | 2 HIGH vulnerabilities in Docker image (binutils) |
Low | None | No — no upstream fix; build-time only |
| 3 | anchore/sbom-action uses Node.js 20 |
Medium | Blocked | No — upstream has not released a node24 build |
| 4 | 13 MEDIUM vulnerabilities in Docker image | Mixed | ~30 min | 1 fixable (zlib), 12 unfixable (no upstream fix) |
Bottom line: Blocker 1 is the only item requiring code changes. Blockers 2/3/4 are environmental and require either upstream fixes, documented risk acceptance, or a single apk upgrade in the Dockerfile.
2. Blocker 1: Patch Coverage — 15 Uncovered Backend Lines
Methodology
Computed by cross-referencing git diff HEAD~1...HEAD (Slack commit 26be592f) against backend/coverage.txt using Go's atomic coverage profile. Only non-test .go files in the Slack commit are considered.
Totals: 63 changed source lines, 15 uncovered → 76.2% patch coverage.
Uncovered Lines
File: backend/internal/api/handlers/notification_provider_handler.go (9 lines)
| Lines | Code | Description |
|---|---|---|
| 141–143 | return "PROVIDER_TEST_VALIDATION_FAILED", "validation", "Slack rejected the payload..." |
Error classification for invalid_payload / missing_text_or_fallback Slack API errors |
| 145–147 | return "PROVIDER_TEST_AUTH_REJECTED", "dispatch", "Slack webhook is revoked..." |
Error classification for no_service Slack API error |
| 325–327 | respondSanitizedProviderError(c, http.StatusBadRequest, "TOKEN_WRITE_ONLY", ...) + return |
Guard preventing Slack webhook URL from being sent in test-notification requests |
File: backend/internal/services/notification_service.go (6 lines)
| Lines | Code | Description |
|---|---|---|
| 462–463 | marshalErr branch → return fmt.Errorf("failed to normalize slack payload: %w", marshalErr) |
Error path when json.Marshal fails during Slack payload normalization (message → text rewrite) |
| 466–467 | writeErr branch → return fmt.Errorf("failed to write normalized slack payload: %w", writeErr) |
Error path when body.Write fails after normalization |
| 549–550 | return fmt.Errorf("slack webhook URL is not configured") |
Guard when decrypted Slack webhook URL is empty at dispatch time |
Proposed Test Additions
All tests go in existing test files alongside the current Slack test cases.
1. notification_provider_handler_test.go — Slack error classification (covers lines 141–147)
Add test cases to the classifySlackProviderTestError test table:
- Input error containing
"invalid_payload"→ assert returnsPROVIDER_TEST_VALIDATION_FAILED - Input error containing
"missing_text_or_fallback"→ assert returnsPROVIDER_TEST_VALIDATION_FAILED - Input error containing
"no_service"→ assert returnsPROVIDER_TEST_AUTH_REJECTED
2. notification_provider_handler_test.go — Slack TOKEN_WRITE_ONLY guard (covers lines 325–327)
Add a test case to the test-notification endpoint tests:
- Send a test-notification request with
type=slackand a non-emptytokenfield - Assert HTTP 400 with error code
TOKEN_WRITE_ONLY
3. notification_service_test.go — Slack payload normalization errors (covers lines 462–467)
Add test cases to the Slack dispatch tests:
- Provide a payload with a
messagefield but inject a marshal failure (e.g., via a value that causesjson.Marshalto fail such asmath.NaNor a channel-based mock) - Alternatively, test the
message→textnormalization happy path (which exercises lines 459–467 inclusive) and use a mockbody.Writethat returns an error
4. notification_service_test.go — Empty Slack webhook URL (covers lines 549–550)
Add a test case:
- Create a Slack provider with an empty/whitespace-only Token (webhook URL)
- Call dispatch
- Assert error contains
"slack webhook URL is not configured"
3. Blocker 2: 2 HIGH Vulnerabilities
Findings
| CVE | Package | Version | CVSS | Fix Available | Source |
|---|---|---|---|---|---|
| CVE-2025-69650 | binutils |
2.45.1-r0 | 7.5 | No | grype-results.json |
| CVE-2025-69649 | binutils |
2.45.1-r0 | 7.5 | No | grype-results.json |
Analysis
- CVE-2025-69650: Double-free in
readelfwhen processing crafted ELF files. - CVE-2025-69649: Null pointer dereference in
readelfwhen processing crafted ELF files.
Both affect GNU Binutils, which is present in the Alpine image as a build dependency pulled in by gcc/musl-dev for CGo compilation. These are:
- Build-time only —
binutilsis not used at runtime by Charon - Not exploitable — requires processing a malicious ELF file via
readelf, which Charon never invokes - No upstream fix — Alpine has not released a patched
binutils - Pre-existing — present before the Slack commit
Remediation
- Action: Document as accepted risk in the PR description
- Rationale: Build-toolchain-only vulnerability with no runtime exposure. No fix available upstream.
- Review trigger: Re-evaluate when Alpine releases
binutils >= 2.47or patches the 2.45.1 package
4. Blocker 3: anchore/sbom-action Uses Node.js 20
Current State
| Workflow | File | Line |
|---|---|---|
| Docker Build | docker-build.yml |
577 |
| Nightly Build | nightly-build.yml |
266 |
| Supply Chain PR | supply-chain-pr.yml |
269 |
| Supply Chain Verify | supply-chain-verify.yml |
122 |
All four reference the same pin: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1
v0.23.1 (released 2026-03-10) is the latest release. Its action.yml declares runs.using: "node20".
Analysis
- GitHub Actions is deprecating Node.js 20 actions (targeting Node.js 24 as the successor runtime).
anchore/sbom-actionhas not released a node24-compatible version yet. The project transitioned from node16→node20 around v0.17.x.- No open issue or PR on the
anchore/sbom-actionrepository tracks node24 migration. - The current pin at v0.23.1 /
57aae52is the best available version.
Remediation
| Option | Action | Risk |
|---|---|---|
| A) Wait (recommended) | Keep current pin. Monitor anchore/sbom-action releases for a node24 build. Renovate will auto-propose the update. |
GitHub will show deprecation warnings but will not break the action until the hard cutoff. |
| B) Suppress warning | Add ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true env var to the workflow steps. |
Masks the warning but does not fix the underlying issue. Not recommended. |
| C) Fork and patch | Fork the action, change node20 to node24, rebuild dist. |
Maintenance burden; likely breaks without code changes to support Node 24 APIs. |
Recommendation: Option A. Pin stays at v0.23.1 / 57aae528053a48a3f6235f2d9461b05fbcb7366d. Document in PR description as a known upstream dependency awaiting update.
5. Blocker 4: 13 MEDIUM Vulnerabilities
Full Catalog
All from grype-results.json (Docker image scan of Alpine 3.23.3 base).
| # | CVE | Package | Version | Fix | Category |
|---|---|---|---|---|---|
| 1 | CVE-2025-60876 | busybox |
1.37.0-r30 | None | Unfixable |
| 2 | CVE-2025-60876 | busybox-binsh |
1.37.0-r30 | None | Unfixable (same CVE) |
| 3 | CVE-2025-60876 | busybox-extras |
1.37.0-r30 | None | Unfixable (same CVE) |
| 4 | CVE-2025-60876 | ssl_client |
1.37.0-r30 | None | Unfixable (same CVE) |
| 5 | CVE-2025-14819 | curl |
8.17.0-r1 | None | Unfixable |
| 6 | CVE-2025-15079 | curl |
8.17.0-r1 | None | Unfixable |
| 7 | CVE-2025-14524 | curl |
8.17.0-r1 | None | Unfixable |
| 8 | CVE-2025-13034 | curl |
8.17.0-r1 | None | Unfixable |
| 9 | CVE-2025-14017 | curl |
8.17.0-r1 | None | Unfixable |
| 10 | CVE-2025-69652 | binutils |
2.45.1-r0 | None | Unfixable |
| 11 | CVE-2025-69644 | binutils |
2.45.1-r0 | None | Unfixable |
| 12 | CVE-2025-69651 | binutils |
2.45.1-r0 | None | Unfixable |
| 13 | CVE-2026-27171 | zlib |
1.3.1-r2 | 1.3.2-r0 | Fixable |
Grouping
| Category | Entries | Unique CVEs | Packages |
|---|---|---|---|
| BusyBox wget CRLF injection | 4 | 1 (CVE-2025-60876) | busybox, busybox-binsh, busybox-extras, ssl_client |
| curl TLS/SSH edge cases | 5 | 5 distinct | curl 8.17.0-r1 |
| binutils readelf issues | 3 | 3 distinct | binutils 2.45.1-r0 |
| zlib vulnerability | 1 | 1 (CVE-2026-27171) | zlib 1.3.1-r2 |
Remediation
Fixable (1 vuln):
| CVE | Fix |
|---|---|
CVE-2026-27171 (zlib) |
Add RUN apk upgrade --no-cache zlib to Dockerfile runtime stage, or bump Alpine base if 3.23.4+ ships with zlib 1.3.2 |
Unfixable (12 vulns):
| Package | Risk | Mitigation |
|---|---|---|
busybox (CVE-2025-60876) |
Low — wget CRLF injection. Charon does not invoke wget at runtime. |
Accept risk; monitor Alpine. |
curl (5 CVEs) |
Low–Medium — TLS/SSH edge cases. Charon uses Go net/http, not curl. Present only for health checks. |
Accept risk; consider removing curl from runtime image. |
binutils (3 CVEs) |
Low — build-time only (readelf DoS). Not in runtime path. |
Accept risk; monitor Alpine. |
6. Commit Slicing Strategy
Decision: 2 PRs
| PR | Scope | Files | Validation |
|---|---|---|---|
| PR-1: Coverage + zlib fix | Unit tests for 15 uncovered Slack lines + apk upgrade zlib in Dockerfile |
notification_provider_handler_test.go, notification_service_test.go, Dockerfile |
go test ./..., re-run grype scan, regenerate patch report |
| PR-2: Risk acceptance docs | Document accepted risks for unfixable vulns + SBOM node20 | PR description or .github/security-accepted-risks.md |
Review-only |
Trigger Reasons
- PR-1 is code (tests + Dockerfile) — requires CI
- PR-2 is documentation/process — review-only, no CI risk
- Splitting allows PR-1 to merge quickly while risk discussions happen asynchronously
Rollback
- PR-1: Safe to revert — only adds tests and an
apk upgrade. No behavioral changes. - PR-2: Documentation only.
7. Execution Order
| Step | Action | Blocker | Effort |
|---|---|---|---|
| 1 | Add unit tests for 15 uncovered Slack lines | 1 | ~45 min |
| 2 | Add apk upgrade --no-cache zlib to Dockerfile runtime stage |
4 (partial) | ~5 min |
| 3 | Re-run backend tests + coverage, regenerate patch report | 1 verification | ~10 min |
| 4 | Re-run Docker image scan (grype/trivy) | 4 verification | ~5 min |
| 5 | Open PR-1 with test + Dockerfile changes | — | ~10 min |
| 6 | Document risk acceptance for unfixable vulns + SBOM node20 in PR-2 | 2, 3, 4 | ~15 min |
Acceptance Criteria
- All 15 uncovered Slack lines have corresponding unit test cases
- Backend patch coverage for Slack commit ≥ 95%
zlibupgraded to ≥ 1.3.2-r0 in Docker image- Docker image scan shows 0 fixable MEDIUM+ vulnerabilities
- Unfixable vulnerabilities documented with risk acceptance rationale
anchore/sbom-actionnode20 status documented; pin unchanged at v0.23.1