@openhands/extensions 0.0.1-alpha → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/.agents/skills/custom-codereview-guide.md +25 -0
  2. package/.github/pull_request_template.md +38 -0
  3. package/.github/release.yml +14 -0
  4. package/.github/workflows/check-extensions.yml +72 -0
  5. package/.github/workflows/npm-publish.yml +89 -0
  6. package/.github/workflows/pr.yml +30 -0
  7. package/.github/workflows/release.yml +24 -0
  8. package/.github/workflows/tests.yml +25 -0
  9. package/.github/workflows/vulnerability-scan.yml +87 -0
  10. package/.release-please-manifest.json +3 -0
  11. package/AGENTS.md +132 -0
  12. package/README.md +10 -0
  13. package/analysis_results.md +162 -0
  14. package/marketplaces/large-codebase.json +66 -0
  15. package/marketplaces/openhands-extensions.json +682 -0
  16. package/package.json +4 -10
  17. package/plugins/README.md +30 -0
  18. package/plugins/city-weather/.plugin/plugin.json +13 -0
  19. package/plugins/city-weather/README.md +145 -0
  20. package/plugins/city-weather/commands/now.md +56 -0
  21. package/plugins/cobol-modernization/.plugin/plugin.json +19 -0
  22. package/plugins/cobol-modernization/README.md +201 -0
  23. package/plugins/cobol-modernization/references/troubleshooting.md +18 -0
  24. package/plugins/cobol-modernization/skills/build-setup/SKILL.md +78 -0
  25. package/plugins/cobol-modernization/skills/build-setup/scripts/install-gnucobol.sh +32 -0
  26. package/plugins/cobol-modernization/skills/cobol-modernization-overview/SKILL.md +113 -0
  27. package/plugins/cobol-modernization/skills/mainfraime-removal/SKILL.md +62 -0
  28. package/plugins/cobol-modernization/skills/mainfraime-removal/references/cics-transformation-examples.md +45 -0
  29. package/plugins/cobol-modernization/skills/mainframe-planning/SKILL.md +78 -0
  30. package/plugins/cobol-modernization/skills/to-java-migration/SKILL.md +59 -0
  31. package/plugins/cobol-modernization/skills/to-java-migration/references/cobol-to-java-example.md +58 -0
  32. package/plugins/cobol-modernization/skills/to-java-migration/references/datatype-mappings.md +19 -0
  33. package/plugins/issue-duplicate-checker/.plugin/plugin.json +13 -0
  34. package/plugins/issue-duplicate-checker/README.md +51 -0
  35. package/plugins/issue-duplicate-checker/action.yml +349 -0
  36. package/plugins/issue-duplicate-checker/scripts/auto_close_duplicate_issues.py +569 -0
  37. package/plugins/issue-duplicate-checker/scripts/issue_duplicate_check_openhands.py +681 -0
  38. package/plugins/issue-duplicate-checker/scripts/post_duplicate_notice.js +220 -0
  39. package/plugins/issue-duplicate-checker/scripts/remove_duplicate_candidate_label.js +27 -0
  40. package/plugins/magic-test/.plugin/plugin.json +13 -0
  41. package/plugins/magic-test/skills/magic-word/SKILL.md +33 -0
  42. package/plugins/migration-scoring/.plugin/plugin.json +19 -0
  43. package/plugins/migration-scoring/README.md +244 -0
  44. package/plugins/migration-scoring/skills/migration-mapping/SKILL.md +72 -0
  45. package/plugins/migration-scoring/skills/migration-report/SKILL.md +118 -0
  46. package/plugins/migration-scoring/skills/migration-scoring-overview/SKILL.md +126 -0
  47. package/plugins/migration-scoring/skills/score-quality/SKILL.md +54 -0
  48. package/plugins/migration-scoring/skills/score-quality/references/scoring-criteria.md +30 -0
  49. package/plugins/migration-scoring/skills/score-style/SKILL.md +106 -0
  50. package/plugins/onboarding/.plugin/plugin.json +20 -0
  51. package/plugins/onboarding/README.md +30 -0
  52. package/plugins/onboarding/references/criteria.md +144 -0
  53. package/plugins/onboarding/skills/agent-readiness-report/README.md +23 -0
  54. package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +122 -0
  55. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_agent_instructions.sh +88 -0
  56. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_build_env.sh +114 -0
  57. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_feedback_loops.sh +133 -0
  58. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_policy.sh +113 -0
  59. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_workflows.sh +127 -0
  60. package/plugins/onboarding/skills/improve-agent-readiness/README.md +19 -0
  61. package/plugins/onboarding/skills/improve-agent-readiness/SKILL.md +167 -0
  62. package/plugins/onboarding/skills/setup-agents-md/README.md +15 -0
  63. package/plugins/onboarding/skills/setup-agents-md/SKILL.md +150 -0
  64. package/plugins/onboarding/skills/setup-openhands/README.md +20 -0
  65. package/plugins/onboarding/skills/setup-openhands/SKILL.md +56 -0
  66. package/plugins/onboarding/skills/setup-pr-review/README.md +23 -0
  67. package/plugins/onboarding/skills/setup-pr-review/SKILL.md +72 -0
  68. package/plugins/openhands/.plugin/plugin.json +13 -0
  69. package/plugins/openhands/README.md +52 -0
  70. package/plugins/openhands/SKILL.md +61 -0
  71. package/plugins/openhands/commands/create.md +55 -0
  72. package/plugins/openhands/commands/openhands-cloud.md +8 -0
  73. package/plugins/openhands/scripts/run.sh +69 -0
  74. package/plugins/pr-review/.plugin/plugin.json +13 -0
  75. package/plugins/pr-review/README.md +393 -0
  76. package/plugins/pr-review/action.yml +298 -0
  77. package/plugins/pr-review/scripts/agent_script.py +1282 -0
  78. package/plugins/pr-review/scripts/evaluate_review.py +655 -0
  79. package/plugins/pr-review/scripts/prompt.py +260 -0
  80. package/plugins/pr-review/workflows/pr-review-by-openhands.yml +51 -0
  81. package/plugins/pr-review/workflows/pr-review-evaluation.yml +85 -0
  82. package/plugins/qa-changes/.plugin/plugin.json +11 -0
  83. package/plugins/qa-changes/README.md +185 -0
  84. package/plugins/qa-changes/action.yml +181 -0
  85. package/plugins/qa-changes/scripts/agent_script.py +406 -0
  86. package/plugins/qa-changes/scripts/evaluate_qa_changes.py +385 -0
  87. package/plugins/qa-changes/scripts/prompt.py +174 -0
  88. package/plugins/qa-changes/workflows/qa-changes-by-openhands.yml +50 -0
  89. package/plugins/qa-changes/workflows/qa-changes-evaluation.yml +85 -0
  90. package/plugins/release-notes/.plugin/plugin.json +19 -0
  91. package/plugins/release-notes/README.md +283 -0
  92. package/plugins/release-notes/SKILL.md +83 -0
  93. package/plugins/release-notes/action.yml +117 -0
  94. package/plugins/release-notes/commands/release-notes.md +8 -0
  95. package/plugins/release-notes/scripts/agent_script.py +292 -0
  96. package/plugins/release-notes/scripts/generate_release_notes.py +733 -0
  97. package/plugins/release-notes/scripts/prompt.py +90 -0
  98. package/plugins/release-notes/scripts/validate_release_notes.py +328 -0
  99. package/plugins/release-notes/workflows/release-notes.yml +76 -0
  100. package/plugins/vulnerability-remediation/.plugin/plugin.json +19 -0
  101. package/plugins/vulnerability-remediation/README.md +217 -0
  102. package/plugins/vulnerability-remediation/action.yml +187 -0
  103. package/plugins/vulnerability-remediation/scripts/scan_and_remediate.py +561 -0
  104. package/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml +87 -0
  105. package/pyproject.toml +12 -0
  106. package/release-please-config.json +16 -0
  107. package/scripts/sync_extensions.py +494 -0
  108. package/scripts/sync_openhands_sdk_skill.py +264 -0
  109. package/skills/README.md +159 -0
  110. package/skills/add-javadoc/.plugin/plugin.json +18 -0
  111. package/skills/add-javadoc/README.md +40 -0
  112. package/skills/add-javadoc/SKILL.md +35 -0
  113. package/skills/add-javadoc/references/example.md +32 -0
  114. package/skills/add-skill/.plugin/plugin.json +18 -0
  115. package/skills/add-skill/README.md +67 -0
  116. package/skills/add-skill/SKILL.md +47 -0
  117. package/skills/add-skill/scripts/fetch_skill.py +259 -0
  118. package/skills/agent-creator/.plugin/plugin.json +20 -0
  119. package/skills/agent-creator/README.md +104 -0
  120. package/skills/agent-creator/SKILL.md +190 -0
  121. package/skills/agent-creator/commands/agent-creator.md +8 -0
  122. package/skills/agent-creator/references/fallback.md +117 -0
  123. package/skills/agent-memory/.plugin/plugin.json +18 -0
  124. package/skills/agent-memory/README.md +35 -0
  125. package/skills/agent-memory/SKILL.md +30 -0
  126. package/skills/agent-memory/commands/remember.md +8 -0
  127. package/skills/agent-sdk-builder/.plugin/plugin.json +18 -0
  128. package/skills/agent-sdk-builder/README.md +40 -0
  129. package/skills/agent-sdk-builder/SKILL.md +37 -0
  130. package/skills/agent-sdk-builder/commands/agent-builder.md +8 -0
  131. package/skills/azure-devops/.plugin/plugin.json +18 -0
  132. package/skills/azure-devops/README.md +55 -0
  133. package/skills/azure-devops/SKILL.md +50 -0
  134. package/skills/bitbucket/.plugin/plugin.json +17 -0
  135. package/skills/bitbucket/README.md +50 -0
  136. package/skills/bitbucket/SKILL.md +45 -0
  137. package/skills/code-review/.plugin/plugin.json +19 -0
  138. package/skills/code-review/README.md +18 -0
  139. package/skills/code-review/SKILL.md +208 -0
  140. package/skills/code-review/commands/codereview-roasted.md +8 -0
  141. package/skills/code-review/commands/codereview.md +8 -0
  142. package/skills/code-review/references/risk-evaluation.md +41 -0
  143. package/skills/code-review/references/supply-chain-security.md +31 -0
  144. package/skills/code-simplifier/.plugin/plugin.json +21 -0
  145. package/skills/code-simplifier/README.md +30 -0
  146. package/skills/code-simplifier/SKILL.md +91 -0
  147. package/skills/code-simplifier/commands/simplify.md +8 -0
  148. package/skills/code-simplifier/references/code-quality-review.md +86 -0
  149. package/skills/code-simplifier/references/code-reuse-review.md +63 -0
  150. package/skills/code-simplifier/references/efficiency-review.md +81 -0
  151. package/skills/datadog/.plugin/plugin.json +19 -0
  152. package/skills/datadog/README.md +100 -0
  153. package/skills/datadog/SKILL.md +95 -0
  154. package/skills/deno/.plugin/plugin.json +18 -0
  155. package/skills/deno/README.md +5 -0
  156. package/skills/deno/SKILL.md +99 -0
  157. package/skills/deno/references/README.md +6 -0
  158. package/skills/discord/.plugin/plugin.json +18 -0
  159. package/skills/discord/README.md +31 -0
  160. package/skills/discord/SKILL.md +109 -0
  161. package/skills/discord/__init__.py +0 -0
  162. package/skills/discord/references/REFERENCE.md +78 -0
  163. package/skills/discord/scripts/__init__.py +0 -0
  164. package/skills/discord/scripts/_http.py +127 -0
  165. package/skills/discord/scripts/post_webhook.py +106 -0
  166. package/skills/discord/scripts/send_message.py +102 -0
  167. package/skills/docker/.plugin/plugin.json +17 -0
  168. package/skills/docker/README.md +34 -0
  169. package/skills/docker/SKILL.md +29 -0
  170. package/skills/evidence-based-citations/.plugin/plugin.json +20 -0
  171. package/skills/evidence-based-citations/README.md +31 -0
  172. package/skills/evidence-based-citations/SKILL.md +59 -0
  173. package/skills/flarglebargle/.plugin/plugin.json +16 -0
  174. package/skills/flarglebargle/README.md +14 -0
  175. package/skills/flarglebargle/SKILL.md +9 -0
  176. package/skills/frontend-design/.plugin/plugin.json +21 -0
  177. package/skills/frontend-design/LICENSE.txt +177 -0
  178. package/skills/frontend-design/README.md +42 -0
  179. package/skills/frontend-design/SKILL.md +42 -0
  180. package/skills/github/.plugin/plugin.json +19 -0
  181. package/skills/github/README.md +42 -0
  182. package/skills/github/SKILL.md +106 -0
  183. package/skills/github-pr-review/.plugin/plugin.json +18 -0
  184. package/skills/github-pr-review/README.md +145 -0
  185. package/skills/github-pr-review/SKILL.md +148 -0
  186. package/skills/github-pr-review/commands/github-pr-review.md +8 -0
  187. package/skills/github-pr-reviewer/.plugin/plugin.json +20 -0
  188. package/skills/github-pr-reviewer/README.md +34 -0
  189. package/skills/github-pr-reviewer/SKILL.md +89 -0
  190. package/skills/github-pr-reviewer/commands/pr-reviewer:setup.md +8 -0
  191. package/skills/github-repo-monitor/.plugin/plugin.json +22 -0
  192. package/skills/github-repo-monitor/README.md +70 -0
  193. package/skills/github-repo-monitor/SKILL.md +316 -0
  194. package/skills/github-repo-monitor/commands/github-monitor:poll.md +8 -0
  195. package/skills/github-repo-monitor/references/github-api.md +241 -0
  196. package/skills/github-repo-monitor/references/state-schema.md +160 -0
  197. package/skills/github-repo-monitor/scripts/main.py +915 -0
  198. package/skills/github-repo-monitor/tests/test_main.py +400 -0
  199. package/skills/gitlab/.plugin/plugin.json +17 -0
  200. package/skills/gitlab/README.md +37 -0
  201. package/skills/gitlab/SKILL.md +32 -0
  202. package/skills/incident-retrospective/.plugin/plugin.json +21 -0
  203. package/skills/incident-retrospective/README.md +34 -0
  204. package/skills/incident-retrospective/SKILL.md +98 -0
  205. package/skills/incident-retrospective/commands/incident-retro:setup.md +8 -0
  206. package/skills/iterate/.plugin/plugin.json +13 -0
  207. package/skills/iterate/README.md +25 -0
  208. package/skills/iterate/SKILL.md +399 -0
  209. package/skills/iterate/commands/babysit.md +8 -0
  210. package/skills/iterate/commands/iterate.md +8 -0
  211. package/skills/iterate/commands/verify.md +8 -0
  212. package/skills/iterate/references/heuristics.md +58 -0
  213. package/skills/iterate/references/verification.md +96 -0
  214. package/skills/jupyter/.plugin/plugin.json +18 -0
  215. package/skills/jupyter/README.md +55 -0
  216. package/skills/jupyter/SKILL.md +50 -0
  217. package/skills/kubernetes/.plugin/plugin.json +18 -0
  218. package/skills/kubernetes/README.md +53 -0
  219. package/skills/kubernetes/SKILL.md +48 -0
  220. package/skills/learn-from-code-review/.plugin/plugin.json +19 -0
  221. package/skills/learn-from-code-review/README.md +64 -0
  222. package/skills/learn-from-code-review/SKILL.md +186 -0
  223. package/skills/learn-from-code-review/commands/learn-from-reviews.md +8 -0
  224. package/skills/linear/.plugin/plugin.json +19 -0
  225. package/skills/linear/README.md +58 -0
  226. package/skills/linear/SKILL.md +213 -0
  227. package/skills/linear-triage/.plugin/plugin.json +21 -0
  228. package/skills/linear-triage/README.md +34 -0
  229. package/skills/linear-triage/SKILL.md +91 -0
  230. package/skills/linear-triage/commands/linear-triage:setup.md +8 -0
  231. package/skills/notion/.plugin/plugin.json +17 -0
  232. package/skills/notion/README.md +114 -0
  233. package/skills/notion/SKILL.md +109 -0
  234. package/skills/npm/.plugin/plugin.json +17 -0
  235. package/skills/npm/README.md +14 -0
  236. package/skills/npm/SKILL.md +9 -0
  237. package/skills/openhands-api/.plugin/plugin.json +22 -0
  238. package/skills/openhands-api/README.md +48 -0
  239. package/skills/openhands-api/SKILL.md +399 -0
  240. package/skills/openhands-api/references/README.md +33 -0
  241. package/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
  242. package/skills/openhands-api/references/example_prompt.md +12 -0
  243. package/skills/openhands-api/scripts/openhands_api.py +606 -0
  244. package/skills/openhands-api/scripts/openhands_api.ts +252 -0
  245. package/skills/openhands-automation/.plugin/plugin.json +19 -0
  246. package/skills/openhands-automation/README.md +89 -0
  247. package/skills/openhands-automation/SKILL.md +875 -0
  248. package/skills/openhands-automation/commands/automation:create.md +8 -0
  249. package/skills/openhands-automation/references/ab-testing.md +185 -0
  250. package/skills/openhands-automation/references/custom-automation.md +644 -0
  251. package/skills/openhands-sdk/.plugin/plugin.json +20 -0
  252. package/skills/openhands-sdk/README.md +22 -0
  253. package/skills/openhands-sdk/SKILL.md +229 -0
  254. package/skills/openhands-sdk/commands/sdk.md +8 -0
  255. package/skills/pdflatex/.plugin/plugin.json +18 -0
  256. package/skills/pdflatex/README.md +39 -0
  257. package/skills/pdflatex/SKILL.md +34 -0
  258. package/skills/prd/.plugin/plugin.json +19 -0
  259. package/skills/prd/README.md +28 -0
  260. package/skills/prd/SKILL.md +237 -0
  261. package/skills/prd/commands/prd.md +8 -0
  262. package/skills/qa-changes/README.md +18 -0
  263. package/skills/qa-changes/SKILL.md +229 -0
  264. package/skills/qa-changes/commands/qa-changes.md +8 -0
  265. package/skills/release-notes/README.md +24 -0
  266. package/skills/release-notes/SKILL.md +19 -0
  267. package/skills/release-notes/commands/release-notes.md +8 -0
  268. package/skills/research-brief/.plugin/plugin.json +20 -0
  269. package/skills/research-brief/README.md +34 -0
  270. package/skills/research-brief/SKILL.md +99 -0
  271. package/skills/research-brief/commands/research-brief:setup.md +8 -0
  272. package/skills/security/.plugin/plugin.json +18 -0
  273. package/skills/security/README.md +38 -0
  274. package/skills/security/SKILL.md +33 -0
  275. package/skills/skill-creator/.plugin/plugin.json +17 -0
  276. package/skills/skill-creator/LICENSE.txt +202 -0
  277. package/skills/skill-creator/README.md +182 -0
  278. package/skills/skill-creator/SKILL.md +545 -0
  279. package/skills/skill-creator/references/output-patterns.md +82 -0
  280. package/skills/skill-creator/references/workflows.md +28 -0
  281. package/skills/skill-creator/scripts/init_skill.py +303 -0
  282. package/skills/skill-creator/scripts/quick_validate.py +95 -0
  283. package/skills/slack-channel-monitor/.plugin/plugin.json +21 -0
  284. package/skills/slack-channel-monitor/README.md +91 -0
  285. package/skills/slack-channel-monitor/SKILL.md +276 -0
  286. package/skills/slack-channel-monitor/commands/slack-monitor:poll.md +8 -0
  287. package/skills/slack-channel-monitor/references/slack-api.md +207 -0
  288. package/skills/slack-channel-monitor/references/state-schema.md +180 -0
  289. package/skills/slack-channel-monitor/scripts/main.py +962 -0
  290. package/skills/slack-standup-digest/.plugin/plugin.json +21 -0
  291. package/skills/slack-standup-digest/README.md +34 -0
  292. package/skills/slack-standup-digest/SKILL.md +92 -0
  293. package/skills/slack-standup-digest/commands/standup-digest:setup.md +8 -0
  294. package/skills/spark-version-upgrade/.plugin/plugin.json +20 -0
  295. package/skills/spark-version-upgrade/README.md +54 -0
  296. package/skills/spark-version-upgrade/SKILL.md +233 -0
  297. package/skills/ssh/.plugin/plugin.json +18 -0
  298. package/skills/ssh/README.md +140 -0
  299. package/skills/ssh/SKILL.md +135 -0
  300. package/skills/swift-linux/.plugin/plugin.json +17 -0
  301. package/skills/swift-linux/README.md +86 -0
  302. package/skills/swift-linux/SKILL.md +81 -0
  303. package/skills/theme-factory/.plugin/plugin.json +19 -0
  304. package/skills/theme-factory/LICENSE.txt +202 -0
  305. package/skills/theme-factory/README.md +58 -0
  306. package/skills/theme-factory/SKILL.md +59 -0
  307. package/skills/theme-factory/theme-showcase.pdf +0 -0
  308. package/skills/theme-factory/themes/arctic-frost.md +19 -0
  309. package/skills/theme-factory/themes/botanical-garden.md +19 -0
  310. package/skills/theme-factory/themes/desert-rose.md +19 -0
  311. package/skills/theme-factory/themes/forest-canopy.md +19 -0
  312. package/skills/theme-factory/themes/golden-hour.md +19 -0
  313. package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  314. package/skills/theme-factory/themes/modern-minimalist.md +19 -0
  315. package/skills/theme-factory/themes/ocean-depths.md +19 -0
  316. package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  317. package/skills/theme-factory/themes/tech-innovation.md +19 -0
  318. package/skills/uv/.plugin/plugin.json +18 -0
  319. package/skills/uv/README.md +5 -0
  320. package/skills/uv/SKILL.md +95 -0
  321. package/skills/uv/references/README.md +5 -0
  322. package/skills/vercel/.plugin/plugin.json +18 -0
  323. package/skills/vercel/README.md +108 -0
  324. package/skills/vercel/SKILL.md +103 -0
  325. package/tests/test_add_skill_installs_to_agents_dir.py +42 -0
  326. package/tests/test_catalogs.py +109 -0
  327. package/tests/test_code_review_risk_evaluation.py +94 -0
  328. package/tests/test_issue_duplicate_checker.py +240 -0
  329. package/tests/test_openhands_api_python.py +152 -0
  330. package/tests/test_plugin_manifest.py +83 -0
  331. package/tests/test_pr_review_diff_payload.py +202 -0
  332. package/tests/test_pr_review_feedback.py +263 -0
  333. package/tests/test_pr_review_prompt.py +152 -0
  334. package/tests/test_pr_review_review_context.py +253 -0
  335. package/tests/test_qa_changes.py +232 -0
  336. package/tests/test_qa_changes_evaluation.py +259 -0
  337. package/tests/test_release_notes_generator.py +990 -0
  338. package/tests/test_sdk_loading.py +150 -0
  339. package/tests/test_skill_plugin_loading.py +149 -0
  340. package/tests/test_skills_have_readme.py +66 -0
  341. package/tests/test_sync_extensions.py +292 -0
  342. package/tests/test_workflow_sync.py +46 -0
  343. package/utils/analysis/README.md +7 -0
  344. package/utils/analysis/laminar_signals/README.md +211 -0
  345. package/utils/analysis/laminar_signals/analyze.py +780 -0
  346. package/utils/analysis/laminar_signals/templates/default.j2 +49 -0
  347. package/utils/analysis/laminar_signals/templates/pr_review.j2 +61 -0
@@ -0,0 +1,561 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Vulnerability Scan and Remediation Agent
4
+
5
+ This script runs a Trivy security scan on the repository, identifies vulnerabilities
6
+ above a severity threshold, and uses OpenHands agents to create PRs with fixes.
7
+
8
+ Usage:
9
+ python scan_and_remediate.py --scan-only # Only run Trivy scan, output results
10
+ python scan_and_remediate.py --remediate # Run remediation on existing scan results
11
+ python scan_and_remediate.py # Full scan + remediation (legacy mode)
12
+
13
+ Environment Variables:
14
+ LLM_API_KEY: API key for the LLM (required for remediation)
15
+ LLM_MODEL: Language model to use (default: anthropic/claude-sonnet-4-5-20250929)
16
+ LLM_BASE_URL: Optional base URL for LLM API
17
+ GITHUB_TOKEN: GitHub token for API access and creating PRs (required for remediation)
18
+ REPO_NAME: Repository name in format owner/repo (required for remediation)
19
+ SEVERITY_THRESHOLD: Minimum severity to remediate (default: HIGH)
20
+ MAX_VULNERABILITIES: Maximum vulnerabilities to fix per run (default: 5, 0=unlimited)
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import logging
28
+ import os
29
+ import subprocess
30
+ import sys
31
+ from dataclasses import dataclass
32
+ from pathlib import Path
33
+
34
+ # Set up basic logging for scan-only mode (no openhands dependency)
35
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
36
+ basic_logger = logging.getLogger(__name__)
37
+
38
+ SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
39
+
40
+
41
+ @dataclass
42
+ class Vulnerability:
43
+ """Represents a security vulnerability."""
44
+
45
+ vuln_id: str
46
+ package_name: str
47
+ installed_version: str
48
+ fixed_version: str | None
49
+ severity: str
50
+ title: str
51
+ description: str
52
+ target: str
53
+
54
+ @property
55
+ def has_fix(self) -> bool:
56
+ return self.fixed_version is not None and self.fixed_version != ""
57
+
58
+
59
+ def get_required_env(name: str) -> str:
60
+ """Get a required environment variable."""
61
+ value = os.getenv(name)
62
+ if not value:
63
+ raise ValueError(f"{name} environment variable is required")
64
+ return value
65
+
66
+
67
+ def run_trivy_scan(repo_path: str, logger) -> dict:
68
+ """Run Trivy scan on the repository and return results."""
69
+ logger.info("Running Trivy security scan...")
70
+
71
+ output_file = Path(repo_path) / "trivy-results.json"
72
+
73
+ cmd = [
74
+ "trivy",
75
+ "fs",
76
+ "--format",
77
+ "json",
78
+ "--output",
79
+ str(output_file),
80
+ "--severity",
81
+ "CRITICAL,HIGH,MEDIUM,LOW",
82
+ repo_path,
83
+ ]
84
+
85
+ try:
86
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
87
+ if result.returncode != 0 and not output_file.exists():
88
+ logger.error(f"Trivy scan failed: {result.stderr}")
89
+ raise RuntimeError(f"Trivy scan failed: {result.stderr}")
90
+
91
+ with open(output_file) as f:
92
+ return json.load(f)
93
+
94
+ except subprocess.TimeoutExpired:
95
+ raise RuntimeError("Trivy scan timed out after 10 minutes")
96
+ except json.JSONDecodeError as e:
97
+ raise RuntimeError(f"Failed to parse Trivy output: {e}")
98
+
99
+
100
+ def parse_vulnerabilities(trivy_output: dict) -> list[Vulnerability]:
101
+ """Parse Trivy output into Vulnerability objects."""
102
+ vulnerabilities = []
103
+
104
+ results = trivy_output.get("Results", [])
105
+ for result in results:
106
+ target = result.get("Target", "unknown")
107
+ vulns = result.get("Vulnerabilities", [])
108
+
109
+ for vuln in vulns:
110
+ vulnerabilities.append(
111
+ Vulnerability(
112
+ vuln_id=vuln.get("VulnerabilityID", ""),
113
+ package_name=vuln.get("PkgName", ""),
114
+ installed_version=vuln.get("InstalledVersion", ""),
115
+ fixed_version=vuln.get("FixedVersion"),
116
+ severity=vuln.get("Severity", "UNKNOWN"),
117
+ title=vuln.get("Title", ""),
118
+ description=vuln.get("Description", ""),
119
+ target=target,
120
+ )
121
+ )
122
+
123
+ return vulnerabilities
124
+
125
+
126
+ def filter_vulnerabilities(
127
+ vulnerabilities: list[Vulnerability],
128
+ severity_threshold: str,
129
+ max_count: int,
130
+ ) -> list[Vulnerability]:
131
+ """Filter vulnerabilities by severity threshold and limit count.
132
+
133
+ Note: severity_threshold should be validated before calling this function.
134
+ """
135
+ # Ensure valid threshold (defensive, validation should happen earlier)
136
+ if severity_threshold not in SEVERITY_ORDER:
137
+ severity_threshold = "HIGH"
138
+
139
+ threshold_index = SEVERITY_ORDER.index(severity_threshold)
140
+ allowed_severities = set(SEVERITY_ORDER[: threshold_index + 1])
141
+
142
+ # Filter by severity and only include those with fixes
143
+ filtered = [
144
+ v
145
+ for v in vulnerabilities
146
+ if v.severity in allowed_severities and v.has_fix
147
+ ]
148
+
149
+ # Sort by severity (most critical first)
150
+ filtered.sort(key=lambda v: SEVERITY_ORDER.index(v.severity))
151
+
152
+ # Limit count
153
+ if max_count > 0:
154
+ filtered = filtered[:max_count]
155
+
156
+ return filtered
157
+
158
+
159
+ def create_remediation_prompt(vuln: Vulnerability, repo_name: str) -> str:
160
+ """Create the prompt for the remediation agent."""
161
+ return f"""You are a security engineer tasked with fixing a vulnerability in a repository.
162
+
163
+ ## Repository
164
+ {repo_name}
165
+
166
+ ## Vulnerability Details
167
+ - **ID**: {vuln.vuln_id}
168
+ - **Package**: {vuln.package_name}
169
+ - **Severity**: {vuln.severity}
170
+ - **Current Version**: {vuln.installed_version}
171
+ - **Fixed Version**: {vuln.fixed_version}
172
+ - **Location**: {vuln.target}
173
+ - **Title**: {vuln.title}
174
+ - **Description**: {vuln.description}
175
+
176
+ ## Your Task
177
+
178
+ 1. **Analyze** the vulnerability and understand what needs to be fixed
179
+
180
+ 2. **Find ALL dependency files across the repository**
181
+ **CRITICAL**: Search the repository for ALL dependency files that contain the vulnerable package. Do NOT assume dependencies only exist in the root directory.
182
+
183
+ ```bash
184
+ # Find ALL dependency files containing the package (excludes dependency/build directories)
185
+ find . \( -name node_modules -o -name .venv -o -name venv -o -name vendor -o -name .git -o -name __pycache__ -o -name dist -o -name build \) -prune -o -name "pyproject.toml" -exec grep -l "{vuln.package_name}" {} + 2>/dev/null
186
+ find . \( -name node_modules -o -name .venv -o -name venv -o -name vendor -o -name .git \) -prune -o -name "requirements*.txt" -exec grep -l "{vuln.package_name}" {} + 2>/dev/null
187
+ find . \( -name node_modules -o -name .venv -o -name venv -o -name vendor -o -name .git \) -prune -o -name "package.json" -exec grep -l "{vuln.package_name}" {} + 2>/dev/null
188
+ ```
189
+
190
+ Update {vuln.package_name} from {vuln.installed_version} to {vuln.fixed_version} in **EVERY** file found.
191
+
192
+ 3. **CRITICAL: Sync/regenerate ALL lockfiles in the repository**
193
+ After updating the version in ALL manifest files, you MUST regenerate ALL corresponding lockfiles. Do NOT manually edit lockfiles.
194
+
195
+ **CRITICAL**: First, find ALL lockfiles (excluding dependency/build directories):
196
+ ```bash
197
+ find . \\( -name node_modules -o -name .venv -o -name venv -o -name vendor -o -name .git \\) -prune -o \\( -name "poetry.lock" -o -name "uv.lock" -o -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" -o -name "Cargo.lock" -o -name "go.sum" \\) -print
198
+ ```
199
+
200
+ You MUST regenerate EVERY lockfile found, not just those in the root directory.
201
+
202
+ **IMPORTANT**: To avoid unnecessary diff noise, you MUST detect and use the same tool version that originally generated each lockfile. Each lockfile contains a version header that indicates which tool version was used.
203
+
204
+ **Note**: This agent runs in an isolated execution environment (container), so installing specific tool versions with `--force` will not affect other projects or system-wide installations.
205
+
206
+ For EACH lockfile found, `cd` to its directory and regenerate it:
207
+
208
+ - **Poetry (pyproject.toml + poetry.lock)**:
209
+ 1. `cd` to the directory containing the lockfile
210
+ 2. Extract version: `grep -m1 "^# This file is automatically @generated by Poetry" poetry.lock | sed 's/.*Poetry \\([0-9.]*\\).*/\\1/'`
211
+ 3. If a version is found, install it: `pipx install poetry==$POETRY_VERSION --force`
212
+ 4. Verify installation: `poetry --version | grep "$POETRY_VERSION"` (proceed only if successful)
213
+ 5. If version extraction fails or returns empty, proceed with the currently installed version and note this in your output
214
+ 6. Run: `poetry lock --no-update` or `poetry update {vuln.package_name}`
215
+
216
+ - **uv (pyproject.toml + uv.lock)**:
217
+ 1. `cd` to the directory containing the lockfile
218
+ 2. Extract version: `grep -m1 "^# This file was autogenerated by uv" uv.lock | sed 's/.*uv version \\([0-9.]*\\).*/\\1/'`
219
+ 3. If a version is found, install it: `pipx install uv==$UV_VERSION --force`
220
+ 4. Verify installation: `uv --version | grep "$UV_VERSION"` (proceed only if successful)
221
+ 5. If version extraction fails or returns empty, proceed with the currently installed version and note this in your output
222
+ 6. Run: `uv lock --upgrade-package {vuln.package_name}` or `uv sync`
223
+
224
+ - **npm (package.json + package-lock.json)**: `cd` to directory, run `npm install` or `npm update {vuln.package_name}`
225
+ - **yarn (package.json + yarn.lock)**: `cd` to directory, run `yarn install` or `yarn upgrade {vuln.package_name}`
226
+ - **pnpm (package.json + pnpm-lock.yaml)**: `cd` to directory, run `pnpm install` or `pnpm update {vuln.package_name}`
227
+ - **pip (requirements.txt)**: Update the version directly in requirements.txt
228
+ - **Go (go.mod + go.sum)**: `cd` to directory, run `go mod tidy`
229
+ - **Cargo (Cargo.toml + Cargo.lock)**: `cd` to directory, run `cargo update -p {vuln.package_name}`
230
+ - **Maven (pom.xml)**: Update the version directly in pom.xml
231
+ - **Gradle**: Update the version in build.gradle/build.gradle.kts
232
+
233
+ 4. **Verify** the change doesn't break the build (run any available build/test commands)
234
+ 5. **Create a branch** named `fix/{vuln.vuln_id.lower()}`
235
+ 6. **Commit** your changes with a clear message explaining the security fix
236
+ 7. **Push** the branch to origin
237
+ 8. **Create a Pull Request** using the GitHub CLI:
238
+ ```bash
239
+ gh pr create --title "fix: {vuln.vuln_id} - Update {vuln.package_name} to {vuln.fixed_version}" \\
240
+ --body "## Security Fix
241
+
242
+ This PR addresses {vuln.vuln_id} ({vuln.severity} severity).
243
+
244
+ ### Vulnerability
245
+ {vuln.title}
246
+
247
+ ### Changes
248
+ - Updated `{vuln.package_name}` from `{vuln.installed_version}` to `{vuln.fixed_version}`
249
+
250
+ ### References
251
+ - https://nvd.nist.gov/vuln/detail/{vuln.vuln_id}
252
+ "
253
+ ```
254
+
255
+ ## Important Notes
256
+ - Do NOT modify any code beyond what's necessary for the fix
257
+ - Do NOT manually edit lockfiles - always use the package manager commands above
258
+ - If the package update requires other dependency changes, include them
259
+ - If you encounter conflicts or issues, document them in the PR description
260
+ - Always test that the fix doesn't break the build before creating the PR
261
+ """
262
+
263
+
264
+ def create_agent(config: dict, logger):
265
+ """Create and configure the remediation agent."""
266
+ from openhands.sdk import LLM, Agent
267
+ from openhands.tools.preset.default import get_default_condenser, get_default_tools
268
+
269
+ llm_config = {
270
+ "model": config["model"],
271
+ "api_key": config["api_key"],
272
+ "usage_id": "vulnerability_remediation",
273
+ "drop_params": True,
274
+ }
275
+ if config.get("base_url"):
276
+ llm_config["base_url"] = config["base_url"]
277
+
278
+ llm = LLM(**llm_config)
279
+
280
+ return Agent(
281
+ llm=llm,
282
+ tools=get_default_tools(enable_browser=False),
283
+ system_prompt_kwargs={"cli_mode": True},
284
+ condenser=get_default_condenser(
285
+ llm=llm.model_copy(update={"usage_id": "condenser"})
286
+ ),
287
+ )
288
+
289
+
290
+ def remediate_vulnerability(
291
+ agent,
292
+ vuln: Vulnerability,
293
+ repo_name: str,
294
+ secrets: dict[str, str],
295
+ logger,
296
+ ) -> dict:
297
+ """Run the remediation agent for a single vulnerability."""
298
+ from openhands.sdk import Conversation
299
+
300
+ logger.info(f"Remediating {vuln.vuln_id} ({vuln.severity}): {vuln.package_name}")
301
+
302
+ prompt = create_remediation_prompt(vuln, repo_name)
303
+ cwd = os.getcwd()
304
+
305
+ conversation = Conversation(
306
+ agent=agent,
307
+ workspace=cwd,
308
+ secrets=secrets,
309
+ )
310
+
311
+ try:
312
+ conversation.send_message(prompt)
313
+ conversation.run()
314
+
315
+ metrics = conversation.conversation_stats.get_combined_metrics()
316
+
317
+ return {
318
+ "vuln_id": vuln.vuln_id,
319
+ "package": vuln.package_name,
320
+ "severity": vuln.severity,
321
+ "status": "completed",
322
+ "cost": metrics.accumulated_cost,
323
+ }
324
+
325
+ except Exception as e:
326
+ logger.error(f"Failed to remediate {vuln.vuln_id}: {type(e).__name__}: {e}")
327
+ return {
328
+ "vuln_id": vuln.vuln_id,
329
+ "package": vuln.package_name,
330
+ "severity": vuln.severity,
331
+ "status": "failed",
332
+ "error": str(e),
333
+ "error_type": type(e).__name__,
334
+ }
335
+
336
+
337
+ def save_report(vulnerabilities: list[Vulnerability], results: list[dict], logger) -> None:
338
+ """Save the remediation report."""
339
+ report = {
340
+ "total_vulnerabilities_found": len(vulnerabilities),
341
+ "remediation_results": results,
342
+ "summary": {
343
+ "completed": len([r for r in results if r["status"] == "completed"]),
344
+ "failed": len([r for r in results if r["status"] == "failed"]),
345
+ "total_cost": sum(r.get("cost", 0) for r in results),
346
+ },
347
+ }
348
+
349
+ with open("remediation-report.json", "w") as f:
350
+ json.dump(report, f, indent=2)
351
+
352
+ logger.info("Remediation report saved to remediation-report.json")
353
+
354
+
355
+ def save_scan_results(
356
+ all_vulns: list[Vulnerability],
357
+ vulns_to_fix: list[Vulnerability],
358
+ severity_threshold: str,
359
+ logger,
360
+ ) -> None:
361
+ """Save scan results for the action to read."""
362
+ scan_results = {
363
+ "total_vulnerabilities": len(all_vulns),
364
+ "vulnerabilities_to_fix": len(vulns_to_fix),
365
+ "severity_threshold": severity_threshold,
366
+ "vulnerabilities": [
367
+ {
368
+ "vuln_id": v.vuln_id,
369
+ "package_name": v.package_name,
370
+ "installed_version": v.installed_version,
371
+ "fixed_version": v.fixed_version,
372
+ "severity": v.severity,
373
+ "title": v.title,
374
+ "target": v.target,
375
+ }
376
+ for v in vulns_to_fix
377
+ ],
378
+ }
379
+
380
+ with open("scan-results.json", "w") as f:
381
+ json.dump(scan_results, f, indent=2)
382
+
383
+ logger.info("Scan results saved to scan-results.json")
384
+
385
+
386
+ def validate_severity_threshold(severity_threshold: str, logger) -> str:
387
+ """Validate severity threshold, warn and default to HIGH if invalid."""
388
+ if severity_threshold not in SEVERITY_ORDER:
389
+ logger.warning(
390
+ f"Invalid severity threshold '{severity_threshold}'. "
391
+ f"Valid options: {', '.join(SEVERITY_ORDER)}. Defaulting to 'HIGH'."
392
+ )
393
+ return "HIGH"
394
+ return severity_threshold
395
+
396
+
397
+ def run_scan_only():
398
+ """Run only the Trivy scan and save results (no OpenHands dependency)."""
399
+ logger = basic_logger
400
+ logger.info("Running vulnerability scan (scan-only mode)...")
401
+
402
+ severity_threshold = os.getenv("SEVERITY_THRESHOLD", "HIGH")
403
+ severity_threshold = validate_severity_threshold(severity_threshold, logger)
404
+ max_vulns = int(os.getenv("MAX_VULNERABILITIES", "5"))
405
+
406
+ logger.info(f"Severity threshold: {severity_threshold}")
407
+ logger.info(f"Max vulnerabilities: {max_vulns if max_vulns > 0 else 'unlimited'}")
408
+
409
+ # Run Trivy scan
410
+ repo_path = os.getcwd()
411
+ trivy_output = run_trivy_scan(repo_path, logger)
412
+
413
+ # Parse and filter vulnerabilities
414
+ all_vulns = parse_vulnerabilities(trivy_output)
415
+ logger.info(f"Found {len(all_vulns)} total vulnerabilities")
416
+
417
+ vulns_to_fix = filter_vulnerabilities(all_vulns, severity_threshold, max_vulns)
418
+ logger.info(
419
+ f"Filtered to {len(vulns_to_fix)} vulnerabilities "
420
+ f"({severity_threshold}+ with available fixes)"
421
+ )
422
+
423
+ # Save scan results for the action to read
424
+ save_scan_results(all_vulns, vulns_to_fix, severity_threshold, logger)
425
+
426
+ print(f"\n=== Vulnerability Scan Summary ===")
427
+ print(f"Total Vulnerabilities Found: {len(all_vulns)}")
428
+ print(f"Vulnerabilities to Remediate: {len(vulns_to_fix)}")
429
+
430
+ if not vulns_to_fix:
431
+ logger.info("✅ No vulnerabilities found that need remediation!")
432
+ else:
433
+ logger.info(f"🔍 Found {len(vulns_to_fix)} vulnerabilities to remediate")
434
+
435
+
436
+ def run_remediation():
437
+ """Run remediation using existing scan results (requires OpenHands)."""
438
+ from openhands.sdk import LLM, Agent, Conversation, get_logger
439
+ from openhands.tools.preset.default import get_default_condenser, get_default_tools
440
+
441
+ logger = get_logger(__name__)
442
+ logger.info("Running vulnerability remediation...")
443
+
444
+ # Load scan results
445
+ scan_results_file = Path("scan-results.json")
446
+ if not scan_results_file.exists():
447
+ logger.error("No scan results found. Run with --scan-only first.")
448
+ sys.exit(1)
449
+
450
+ with open(scan_results_file) as f:
451
+ scan_results = json.load(f)
452
+
453
+ vulns_data = scan_results.get("vulnerabilities", [])
454
+ if not vulns_data:
455
+ logger.info("No vulnerabilities to remediate!")
456
+ return
457
+
458
+ # Convert to Vulnerability objects
459
+ vulns_to_fix = [
460
+ Vulnerability(
461
+ vuln_id=v["vuln_id"],
462
+ package_name=v["package_name"],
463
+ installed_version=v["installed_version"],
464
+ fixed_version=v["fixed_version"],
465
+ severity=v["severity"],
466
+ title=v["title"],
467
+ description="", # Not stored in scan results
468
+ target=v["target"],
469
+ )
470
+ for v in vulns_data
471
+ ]
472
+
473
+ # Get configuration
474
+ config = {
475
+ "api_key": get_required_env("LLM_API_KEY"),
476
+ "model": os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
477
+ "base_url": os.getenv("LLM_BASE_URL"),
478
+ }
479
+
480
+ github_token = get_required_env("GITHUB_TOKEN")
481
+ repo_name = get_required_env("REPO_NAME")
482
+
483
+ logger.info(f"Repository: {repo_name}")
484
+ logger.info(f"Remediating {len(vulns_to_fix)} vulnerabilities...")
485
+
486
+ # Create agent and remediate
487
+ agent = create_agent(config, logger)
488
+ secrets = {
489
+ "LLM_API_KEY": config["api_key"],
490
+ "GITHUB_TOKEN": github_token,
491
+ }
492
+
493
+ results = []
494
+ for vuln in vulns_to_fix:
495
+ result = remediate_vulnerability(agent, vuln, repo_name, secrets, logger)
496
+ results.append(result)
497
+
498
+ # Save report
499
+ save_report(vulns_to_fix, results, logger)
500
+
501
+ # Print summary
502
+ completed = len([r for r in results if r["status"] == "completed"])
503
+ failed = len([r for r in results if r["status"] == "failed"])
504
+ total_cost = sum(r.get("cost", 0) for r in results)
505
+
506
+ print("\n=== Vulnerability Remediation Summary ===")
507
+ print(f"Attempted Remediations: {len(results)}")
508
+ print(f"Completed: {completed}")
509
+ print(f"Failed: {failed}")
510
+ print(f"Total Cost: ${total_cost:.6f}")
511
+
512
+ if failed > 0:
513
+ logger.warning(f"{failed} remediations failed - check logs for details")
514
+ sys.exit(1)
515
+
516
+ logger.info("Vulnerability remediation completed successfully")
517
+
518
+
519
+ def main():
520
+ """Run vulnerability scan and remediation (legacy mode - full workflow).
521
+
522
+ This combines scan-only and remediate modes for backward compatibility.
523
+ Prefer using --scan-only and --remediate separately for better control.
524
+ """
525
+ # Run scan first
526
+ run_scan_only()
527
+
528
+ # Check if there are vulnerabilities to fix
529
+ scan_results_file = Path("scan-results.json")
530
+ if scan_results_file.exists():
531
+ with open(scan_results_file) as f:
532
+ scan_results = json.load(f)
533
+ if scan_results.get("vulnerabilities_to_fix", 0) > 0:
534
+ # Run remediation
535
+ run_remediation()
536
+
537
+
538
+ if __name__ == "__main__":
539
+ parser = argparse.ArgumentParser(
540
+ description="Vulnerability Scan and Remediation Agent"
541
+ )
542
+ parser.add_argument(
543
+ "--scan-only",
544
+ action="store_true",
545
+ help="Only run Trivy scan, save results without starting agent",
546
+ )
547
+ parser.add_argument(
548
+ "--remediate",
549
+ action="store_true",
550
+ help="Run remediation using existing scan results",
551
+ )
552
+
553
+ args = parser.parse_args()
554
+
555
+ if args.scan_only:
556
+ run_scan_only()
557
+ elif args.remediate:
558
+ run_remediation()
559
+ else:
560
+ # Legacy mode: full scan + remediation
561
+ main()
@@ -0,0 +1,87 @@
1
+ # Vulnerability Scan and Remediation Workflow
2
+ #
3
+ # This is a thin wrapper that uses the OpenHands vulnerability-remediation action.
4
+ # It auto-updates when the action in OpenHands/extensions is updated.
5
+ #
6
+ # INSTALLATION:
7
+ # curl -o .github/workflows/vulnerability-scan.yml \
8
+ # https://raw.githubusercontent.com/OpenHands/extensions/main/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml
9
+ #
10
+ # REQUIRED SECRETS:
11
+ # - LLM_API_KEY: API key for your LLM provider (OpenAI, Anthropic, etc.)
12
+ #
13
+ # OPTIONAL SECRETS:
14
+ # - PAT_TOKEN: GitHub PAT for creating PRs (uses GITHUB_TOKEN if not set)
15
+ #
16
+ # The action will:
17
+ # 1. Run a Trivy security scan
18
+ # 2. Skip agent if no vulnerabilities found (saves costs)
19
+ # 3. Create PRs for fixable vulnerabilities
20
+
21
+ name: Vulnerability Scan and Remediation
22
+
23
+ on:
24
+ # Run weekly on Monday at 9am UTC
25
+ schedule:
26
+ - cron: '0 9 * * 1'
27
+
28
+ # Allow manual trigger
29
+ workflow_dispatch:
30
+ inputs:
31
+ severity_threshold:
32
+ description: 'Minimum severity to remediate'
33
+ required: false
34
+ default: 'HIGH'
35
+ type: choice
36
+ options:
37
+ - CRITICAL
38
+ - HIGH
39
+ - MEDIUM
40
+ - LOW
41
+ max_vulnerabilities:
42
+ description: 'Maximum vulnerabilities to fix (0 = unlimited)'
43
+ required: false
44
+ default: '5'
45
+ type: string
46
+ llm_model:
47
+ description: 'LLM model to use for remediation'
48
+ required: false
49
+ default: 'anthropic/claude-sonnet-4-5-20250929'
50
+ type: string
51
+ llm_base_url:
52
+ description: 'Custom LLM base URL (optional)'
53
+ required: false
54
+ default: ''
55
+ type: string
56
+
57
+ permissions:
58
+ contents: write
59
+ pull-requests: write
60
+ security-events: read
61
+
62
+ jobs:
63
+ scan-and-remediate:
64
+ runs-on: ubuntu-latest
65
+ timeout-minutes: 60
66
+
67
+ steps:
68
+ # Uses @main to auto-update when the action is improved
69
+ - name: Run Vulnerability Remediation
70
+ id: remediate
71
+ uses: OpenHands/extensions/plugins/vulnerability-remediation@main
72
+ with:
73
+ severity-threshold: ${{ inputs.severity_threshold || 'HIGH' }}
74
+ max-vulnerabilities: ${{ inputs.max_vulnerabilities || '5' }}
75
+ llm-model: ${{ inputs.llm_model || 'anthropic/claude-sonnet-4-5-20250929' }}
76
+ llm-base-url: ${{ inputs.llm_base_url || '' }}
77
+ llm-api-key: ${{ secrets.LLM_API_KEY }}
78
+ github-token: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC || secrets.GITHUB_TOKEN }}
79
+
80
+ - name: Summary
81
+ run: |
82
+ echo "### Vulnerability Scan Results" >> $GITHUB_STEP_SUMMARY
83
+ if [ "${{ steps.remediate.outputs.scan-only }}" == "true" ]; then
84
+ echo "✅ No vulnerabilities found that need remediation." >> $GITHUB_STEP_SUMMARY
85
+ else
86
+ echo "🔍 Found ${{ steps.remediate.outputs.vulnerabilities-found }} vulnerabilities to remediate." >> $GITHUB_STEP_SUMMARY
87
+ fi
package/pyproject.toml ADDED
@@ -0,0 +1,12 @@
1
+ [project]
2
+ name = "extensions"
3
+ version = "0.2.0"
4
+ description = "OpenHands extensions, plugins, and skills"
5
+ requires-python = ">=3.12"
6
+
7
+ [dependency-groups]
8
+ test = [
9
+ "pytest>=8.0",
10
+ "requests>=2.31",
11
+ "openhands-sdk>=0.3",
12
+ ]
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "bump-minor-pre-major": true,
4
+ "bootstrap-sha": "5dd68ff47a197a7f8e7cdd7a32a6dcb3702cbd7f",
5
+ "packages": {
6
+ ".": {
7
+ "release-type": "node",
8
+ "changelog-type": "github",
9
+ "include-component-in-tag": false,
10
+ "skip-changelog": true,
11
+ "extra-files": [
12
+ { "type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version" }
13
+ ]
14
+ }
15
+ }
16
+ }