Skip to content

Commit 003c9ed

Browse files
committed
chore(skills): improve vulnerability-remediation skill
Signed-off-by: Miguel Martinez <miguel@chainloop.dev>
1 parent df1ce80 commit 003c9ed

1 file changed

Lines changed: 199 additions & 0 deletions

File tree

  • .claude/skills/vulnerability-remediation
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
---
2+
name: vulnerability-remediation
3+
description: 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.
4+
---
5+
6+
# Vulnerability Remediation for chainloop
7+
8+
This skill reviews open vulnerability policy violations recorded in Chainloop for the `chainloop` project and applies fixes to the affected source files.
9+
10+
## Step 1: Find the Latest Project Version
11+
12+
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).
13+
14+
## Step 2: Gather Compliance Results and Evidence in Parallel
15+
16+
Once the `projectVersionId` is known, make both of these calls **at the same time**:
17+
18+
**Call A**`get_frameworks_compliance`:
19+
- `project_version_id`: the UUID from Step 1
20+
- `framework_ids`: `["0ceef195-6900-4166-8407-77eb84954ed3"]` (chainloop-best-practices)
21+
22+
**Call B**`list_pieces_of_evidence`:
23+
- `project_name`: `chainloop`
24+
- `project_version_name`: the version name from Step 1 (e.g. `v1.77.0`)
25+
- `latest`: `true`
26+
27+
### Parsing the compliance result (Call A)
28+
29+
The response will be large — parse it programmatically using a Bash subagent:
30+
31+
```python
32+
import json
33+
34+
with open('<tool-result-file>') as f:
35+
raw = json.load(f)
36+
data = json.loads(raw[0]['text'])
37+
38+
for req in data:
39+
if 'vulnerabilit' in req.get('name', '').lower():
40+
status = req.get('status', '')
41+
failed = [e for e in req.get('policyEvaluations', [])
42+
if e.get('status') not in ('PASSED', 'SKIPPED')]
43+
print(f"Requirement: {req['name']} — Status: {status}")
44+
for e in failed:
45+
print(f" FAILED: policy={e['name']} material={e.get('materialName','-')} status={e.get('status','-')}")
46+
```
47+
48+
This gives you the list of **failing material names** (e.g. `control-plane-migrations-report`).
49+
50+
## Step 3: Download the SARIF for Each Failing Material
51+
52+
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.
53+
54+
Call `download_evidence_by_digest` with:
55+
- `digest`: the digest of the matching SARIF item
56+
- `download_content`: `true`
57+
58+
The SARIF `logicalLocations[].fullyQualifiedName` field contains the full image reference, e.g.:
59+
```
60+
ghcr.io/chainloop-dev/chainloop/control-plane-migrations:v1.77.0@sha256:<digest>:/atlas
61+
```
62+
63+
The binary path (e.g. `/atlas`, `/app`) tells you which binary inside the image is vulnerable.
64+
65+
## Step 4: Identify the Fix
66+
67+
### 4a. Atlas / Migration Dockerfile vulnerabilities
68+
69+
**Symptom**: SARIF location is `/atlas`, image is `control-plane-migrations`
70+
71+
**Source file**: `app/controlplane/Dockerfile.migrations`
72+
73+
**Fix procedure**:
74+
1. Check the current atlas version in the comment at the top of the Dockerfile (e.g. `# atlas version v1.1.0`)
75+
2. Find the latest available version:
76+
```bash
77+
curl -s "https://registry.hub.docker.com/v2/repositories/arigaio/atlas/tags?page_size=20&ordering=last_updated" \
78+
| 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']]"
79+
```
80+
3. Run grype on the current and candidate versions to confirm the CVEs are present then gone:
81+
```bash
82+
grype arigaio/atlas:<current-version> --only-fixed 2>&1
83+
grype arigaio/atlas:<new-version> --only-fixed 2>&1
84+
```
85+
A clean run has only the header line and no CVE rows.
86+
4. Once a clean version is confirmed, pull it and get its digest:
87+
```bash
88+
docker pull arigaio/atlas:<new-version>
89+
docker inspect --format='{{index .RepoDigests 0}}' arigaio/atlas:<new-version>
90+
```
91+
5. Update `app/controlplane/Dockerfile.migrations`:
92+
```dockerfile
93+
# from: arigaio/atlas:<NEW_VERSION>
94+
# docker run arigaio/atlas@sha256:<NEW_DIGEST> version
95+
# atlas version v<NEW_VERSION>
96+
FROM arigaio/atlas@sha256:<NEW_DIGEST> as base
97+
```
98+
99+
### 4b. Go stdlib / Go module vulnerabilities (backend)
100+
101+
**Symptom**: SARIF location is a Go binary (e.g. `/app`, `/server`), package is `stdlib` or a Go module
102+
103+
**Fix options** (in order of preference):
104+
105+
**Option A — Upgrade Go version** (for stdlib CVEs):
106+
Use the `upgrading-golang` skill to bump the Go version in `go.mod` and all Dockerfiles.
107+
108+
**Option B — Upgrade a specific Go dependency** (for third-party modules):
109+
1. Identify the affected module from the SARIF `purls` field (e.g. `pkg:golang/github.com/foo/bar@v1.2.3`)
110+
2. Update `go.mod`:
111+
```bash
112+
go get github.com/foo/bar@<fixed-version>
113+
go mod tidy
114+
```
115+
3. Verify with grype:
116+
```bash
117+
grype dir:. --only-fixed 2>&1
118+
```
119+
120+
## Step 5: Verify the Fix with Grype
121+
122+
Always run grype before and after the change to confirm the CVEs are resolved:
123+
124+
```bash
125+
# Before — should show the CVE rows
126+
grype <image>:<current-version> --only-fixed 2>&1
127+
128+
# After — should show header only, no CVE rows
129+
grype <image>:<new-version> --only-fixed 2>&1
130+
```
131+
132+
A clean run has only the column header line and zero data rows.
133+
134+
## Step 6: Commit and Create a PR
135+
136+
1. Check which branch you are on — do **not** create a new branch if one already exists:
137+
```bash
138+
git branch --show-current
139+
```
140+
141+
2. Commit with signoff (no co-author):
142+
```bash
143+
git add <changed-files>
144+
git commit -s -m "fix(<scope>): <short description of the CVE fix>"
145+
git push -u origin <current-branch>
146+
```
147+
148+
3. Create the PR with `gh pr create`:
149+
```bash
150+
gh pr create --title "fix(<scope>): <short description>" --body "$(cat <<'EOF'
151+
## Summary
152+
153+
- <bullet: what was upgraded and why>
154+
- Fixes <CVE-ID> (<Severity>) and <CVE-ID> (<Severity>) in <package>
155+
- <brief note on how the fix works, e.g. new version built with Go X.Y.Z>
156+
EOF
157+
)"
158+
```
159+
160+
## Step 7: Report Results
161+
162+
Summarise the findings and changes in this format:
163+
164+
```
165+
## Vulnerability Remediation Summary
166+
167+
**Project**: chainloop v<version>
168+
**Requirement**: no-vulnerabilities-high — was: FAIL
169+
170+
### Fixed
171+
172+
| CVE | Severity | Package | Old Version | Fix Applied |
173+
|-----|----------|---------|-------------|-------------|
174+
| CVE-XXXX-XXXXX | Critical | <pkg> | <old> | Upgraded <file> to <new> |
175+
176+
### Files Changed
177+
- `app/controlplane/Dockerfile.migrations` — atlas vX.X.X → vX.X.X
178+
179+
### PR
180+
<GitHub PR URL>
181+
```
182+
183+
## Key Reference Data
184+
185+
| Item | Value |
186+
|------|-------|
187+
| Chainloop org | `chainloop` |
188+
| Project name | `chainloop` |
189+
| chainloop-best-practices framework ID | `0ceef195-6900-4166-8407-77eb84954ed3` |
190+
| Continuous-scanning workflow ID | `c506a425-d307-4a59-9132-659ffd417b57` |
191+
| Migrations Dockerfile | `app/controlplane/Dockerfile.migrations` |
192+
| Backend go.mod | `go.mod` (root) |
193+
194+
## Important Notes
195+
196+
- Always pin Docker images by SHA256 digest, not tag alone
197+
- For Go stdlib CVEs, upgrading the atlas or golang builder image is usually sufficient — check via grype before touching go.mod
198+
- Run `go mod tidy` after any go.mod change
199+
- The compliance-scanning runs daily, so the policy status will update automatically after the fix is merged and a new image is built

0 commit comments

Comments
 (0)