https://github.com/cartyc/cve-comparision
https://github.com/cartyc/cve-comparision
Last synced: 26 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/cartyc/cve-comparision
- Owner: cartyc
- License: apache-2.0
- Created: 2026-06-23T23:29:07.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-24T01:36:43.000Z (about 1 month ago)
- Last Synced: 2026-06-24T02:23:35.476Z (about 1 month ago)
- Language: HTML
- Size: 46.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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).