2323 type : string
2424 required : false
2525 default : ' 3.11'
26+ security-workflows-ref :
27+ description : Ref of kernel/security-workflows to fetch shared scripts and prompts from
28+ type : string
29+ required : false
30+ default : main
2631
2732jobs :
2833 scan :
@@ -112,36 +117,69 @@ jobs:
112117 name : socket-report
113118 path : socket-report.json
114119
120+ - name : Normalize Socket report
121+ run : |
122+ curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/${{ inputs.security-workflows-ref }}/scripts/vuln_remediation.py -o /tmp/vuln_remediation.py
123+ python3 /tmp/vuln_remediation.py normalize \
124+ --input socket-report.json \
125+ --output remediation-input.json
126+
127+ - name : Upload normalized remediation input
128+ uses : actions/upload-artifact@v4
129+ with :
130+ name : remediation-input
131+ path : remediation-input.json
132+
115133 triage :
116134 needs : scan
117135 runs-on : ubuntu-latest
118136 steps :
119137 - name : Checkout repository
120138 uses : actions/checkout@v4
121139
122- - name : Download scan report
140+ - name : Download normalized remediation input
123141 uses : actions/download-artifact@v4
124142 with :
125- name : socket-report
143+ name : remediation-input
126144
127- - name : Install Cursor CLI
128- run : |
129- curl https://cursor.com/install -fsS | bash
130- echo "$HOME/.cursor/bin" >> $GITHUB_PATH
145+ - name : Install Socket CLI
146+ run : npm install -g @socketsecurity/cli
131147
132- - name : Triage alerts
148+ - name : Ask Socket for a fix plan
133149 env :
134- CURSOR_API_KEY : ${{ secrets.CURSOR_API_KEY }}
135150 SOCKET_SECURITY_API_KEY : ${{ secrets.SOCKET_API_TOKEN }}
136151 run : |
137- export DATE="$(date -u +%Y-%m-%d)"
138- curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/main/.github/workflows/vuln-remediation/triage-prompt.md | envsubst '${GITHUB_REPOSITORY} ${DATE}' | agent -p --model ${{ secrets.CURSOR_PREFERRED_MODEL }} --trust --force --output-format=text
152+ set +e
153+ socket fix . \
154+ --no-apply-fixes \
155+ --no-major-updates \
156+ --show-affected-direct-dependencies \
157+ --json \
158+ --output-file socket-fix-plan.json
159+ FIX_PLAN_EXIT=$?
160+ set -e
161+ if [ $FIX_PLAN_EXIT -ne 0 ] || ! jq empty socket-fix-plan.json 2>/dev/null; then
162+ echo "::warning::Socket fix planning failed; manager will defer unplanned or insufficiently described fixes."
163+ echo '{}' > socket-fix-plan.json
164+ fi
165+
166+ - name : Build remediation context
167+ run : |
168+ curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/${{ inputs.security-workflows-ref }}/scripts/vuln_remediation.py -o /tmp/vuln_remediation.py
169+ python3 /tmp/vuln_remediation.py build-context \
170+ --input remediation-input.json \
171+ --fix-plan socket-fix-plan.json \
172+ --output remediation-context.json
173+ cp remediation-context.json triage-result.json
139174
140175 - name : Upload triage results
141176 uses : actions/upload-artifact@v4
142177 with :
143178 name : triage-result
144- path : triage-result.json
179+ path : |
180+ triage-result.json
181+ remediation-context.json
182+ socket-fix-plan.json
145183
146184 fix :
147185 needs : triage
@@ -168,11 +206,11 @@ jobs:
168206 - name : Check for fixable alerts
169207 id : check
170208 run : |
171- if ! [ -f triage-result .json ]; then
209+ if ! [ -f remediation-context .json ]; then
172210 echo "skip=true" >> $GITHUB_OUTPUT
173211 exit 0
174212 fi
175- FIX_COUNT=$(jq '[.alerts[] | select(.category == "fix")] | length' triage-result .json 2>/dev/null || echo "0")
213+ FIX_COUNT=$(jq '.fixes | length' remediation-context .json 2>/dev/null || echo "0")
176214 if [ "$FIX_COUNT" = "0" ]; then
177215 echo "skip=true" >> $GITHUB_OUTPUT
178216 else
@@ -234,18 +272,83 @@ jobs:
234272 env :
235273 CURSOR_API_KEY : ${{ secrets.CURSOR_API_KEY }}
236274 GOPRIVATE : github.com/kernel/*
237- GH_TOKEN : ${{ steps.app-token.outputs.token }}
238275 SOCKET_SECURITY_API_KEY : ${{ secrets.SOCKET_API_TOKEN }}
239276 run : |
240- export DATE="$(date -u +%Y-%m-%d)"
241- curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/main/.github/workflows/vuln-remediation/fix-prompt.md | envsubst '${GITHUB_REPOSITORY} ${DATE}' | agent -p --model ${{ secrets.CURSOR_PREFERRED_MODEL }} --workspace . --trust --force --output-format=text
277+ FIX_IDS=$(jq -r '.fixes[].id' remediation-context.json | paste -sd, -)
278+ set +e
279+ socket fix . \
280+ --id "$FIX_IDS" \
281+ --no-major-updates \
282+ --json \
283+ --output-file socket-fix-apply.json
284+ SOCKET_FIX_EXIT=$?
285+ set -e
286+ if [ $SOCKET_FIX_EXIT -ne 0 ]; then
287+ echo "::warning::Socket fix failed; invoking bounded fallback agent."
288+ fi
289+
290+ if git diff --quiet; then
291+ export DATE="$(date -u +%Y-%m-%d)"
292+ curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/${{ inputs.security-workflows-ref }}/.github/workflows/vuln-remediation/fix-prompt.md | envsubst '${GITHUB_REPOSITORY} ${DATE}' | agent -p --model ${{ secrets.CURSOR_PREFERRED_MODEL }} --workspace . --trust --force --output-format=text
293+ fi
294+
295+ - name : Validate remediation diff
296+ id : diff
297+ if : steps.check.outputs.skip != 'true'
298+ run : |
299+ curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/${{ inputs.security-workflows-ref }}/scripts/vuln_remediation.py -o /tmp/vuln_remediation.py
300+ git diff --name-only > changed-files.txt
301+ if [ ! -s changed-files.txt ]; then
302+ echo '{"fixed": [], "reverted": [], "summary": "No dependency changes were produced."}' > fix-result.json
303+ echo "has_changes=false" >> $GITHUB_OUTPUT
304+ exit 0
305+ fi
306+ echo "has_changes=true" >> $GITHUB_OUTPUT
307+ python3 /tmp/vuln_remediation.py validate-diff \
308+ --repo-root . \
309+ --context remediation-context.json \
310+ --changed-files changed-files.txt \
311+ --output diff-validation.json
312+
313+ - name : Confirm fixes
314+ if : steps.check.outputs.skip != 'true' && steps.diff.outputs.has_changes == 'true'
315+ run : |
316+ set +e
317+ python3 /tmp/vuln_remediation.py confirm \
318+ --repo-root . \
319+ --context remediation-context.json \
320+ --output confirmation-result.json
321+ CONFIRM_EXIT=$?
322+ set -e
323+ python3 /tmp/vuln_remediation.py summarize-fix \
324+ --context remediation-context.json \
325+ --confirmation confirmation-result.json \
326+ --output fix-result.json
327+ exit $CONFIRM_EXIT
328+
329+ - name : Commit and push validated changes
330+ if : steps.check.outputs.skip != 'true' && steps.diff.outputs.has_changes == 'true'
331+ run : |
332+ if ! jq -e '.fixed | length > 0' fix-result.json >/dev/null; then
333+ echo "No confirmed fixes to commit."
334+ exit 0
335+ fi
336+ git add --pathspec-from-file=changed-files.txt
337+ git diff --cached --quiet && exit 0
338+ git commit -m "security: vulnerability remediation ($(date -u +%Y-%m-%d))"
339+ git push --force-with-lease origin security/vuln-remediation
242340
243341 - name : Upload fix results
244342 if : always()
245343 uses : actions/upload-artifact@v4
246344 with :
247345 name : fix-result
248- path : fix-result.json
346+ path : |
347+ fix-result.json
348+ confirmation-result.json
349+ diff-validation.json
350+ changed-files.txt
351+ socket-fix-apply.json
249352 if-no-files-found : ignore
250353
251354 pr :
@@ -272,88 +375,44 @@ jobs:
272375 name : fix-result
273376 continue-on-error : true
274377
378+ - name : Render PR body
379+ run : |
380+ curl -fsSL https://raw.githubusercontent.com/kernel/security-workflows/${{ inputs.security-workflows-ref }}/scripts/vuln_remediation.py -o /tmp/vuln_remediation.py
381+ [ -f triage-result.json ] || echo '{"deferred":[]}' > triage-result.json
382+ [ -f fix-result.json ] || echo '{"fixed":[],"reverted":[]}' > fix-result.json
383+ [ -f confirmation-result.json ] || echo '{"ok":false,"confirmed":[],"failures":[]}' > confirmation-result.json
384+ python3 /tmp/vuln_remediation.py render-pr-body \
385+ --triage triage-result.json \
386+ --fix-result fix-result.json \
387+ --confirmation confirmation-result.json \
388+ --output pr-body.md
389+
275390 - name : Create or update PR
276391 env :
277392 GH_TOKEN : ${{ steps.app-token.outputs.token }}
278393 run : |
279- DATE="$(date -u +%Y-%m-%d)"
280- REPO="${{ github.repository }}"
281-
282- FIXED_TABLE=""
283- DISMISSED_TABLE=""
284- DEFERRED_TABLE=""
285-
286- if [ -f triage-result.json ]; then
287- ALERT_COUNT=$(jq '.alerts | length' triage-result.json 2>/dev/null || echo "0")
288- if [ "$ALERT_COUNT" = "0" ]; then
289- echo "No alerts to report."
290- exit 0
291- fi
292-
293- DISMISSED_TABLE=$(jq -r '
294- .alerts[] | select(.category == "dismiss") |
295- "| \(.type) | \(.package) | \(.severity) | \(.reason) |"
296- ' triage-result.json 2>/dev/null || echo "")
297-
298- DEFERRED_TABLE=$(jq -r '
299- .alerts[] | select(.category == "defer") |
300- "| \(.cve // "N/A") | \(.package) | \(.severity) | \(.reason) |"
301- ' triage-result.json 2>/dev/null || echo "")
302- fi
303-
304- if [ -f fix-result.json ]; then
305- FIXED_TABLE=$(jq -r '
306- .fixed[] |
307- "| \(.cve) | \(.package) | \(.ecosystem) | \(.old_version) | \(.new_version) | \(.manifest) |"
308- ' fix-result.json 2>/dev/null || echo "")
309-
310- REVERTED=$(jq -r '
311- .reverted[] |
312- "| \(.cve) | \(.package) | \(.ecosystem) | \(.reason) |"
313- ' fix-result.json 2>/dev/null || echo "")
314- if [ -n "$REVERTED" ]; then
315- DEFERRED_TABLE="${DEFERRED_TABLE}
316- ${REVERTED}"
317- fi
394+ if [ "${{ needs.fix.result }}" != "success" ]; then
395+ echo "Fix job did not complete successfully; not creating or updating a remediation PR."
396+ exit 0
318397 fi
319-
320- if [ -z "$FIXED_TABLE" ] && [ -z "$DISMISSED_TABLE" ] && [ -z "$DEFERRED_TABLE" ] ; then
321- echo "No results to report ."
398+ REPO="${{ github.repository }}"
399+ if ! jq -e '.fixed | length > 0' fix-result.json >/dev/null 2>&1 ; then
400+ echo "No confirmed fixes; not creating a PR ."
322401 exit 0
323402 fi
324403
325- BODY="## Vulnerability Remediation — ${DATE}
326-
327- ### Fixed
328- | CVE | Package | Ecosystem | Old Version | New Version | Manifest |
329- |-----|---------|-----------|-------------|-------------|----------|
330- ${FIXED_TABLE:-| (none) | | | | | |}
331-
332- ### Skipped (non-actionable)
333- | Alert Type | Package | Severity | Reason |
334- |------------|---------|----------|--------|
335- ${DISMISSED_TABLE:-| (none) | | | |}
336-
337- ### Deferred (needs human review)
338- | CVE | Package | Severity | Reason |
339- |-----|---------|----------|--------|
340- ${DEFERRED_TABLE:-| (none) | | | |}"
341-
342404 EXISTING_PR=$(gh pr list --repo "$REPO" --head security/vuln-remediation --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
343405
344406 if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
345- gh pr edit "$EXISTING_PR" --repo "$REPO" --body "$BODY" --add-assignee ulziibay-kernel --add-reviewer ulziibay-kernel
407+ gh pr edit "$EXISTING_PR" --repo "$REPO" --body-file pr-body.md --add-assignee ulziibay-kernel --add-reviewer ulziibay-kernel
346408 echo "Updated PR #${EXISTING_PR}"
347- elif [ -n "$FIXED_TABLE" ]; then
409+ else
348410 gh pr create \
349411 --repo "$REPO" \
350412 --head security/vuln-remediation \
351413 --base main \
352414 --title "security: vulnerability remediation" \
353415 --assignee ulziibay-kernel \
354416 --reviewer ulziibay-kernel \
355- --body "$BODY"
356- else
357- echo "No fixes applied and no existing PR. Logging results only."
358- echo "$BODY"
417+ --body-file pr-body.md
359418 fi
0 commit comments