Skip to content

Commit 316b987

Browse files
committed
refactor resolutions action
1 parent 1b6ce55 commit 316b987

1 file changed

Lines changed: 85 additions & 33 deletions

File tree

.github/workflows/security-resolutions.yml

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
permissions:
1010
contents: write
1111
pull-requests: write
12+
security-events: read
1213

1314
jobs:
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

Comments
 (0)