Files
Charon/docs/plans/current_spec.md

12 KiB
Raw Blame History

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
141143 return "PROVIDER_TEST_VALIDATION_FAILED", "validation", "Slack rejected the payload..." Error classification for invalid_payload / missing_text_or_fallback Slack API errors
145147 return "PROVIDER_TEST_AUTH_REJECTED", "dispatch", "Slack webhook is revoked..." Error classification for no_service Slack API error
325327 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
462463 marshalErr branch → return fmt.Errorf("failed to normalize slack payload: %w", marshalErr) Error path when json.Marshal fails during Slack payload normalization (messagetext rewrite)
466467 writeErr branch → return fmt.Errorf("failed to write normalized slack payload: %w", writeErr) Error path when body.Write fails after normalization
549550 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 141147)

Add test cases to the classifySlackProviderTestError test table:

  • Input error containing "invalid_payload" → assert returns PROVIDER_TEST_VALIDATION_FAILED
  • Input error containing "missing_text_or_fallback" → assert returns PROVIDER_TEST_VALIDATION_FAILED
  • Input error containing "no_service" → assert returns PROVIDER_TEST_AUTH_REJECTED

2. notification_provider_handler_test.go — Slack TOKEN_WRITE_ONLY guard (covers lines 325327)

Add a test case to the test-notification endpoint tests:

  • Send a test-notification request with type=slack and a non-empty token field
  • Assert HTTP 400 with error code TOKEN_WRITE_ONLY

3. notification_service_test.go — Slack payload normalization errors (covers lines 462467)

Add test cases to the Slack dispatch tests:

  • Provide a payload with a message field but inject a marshal failure (e.g., via a value that causes json.Marshal to fail such as math.NaN or a channel-based mock)
  • Alternatively, test the messagetext normalization happy path (which exercises lines 459467 inclusive) and use a mock body.Write that returns an error

4. notification_service_test.go — Empty Slack webhook URL (covers lines 549550)

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 readelf when processing crafted ELF files.
  • CVE-2025-69649: Null pointer dereference in readelf when 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:

  1. Build-time onlybinutils is not used at runtime by Charon
  2. Not exploitable — requires processing a malicious ELF file via readelf, which Charon never invokes
  3. No upstream fix — Alpine has not released a patched binutils
  4. 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.47 or 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-action has 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-action repository tracks node24 migration.
  • The current pin at v0.23.1 / 57aae52 is 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) LowMedium — 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%
  • zlib upgraded 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-action node20 status documented; pin unchanged at v0.23.1