# Vulnerability Remediation

> Reviews vulnerability policy violations for the chainloop project recorded in Chainloop and performs fixes in Dockerfiles or go.mod. Use when asked to fix vulnerabilities, review CVEs, or remediate security issues in chainloop.

- Skill: `chainloop-dev/vulnerability-remediation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add chainloop-dev/vulnerability-remediation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/chainloop-dev/vulnerability-remediation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: chainloop-dev (https://skillmd.com/u/chainloop-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/chainloop-dev/vulnerability-remediation

---


# Vulnerability Remediation for chainloop

This skill reviews open vulnerability policy violations recorded in Chainloop for the `chainloop` project and applies fixes to the affected source files.

## Step 1: Find the Latest Project Version

Use `list_products_with_versions` (no parameters needed — uses the current `chainloop` org) and locate the **Chainloop Community Edition** product. Find the `chainloop` project version entry and note its `projectVersionId` (UUID).

## Step 2: Gather Compliance Results and Evidence in Parallel

Once the `projectVersionId` is known, make both of these calls **at the same time**:

**Call A** — `get_frameworks_compliance`:
- `project_version_id`: the UUID from Step 1
- `framework_ids`: `["0ceef195-6900-4166-8407-77eb84954ed3"]` (chainloop-best-practices)

**Call B** — `list_pieces_of_evidence`:
- `project_name`: `chainloop`
- `project_version_name`: the version name from Step 1 (e.g. `v1.77.0`)
- `latest`: `true`

### Parsing the compliance result (Call A)

The response will be large — parse it programmatically using a Bash subagent:

```python
import json

with open('<tool-result-file>') as f:
    raw = json.load(f)
data = json.loads(raw[0]['text'])

for req in data:
    if 'vulnerabilit' in req.get('name', '').lower():
        status = req.get('status', '')
        failed = [e for e in req.get('policyEvaluations', [])
                  if e.get('status') not in ('PASSED', 'SKIPPED')]
        print(f"Requirement: {req['name']} — Status: {status}")
        for e in failed:
            print(f"  FAILED: policy={e['name']} material={e.get('materialName','-')} status={e.get('status','-')}")
```

This gives you the list of **failing material names** (e.g. `control-plane-migrations-report`).

## Step 3: Download the SARIF for Each Failing Material

From the `list_pieces_of_evidence` result (Call B above), find the item whose `name` matches the failing material name and whose `kind` is `SARIF`. Use its `digest` field directly — no need to decode attestations.

Call `download_evidence_by_digest` with:
- `digest`: the digest of the matching SARIF item
- `download_content`: `true`

The SARIF `logicalLocations[].fullyQualifiedName` field contains the full image reference, e.g.:
```
ghcr.io/chainloop-dev/chainloop/control-plane-migrations:v1.77.0@sha256:<digest>:/atlas
```

The binary path (e.g. `/atlas`, `/app`) tells you which binary inside the image is vulnerable.

## Step 4: Identify the Fix

### 4a. Atlas / Migration Dockerfile vulnerabilities

**Symptom**: SARIF location is `/atlas`, image is `control-plane-migrations`

**Source file**: `app/controlplane/Dockerfile.migrations`

**Fix procedure**:
1. Check the current atlas version in the comment at the top of the Dockerfile (e.g. `# atlas version v1.2.0`)
2. Find the latest available stable version:
   ```bash
   curl -s "https://registry.hub.docker.com/v2/repositories/arigaio/atlas/tags?page_size=20&ordering=last_updated" \
     | python3 -c "import json,sys; [print(t['name']) for t in json.load(sys.stdin)['results'] if t['name'][0].isdigit() and '-' not in t['name']]"
   ```
3. Run grype on the current and candidate versions to confirm the CVEs are present then gone:
   ```bash
   grype arigaio/atlas:<current-version> --only-fixed 2>&1
   grype arigaio/atlas:<new-version> --only-fixed 2>&1
   ```
   A clean run has only the header line and no CVE rows.
4. **If the latest stable tag still has CVEs**, also check the `latest` tag — atlas rebuilds it frequently with updated Go toolchain and dependencies:
   ```bash
   docker pull arigaio/atlas:latest
   grype arigaio/atlas:latest --only-fixed 2>&1
   docker run --rm arigaio/atlas:latest version
   ```
   If `latest` is clean and the version is a reasonable increment (e.g. canary of the next patch), it is acceptable since the image is pinned by digest.
5. Once a clean version is confirmed, pull it and get its digest:
   ```bash
   docker pull arigaio/atlas:<new-version>
   docker inspect --format='{{index .RepoDigests 0}}' arigaio/atlas:<new-version>
   ```
6. Update `app/controlplane/Dockerfile.migrations`:
   ```dockerfile
   # from: arigaio/atlas:<NEW_VERSION>
   # docker run arigaio/atlas@sha256:<NEW_DIGEST> version
   # atlas version v<NEW_VERSION>
   FROM arigaio/atlas@sha256:<NEW_DIGEST> as base
   ```
7. Update `ATLAS_VERSION` in the following files to match the latest **stable** version (used for CLI tool installation, not the Docker image):
   - `common.mk` — the `ATLAS_VERSION=vX.X.X` in the `init` target
   - `.github/workflows/test.yml` — the `ATLAS_VERSION: vX.X.X` env variable

### 4b. Go stdlib / Go module vulnerabilities (backend)

**Symptom**: SARIF location is a Go binary (e.g. `/app`, `/server`), package is `stdlib` or a Go module

**Fix options** (in order of preference):

**Option A — Upgrade Go version** (for stdlib CVEs):
Use the `upgrading-golang` skill to bump the Go version in `go.mod` and all Dockerfiles.

**Option B — Upgrade a specific Go dependency** (for third-party modules):
1. Identify the affected module from the SARIF `purls` field (e.g. `pkg:golang/github.com/foo/bar@v1.2.3`)
2. Update `go.mod`:
   ```bash
   go get github.com/foo/bar@<fixed-version>
   go mod tidy
   ```
3. Verify with grype:
   ```bash
   grype dir:. --only-fixed 2>&1
   ```

## Step 5: Verify the Fix with Grype

Always run grype before and after the change to confirm the CVEs are resolved:

```bash
# Before — should show the CVE rows
grype <image>:<current-version> --only-fixed 2>&1

# After — should show header only, no CVE rows
grype <image>:<new-version> --only-fixed 2>&1
```

A clean run has only the column header line and zero data rows.

## Step 6: Commit and Create a PR

1. Check which branch you are on — do **not** create a new branch if one already exists:
   ```bash
   git branch --show-current
   ```

2. Commit with signoff (no co-author):
   ```bash
   git add <changed-files>
   git commit -s -m "fix(<scope>): <short description of the CVE fix>"
   git push -u origin <current-branch>
   ```

3. Create the PR with `gh pr create`:
   ```bash
   gh pr create --title "fix(<scope>): <short description>" --body "$(cat <<'EOF'
   ## Summary

   - <bullet: what was upgraded and why>
   - Fixes <CVE-ID> (<Severity>) and <CVE-ID> (<Severity>) in <package>
   - <brief note on how the fix works, e.g. new version built with Go X.Y.Z>
   EOF
   )"
   ```

## Step 7: Report Results

Summarise the findings and changes in this format:

```
## Vulnerability Remediation Summary

**Project**: chainloop v<version>
**Requirement**: no-vulnerabilities-high — was: FAIL

### Fixed

| CVE | Severity | Package | Old Version | Fix Applied |
|-----|----------|---------|-------------|-------------|
| CVE-XXXX-XXXXX | Critical | <pkg> | <old> | Upgraded <file> to <new> |

### Files Changed
- `app/controlplane/Dockerfile.migrations` — atlas vX.X.X → vX.X.X
- `common.mk` — ATLAS_VERSION vX.X.X → vX.X.X (if upgraded)
- `.github/workflows/test.yml` — ATLAS_VERSION vX.X.X → vX.X.X (if upgraded)

### PR
<GitHub PR URL>
```

## Key Reference Data

| Item | Value |
|------|-------|
| Chainloop org | `chainloop` |
| Project name | `chainloop` |
| chainloop-best-practices framework ID | `0ceef195-6900-4166-8407-77eb84954ed3` |
| Continuous-scanning workflow ID | `c506a425-d307-4a59-9132-659ffd417b57` |
| Migrations Dockerfile | `app/controlplane/Dockerfile.migrations` |
| Atlas CLI version (Makefile) | `common.mk` — `ATLAS_VERSION=vX.X.X` in `init` target |
| Atlas CLI version (CI) | `.github/workflows/test.yml` — `ATLAS_VERSION: vX.X.X` env |
| Backend go.mod | `go.mod` (root) |

## Important Notes

- Always pin Docker images by SHA256 digest, not tag alone
- For Go stdlib CVEs, upgrading the atlas or golang builder image is usually sufficient — check via grype before touching go.mod
- Run `go mod tidy` after any go.mod change
- The compliance-scanning runs daily, so the policy status will update automatically after the fix is merged and a new image is built

