99permissions :
1010 contents : write
1111 pull-requests : write
12+ security-events : read
1213
1314jobs :
1415 fix-vulnerabilities :
@@ -24,41 +25,84 @@ jobs:
2425 - name : Install dependencies
2526 run : yarn install --frozen-lockfile
2627
27- - name : Audit and update resolutions
28- id : audit
28+ - name : Fetch Dependabot alerts and run yarn audit
29+ id : fetch
30+ env :
31+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
2932 run : |
30- # Run yarn audit and extract vulnerable packages with their patched versions
31- AUDIT_JSON=$(yarn audit --json 2>/dev/null || true)
33+ echo "::group::Fetching Dependabot alerts (medium/high/critical)"
34+ gh api \
35+ "/repos/${{ github.repository }}/dependabot/alerts?state=open&severity=medium,high,critical&per_page=100" \
36+ > /tmp/dependabot-alerts.json 2>/dev/null || echo "[]" > /tmp/dependabot-alerts.json
37+ echo "::endgroup::"
3238
33- # Parse audit output to find packages needing resolution updates
34- echo "$AUDIT_JSON" | python3 << 'PYEOF'
35- import sys, json, os
39+ echo "::group::Running yarn audit"
40+ yarn audit --json > /tmp/yarn-audit.json 2>/dev/null || true
41+ echo "::endgroup::"
3642
43+ - name : Analyze and update resolutions
44+ id : audit
45+ run : |
46+ python3 << 'PYEOF'
47+ import json, os
48+
49+ MIN_SEVERITIES = {"medium", "high", "critical"}
3750 advisories = {}
38- for line in sys.stdin:
39- try:
40- obj = json.loads(line)
41- if obj.get("type") == "auditAdvisory":
42- d = obj["data"]["advisory"]
43- name = d["module_name"]
44- patched = d.get("patched_versions", "")
45- severity = d["severity"]
46- if patched.startswith(">="):
47- version = patched[2:]
51+
52+ # --- Source 1: Dependabot alerts ---
53+ try:
54+ with open("/tmp/dependabot-alerts.json", "r") as f:
55+ alerts = json.load(f)
56+ if isinstance(alerts, list):
57+ for alert in alerts:
58+ vuln = alert.get("security_vulnerability", {})
59+ name = vuln.get("package", {}).get("name", "")
60+ severity = alert.get("security_advisory", {}).get("severity", "").lower()
61+ if severity not in MIN_SEVERITIES or not name:
62+ continue
63+ patched_obj = vuln.get("first_patched_version")
64+ if patched_obj and patched_obj.get("identifier"):
65+ version = patched_obj["identifier"]
4866 if name not in advisories or version > advisories[name]["version"]:
49- advisories[name] = {"version": version, "severity": severity}
50- except:
51- pass
67+ advisories[name] = {"version": version, "severity": severity, "source": "dependabot"}
68+ print(f" Dependabot: {name} ({severity}) -> {version}")
69+ except Exception as e:
70+ print(f" Warning: Could not parse Dependabot alerts: {e}")
71+
72+ # --- Source 2: yarn audit ---
73+ try:
74+ with open("/tmp/yarn-audit.json", "r") as f:
75+ for line in f:
76+ try:
77+ obj = json.loads(line)
78+ if obj.get("type") == "auditAdvisory":
79+ d = obj["data"]["advisory"]
80+ name = d["module_name"]
81+ patched = d.get("patched_versions", "")
82+ severity = d.get("severity", "").lower()
83+ if severity not in MIN_SEVERITIES:
84+ continue
85+ if patched.startswith(">="):
86+ version = patched[2:].strip()
87+ if name not in advisories or version > advisories[name]["version"]:
88+ advisories[name] = {"version": version, "severity": severity, "source": "yarn-audit"}
89+ print(f" yarn audit: {name} ({severity}) -> {version}")
90+ except json.JSONDecodeError:
91+ pass
92+ except Exception as e:
93+ print(f" Warning: Could not parse yarn audit: {e}")
94+
95+ print(f"\nFound {len(advisories)} vulnerable packages (medium/high/critical)")
5296
5397 output_file = os.environ.get("GITHUB_OUTPUT", "/dev/null")
5498
5599 if not advisories:
56- print("No vulnerabilities with available patches found")
100+ print("No actionable vulnerabilities found")
57101 with open(output_file, "a") as f:
58102 f.write("has_updates=false\n")
59- sys.exit (0)
103+ raise SystemExit (0)
60104
61- # Read current package.json to check existing resolutions
105+ # Read current package.json
62106 with open("package.json", "r") as f:
63107 pkg = json.load(f)
64108
@@ -69,26 +113,33 @@ jobs:
69113 current = resolutions.get(name, "")
70114 target = f"^{info['version']}"
71115 if current != target:
72- needed[name] = {"version": target, "severity": info["severity"], "current": current}
116+ needed[name] = {
117+ "version": target,
118+ "severity": info["severity"],
119+ "current": current,
120+ "source": info["source"],
121+ }
73122
74123 if not needed:
75124 print("All resolutions already up to date")
76125 with open(output_file, "a") as f:
77126 f.write("has_updates=false\n")
78- sys.exit (0)
127+ raise SystemExit (0)
79128
80129 # Build summary table
81130 lines = []
82- lines.append("| Dependency | Before | After | Severity |")
83- lines.append("|---|---|---|---|")
131+ lines.append("| Dependency | Before | After | Severity | Source | ")
132+ lines.append("|---|---|---|---|---| ")
84133 for name, info in sorted(needed.items()):
85134 before = info["current"] if info["current"] else "_(none)_"
86- lines.append(f"| **{name}** | {before} | {info['version']} | {info['severity']} |")
135+ lines.append(
136+ f"| **{name}** | {before} | {info['version']} | {info['severity']} | {info['source']} |"
137+ )
87138
88139 summary = "\n".join(lines)
89- print(f"Updates needed:\n{summary}")
140+ print(f"\nUpdates needed:\n{summary}")
90141
91- # Apply resolutions
142+ # Apply resolutions to package.json
92143 for name, info in needed.items():
93144 resolutions[name] = info["version"]
94145
@@ -98,12 +149,11 @@ jobs:
98149 json.dump(pkg, f, indent=2)
99150 f.write("\n")
100151
101- # Write outputs
102152 with open(output_file, "a") as f:
103153 f.write("has_updates=true\n")
104154 f.write(f"summary<<EOFSUM\n{summary}\nEOFSUM\n")
105155
106- print(f"Applied {len(needed)} resolution updates to package.json")
156+ print(f"\nApplied {len(needed)} resolution updates to package.json")
107157 PYEOF
108158
109159 - name : Reinstall with updated resolutions
@@ -128,12 +178,14 @@ jobs:
128178 body : |
129179 ## Summary
130180 Automated update of `resolutions` in `package.json` to fix vulnerable transitive dependencies.
181+ Sources: Dependabot alerts (medium/high/critical) + yarn audit.
131182
132183 ### Changes
133184 ${{ steps.audit.outputs.summary }}
134185
135186 ### How this works
136- - `yarn audit` detected vulnerable transitive dependencies
187+ - Dependabot alerts were fetched via GitHub API (medium, high, critical only)
188+ - `yarn audit` was also run as a secondary source
137189 - `resolutions` entries were added/updated in `package.json` to force safe versions
138190 - `yarn install` was re-run to update `yarn.lock`
139191
0 commit comments