An open API service indexing awesome lists of open source software.

https://github.com/cartyc/cve-comparision


https://github.com/cartyc/cve-comparision

Last synced: 26 days ago
JSON representation

Awesome Lists containing this project

README

          

# Chainguard Remediated Libraries — Demo

Two identical applications (Java + Python), each built four ways — on a UBI 10 base
and a Chainguard base, with unremediated and remediated library sets. Scan them with
grype to separate the CVE delta from the **base image** versus from **Chainguard's
remediated libraries**.

## Contents

- [Structure](#structure)
- [What each app does](#what-each-app-does)
- [Scan results](#scan-results-grype-jun-2026)
- [Key CVEs eliminated](#key-cves-eliminated)
- [Prerequisites](#prerequisites)
- [Credentials](#credentials)
- [Run the demo](#run-the-demo)
- [CI — build, sign, push to GHCR](#ci--build-sign-push-to-ghcr)
- [License](#license)

---

## Structure

Each app builds in four variants — two base images (UBI 10 and Chainguard) × two
library sets (unremediated and remediated):

```
remediation-demo/
java/
pom-unremediated.xml # Spring Boot 2.7.0 / spring-web 5.3.20 (vulnerable)
pom-remediated.xml # Spring Boot 2.7.0 / spring-web 5.3.39-0.cgr.4 (remediated)
Dockerfile.unremediated # UBI 10: build on openjdk-21, run on ubi-minimal + JRE
Dockerfile.remediated # UBI 10 + java-remediated libraries
Dockerfile.unremediated-cgr # Chainguard jdk (ARG CGR_ORG): cgr.dev//jdk
Dockerfile.remediated-cgr # Chainguard jdk + java-remediated libraries
settings-remediated.xml.template
src/...
python/
requirements-unremediated.txt # PyPI, pinned vulnerable versions
requirements-remediated.txt # libraries.cgr.dev/python-remediated/
Dockerfile.unremediated # UBI 10: ubi-minimal + python3 (3.12)
Dockerfile.remediated # UBI 10 + python-remediated libraries
Dockerfile.unremediated-cgr # Chainguard python:3.12 (ARG CGR_ORG)
Dockerfile.remediated-cgr # Chainguard python:3.12 + python-remediated libraries
app.py
scan.sh
cve-report.html # rendered side-by-side CVE comparison
```

> `scan.sh` builds the two UBI variants per language (tagged `demo--unremediated`
> / `demo--remediated`). The `-cgr` Chainguard variants are built directly with
> `docker build` — see each Dockerfile's header comment for the exact command.

---

## What each app does

**Java** — Spring Boot REST API exercising spring-web's `UriComponentsBuilder` and JSON handling:
- `GET /health` liveness check
- `GET /link?base=&path=` build a URI via `UriComponentsBuilder` (exercises CVE-2024-22243)
- `POST /people` accept and echo a list of `Person` objects (JSON via spring-web)
- `GET /greet/{name}` simple greeting

**Python** — FastAPI app exercising three remediated libraries:
- `GET /health` liveness check
- `POST /token` generate a signed JWT (`pyjwt`)
- `GET /fetch?url=...` proxy an HTTP GET (`urllib3`)
- `POST /upload` parse a multipart file upload (`python-multipart`)

---

## Scan results (grype, Jun 2026)

CVEs are split by layer: **app** = application libraries (java-archive / pip), **OS** =
base-image packages. See `cve-report.html` for the full severity breakdown.

### Java — Spring Boot 2.7.0

| Image | Runtime base | App | OS | Total |
|---|---|---|---|---|
| unremediated `:ubi10` | ubi-minimal + JRE | 77 | 151 | **228** |
| remediated `:ubi10` | ubi-minimal + JRE | 66 | 151 | **217** |
| unremediated `:cgr` | cgr jdk:openjdk-17 | 77 | 0 | **77** |
| **remediated `:cgr`** | cgr jdk:openjdk-17 | 66 | 0 | **66** |

### Python — FastAPI / PyJWT / urllib3 / python-multipart

| Image | Runtime base | App | OS | Total |
|---|---|---|---|---|
| unremediated `:ubi10` | ubi-minimal + python3 (3.12) | 33 | 168 | **201** |
| remediated `:ubi10` | ubi-minimal + python3 (3.12) | 27 | 168 | **195** |
| unremediated `:cgr` | cgr python:3.12 | 29 | 14 | **43** |
| **remediated `:cgr`** | cgr python:3.12 | 23 | 14 | **37** |

Both `:cgr` images use the org-pinned `python:3.12` tag (Python 3.12.13, matching UBI)
and the same library pins as their UBI counterparts, so UBI-vs-Chainguard is apples-to-apples.

### Reading these numbers

- **Base image is the dominant lever.** Moving Java from UBI 10 to the Chainguard JDK
drops all 151 OS CVEs (228 → 77). Library remediation removes a further 11 (→ 66).
- **Grype false-positives on `+cgr` versions.** Grype does not recognise the
`+cgr.N` local version label as a patched backport, so remediated packages are still
matched against upstream advisories. The app-layer reduction shown here therefore
*understates* the true remediation — the backported fixes are present in the packages.
- **Python remediation is a clean app-layer cut.** With the same base, the OS layer is
identical between unremediated and remediated, so remediation shows a pure app-layer
drop (−6 on both bases: 33→27 on UBI, 29→23 on cgr; High 16→11).
- **Python `:cgr` uses the org-pinned `python:3.12` image**, which is far more aggressively
patched than the public `:latest` (OS layer 14, vs ~99 on `:latest` 3.14). Pinned tags
need org access — pass `--build-arg CGR_ORG=`.

## Key CVEs eliminated

### Java — spring-web 5.3.20 → 5.3.39-0.cgr.4
| Advisory | Severity | Description |
|---|---|---|
| GHSA-4wrc-f8pq-fpqp | Critical | Spring URL parsing bypass |
| GHSA-7phw-cxx7-q9vq | Critical | Spring WebMVC open redirect |
| GHSA-g5vr-rgqm-vf78 | High | Spring path traversal |
| GHSA-ccgv-vj62-xf9h | High | Spring web header injection |
| GHSA-hgjh-9rj2-g67j | High | Spring path traversal via URL |
| GHSA-2wrp-6fg6-hmc5 | High | Spring open redirect bypass |

### Python — vulnerable PyPI pins → libraries.cgr.dev/python-remediated/
| Library | Unremediated | Remediated | Top CVEs eliminated |
|---|---|---|---|
| PyJWT | 2.3.0 | 2.3.0+cgr.2 | GHSA-ffqj-6fqr-9h24 (High), GHSA-xgmm-8j9v-c9wx (High) |
| urllib3 | 1.26.8 | 1.26.18+cgr.1 | GHSA-v845-jxx5-vc9f (High), GHSA-qccp-gfcp-xxvc (High) |
| python-multipart | 0.0.6 | 0.0.6+cgr.1 | GHSA-wp53-j4wj-2cfg (High), GHSA-2jv5-9r88-3w3p (High) |

---

## Prerequisites

- Docker with BuildKit (`export DOCKER_BUILDKIT=1`)
- [grype](https://github.com/anchore/grype)
- Chainguard pull tokens (see below)

---

## Credentials

### Java remediated
```bash
chainctl auth pull-token --repository=java --parent=

export CHAINGUARD_JAVA_IDENTITY_ID=
export CHAINGUARD_JAVA_TOKEN=
```

### Python remediated
```bash
chainctl auth pull-token --repository=python --parent=

export CHAINGUARD_PYTHON_IDENTITY_ID= # replace any "/" with "_"
export CHAINGUARD_PYTHON_TOKEN=
```

> Credentials are never baked into image layers. The token (password) is passed
> via a BuildKit `--mount=type=secret`; the username is passed as a `--build-arg`.
> Both are short-lived — treat them as ephemeral and never commit them.

---

## Run the demo

```bash
chmod +x scan.sh

./scan.sh java # Java only
./scan.sh python # Python only
./scan.sh all # Both
```

---

## CI — build, sign, push to GHCR

[`.github/workflows/build-sign-push.yml`](.github/workflows/build-sign-push.yml) builds the
images, pushes them to `ghcr.io///:`, and signs each with
**cosign keyless** (GitHub OIDC + Sigstore — no signing keys to manage). It runs on pushes
to `main` and via manual dispatch.

Each build also generates an **SBOM + SLSA provenance** attestation
(`--sbom --provenance=mode=max`), scans the image with **grype** and uploads SARIF to
GitHub **code scanning** (informational — the unremediated images are intentionally
vulnerable), and (for Node) runs the malware-feed gate. Node deps install from a
committed **lockfile** (`npm ci`); Python deps install from committed, fully-hashed
lockfiles (`pip install --require-hashes -r requirements-*.lock`).

A single Chainguard **assumable identity** (assumed via GitHub OIDC with
`chainguard-dev/setup-chainctl`) provides everything credentialed: private `:cgr`
base-image pulls **and** freshly minted library pull tokens for the remediated builds
(`chainctl auth pull-token create`). No long-lived registry or library secrets are
stored. Any image needing the identity is **skipped, not failed** when it's unset — so
the two public unremediated UBI images build even on forks.

### Hardened GitHub Actions

The third-party actions in the workflow are pulled from
[`chainguard-actions`](https://github.com/chainguard-actions) — hardened, drop-in
rebuilds with identical inputs/outputs. Each is built from source, has every internal
`uses:`/image reference pinned to an immutable SHA, and ships a `HARDENING.md` report.
We pin each by **commit SHA** (defence against tag-repointing attacks like the 2025
`tj-actions` compromise). `chainguard-dev/setup-chainctl` is Chainguard's own
first-party action and is used directly.

| In use (SHA-pinned) | Replaces | Version | Hardening findings fixed |
|---|---|---|---|
| `chainguard-actions/actions-checkout` | `actions/checkout` | v4.3.1 | none (rebuilt; internal refs SHA-pinned) |
| `chainguard-actions/docker-setup-buildx-action` | `docker/setup-buildx-action` | v4.1.0 | none |
| `chainguard-actions/docker-login-action` | `docker/login-action` | v4.2.0 | none |
| `chainguard-actions/sigstore-cosign-installer` | `sigstore/cosign-installer` | v4.1.2 | **2 (High)** — see below |

Per its `HARDENING.md`, the cosign-installer mirror fixed two high-severity issues in
the upstream `action.yml`:
- **`github-env-injection`** — the user-controlled `install-dir` input was written to
`$GITHUB_PATH` without newline sanitization (Linux/macOS and Windows steps), allowing
arbitrary `PATH` entries to be injected.
- **`script-injection`** — unquoted shell expansions of `${{ runner.os }}` / `${{ runner.arch }}`
in `case` statements, vulnerable to shell-metacharacter injection.

Configure under **Settings → Secrets and variables → Actions → Variables**:

| Variable | Value | Used by |
|---|---|---|
| `CGR_ORG` | your org, e.g. `example.com` | all `:cgr` images + library token minting |
| `CGR_IDENTITY` | UIDP of the assumable identity | every credentialed image (6 of 8) |

Create the identity scoped to this repo's `main` branch and grant it **registry pull**
(for `:cgr` base images) **and library pull-token create** (for the remediated builds);
see the workflow header for details.

**Optional JFrog mirror.** If `JFROG_USERNAME` / `JFROG_PASSWORD` secrets are set, each
image is also mirrored (by digest, no rebuild) to
`chainguardlibraries.jfrog.io/carty-demo-images/:` and signed there too. The
mirror is skipped — not failed — when those secrets are absent.

**Malware check (Node).** The Node images run [`node/malware-check.sh`](node/malware-check.sh)
against the built image: it enumerates installed npm dependencies and cross-references
them against the Chainguard Libraries malware feed
(`libraries.cgr.dev/javascript/-/api/malware`), failing the build if any package is
flagged at its exact installed version. Requires the identity's org to be entitled to
the `javascript` ecosystem (same as the remediated Node builds).

### Verify the signed images

Each image is signed keyless, so verification asserts it was built by this repo's
workflow (no key distribution). The packages are private by default — authenticate to
GHCR first (skip if the packages are made public):

```bash
echo "$GITHUB_TOKEN" | docker login ghcr.io -u --password-stdin
# or: gh auth token | docker login ghcr.io -u --password-stdin
```

Verify a single image:

```bash
cosign verify ghcr.io/cartyc/cve-comparision/python-remediated:cgr \
--certificate-identity-regexp "^https://github.com/cartyc/cve-comparision/.github/workflows/build-sign-push.yml@refs/heads/main$" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
-o text
```

Verify all eight at once:

```bash
REPO=ghcr.io/cartyc/cve-comparision
for img in java-unremediated java-remediated python-unremediated python-remediated; do
for tag in ubi10 cgr; do
echo "== $img:$tag =="
cosign verify "$REPO/$img:$tag" \
--certificate-identity-regexp "^https://github.com/cartyc/cve-comparision/.github/workflows/build-sign-push.yml@refs/heads/main$" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
>/dev/null && echo "OK" || echo "FAILED"
done
done
```

The `--certificate-identity-regexp` pins the signer to this workflow on `main`; the
`--certificate-oidc-issuer` pins it to GitHub's OIDC provider. Both must match or
verification fails.

---

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).