pompelmi 1.7.0 → 1.9.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.
- package/BADGE.md +29 -0
- package/README.md +25 -2
- package/action/Dockerfile +8 -14
- package/action/package.json +11 -0
- package/action/scanner.js +133 -1
- package/action.yml +5 -0
- package/package.json +2 -1
- package/src/index.js +2 -1
- package/src/middleware.js +36 -0
- package/types/index.d.ts +82 -0
- package/pr_info.tmp +0 -2
package/BADGE.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Scanned by pompelmi — Badge
|
|
2
|
+
|
|
3
|
+
Add this badge to your repository's `README.md` to show that your file uploads are scanned with pompelmi.
|
|
4
|
+
|
|
5
|
+
## Markdown
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
[](https://github.com/pompelmi/pompelmi)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## HTML
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<a href="https://github.com/pompelmi/pompelmi">
|
|
15
|
+
<img src="https://img.shields.io/badge/scanned%20by-pompelmi-orange?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAQxlWElmTU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAA5AAAAcgE7AAIAAAASAAAArIdpAAQAAAABAAAAvgAAAAAAAABgAAAAAQAAAGAAAAABQ2FudmEgZG9jPURBSEdqUE42M19JIHVzZXI9VUFHZVZYTlJxNEkgYnJhbmQ9QkFHZVZib2RxREkAAFRvbW1hc28gQmVydG9jY2hpAAAGkAAABwAAAAQwMjEwkQEABwAAAAQBAgMAoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAOoAMABAAAAAEAAAAOAAAAAOn+IX8AAAAJcEhZcwAADsQAAA7EAZUrDhsAAAZHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjY1NTM1PC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xNTAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6RXhpZlZlcnNpb24+MDIxMDwvZXhpZjpFeGlmVmVyc2lvbj4KICAgICAgICAgPGV4aWY6Rmxhc2hQaXhWZXJzaW9uPjAxMDA8L2V4aWY6Rmxhc2hQaXhWZXJzaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTUwMDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaT4xPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGk+MjwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpPjM8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaT4wPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9leGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkNhbnZhIGRvYz1EQUhHalBONjNfSSB1c2VyPVVBR2VWWE5ScTRJIGJyYW5kPUJBR2VWYm9kcURJPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjk2PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj45NjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPGRjOnRpdGxlPgogICAgICAgICAgICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij5Qcm9nZXR0byBzZW56YSB0aXRvbG8gLSAxPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOkFsdD4KICAgICAgICAgPC9kYzp0aXRsZT4KICAgICAgICAgPGRjOmNyZWF0b3I+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpPlRvbW1hc28gQmVydG9jY2hpPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9kYzpjcmVhdG9yPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4Kn4j/DQAAAeFJREFUKBWtUjtrFFEU/u68Z5zs7LLPhEgEG0ljETGFImgnNhKsYiEBLYU0KqiERaxEQbcRK8HCiH/AUrFVEEQQ0YiurvvIRp2d2ZnZeezJzIZZSAxi4S3uOffc73zfOede4H8vImJ/4/zj8t7Tc3MdixaYJ70RcsaL6uKd7m4E3M6g6TgrduBf7TQbF463zQX//rXDOzHJeZvixdrJTC7a89hreqdO62VntlzUZD+MAsd96HDqcuXS7X5KIqROYi8fWgom1nsFcX4SimFoYbeF4P1bXhUH54Mw+BZDbqT4can0HEIh7C4rhdx8xMtwbRv+18/wLROm08dgODz7sVaT08SRYjJB/+Wjm+KHzpWNxiswXQE/sxcYAoHVBjERJHIZs/NOiRMHSfKW4u+7hr9uLXk/XUCSwHEcqNWCVNkHafYIpHwJvKquTZ84GqaKW4nZZuS7Zhj0TbC8AfBirJqBV6+DYi7SJ6GVZuaMbOkZ/XitjRUZu2UNNXFVmVBAn9bANroQBA5CpQyKeyTPhZTVFebZByAbo/bGU6XWl2qvOE2cyo6xYtaPdPaEK04dVGR5kTxbo1+dJvrSCsuf6SWK295xVD/FMQYa+fHWfnB9v+4MDNP8Xp+qru76i1LsP9lNSkO4P3HUKYoAAAAASUVORK5CYII=" alt="Scanned by pompelmi">
|
|
16
|
+
</a>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## RST (reStructuredText)
|
|
20
|
+
|
|
21
|
+
```rst
|
|
22
|
+
.. image:: https://img.shields.io/badge/scanned%20by-pompelmi-orange?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAQxlWElmTU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAA5AAAAcgE7AAIAAAASAAAArIdpAAQAAAABAAAAvgAAAAAAAABgAAAAAQAAAGAAAAABQ2FudmEgZG9jPURBSEdqUE42M19JIHVzZXI9VUFHZVZYTlJxNEkgYnJhbmQ9QkFHZVZib2RxREkAAFRvbW1hc28gQmVydG9jY2hpAAAGkAAABwAAAAQwMjEwkQEABwAAAAQBAgMAoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAOoAMABAAAAAEAAAAOAAAAAOn+IX8AAAAJcEhZcwAADsQAAA7EAZUrDhsAAAZHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjY1NTM1PC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xNTAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6RXhpZlZlcnNpb24+MDIxMDwvZXhpZjpFeGlmVmVyc2lvbj4KICAgICAgICAgPGV4aWY6Rmxhc2hQaXhWZXJzaW9uPjAxMDA8L2V4aWY6Rmxhc2hQaXhWZXJzaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTUwMDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaT4xPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGk+MjwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpPjM8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaT4wPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9leGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkNhbnZhIGRvYz1EQUhHalBONjNfSSB1c2VyPVVBR2VWWE5ScTRJIGJyYW5kPUJBR2VWYm9kcURJPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjk2PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj45NjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPGRjOnRpdGxlPgogICAgICAgICAgICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij5Qcm9nZXR0byBzZW56YSB0aXRvbG8gLSAxPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOkFsdD4KICAgICAgICAgPC9kYzp0aXRsZT4KICAgICAgICAgPGRjOmNyZWF0b3I+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpPlRvbW1hc28gQmVydG9jY2hpPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9kYzpjcmVhdG9yPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4Kn4j/DQAAAeFJREFUKBWtUjtrFFEU/u68Z5zs7LLPhEgEG0ljETGFImgnNhKsYiEBLYU0KqiERaxEQbcRK8HCiH/AUrFVEEQQ0YiurvvIRp2d2ZnZeezJzIZZSAxi4S3uOffc73zfOede4H8vImJ/4/zj8t7Tc3MdixaYJ70RcsaL6uKd7m4E3M6g6TgrduBf7TQbF463zQX//rXDOzHJeZvixdrJTC7a89hreqdO62VntlzUZD+MAsd96HDqcuXS7X5KIqROYi8fWgom1nsFcX4SimFoYbeF4P1bXhUH54Mw+BZDbqT4can0HEIh7C4rhdx8xMtwbRv+18/wLROm08dgODz7sVaT08SRYjJB/+Wjm+KHzpWNxiswXQE/sxcYAoHVBjERJHIZs/NOiRMHSfKW4u+7hr9uLXk/XUCSwHEcqNWCVNkHafYIpHwJvKquTZ84GqaKW4nZZuS7Zhj0TbC8AfBirJqBV6+DYi7SJ6GVZuaMbOkZ/XitjRUZu2UNNXFVmVBAn9bANroQBA5CpQyKeyTPhZTVFebZByAbo/bGU6XWl2qvOE2cyo6xYtaPdPaEK04dVGR5kTxbo1+dJvrSCsuf6SWK295xVD/FMQYa+fHWfnB9v+4MDNP8Xp+qru76i1LsP9lNSkO4P3HUKYoAAAAASUVORK5CYII=
|
|
23
|
+
:target: https://github.com/pompelmi/pompelmi
|
|
24
|
+
:alt: Scanned by pompelmi
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Preview
|
|
28
|
+
|
|
29
|
+
[](https://github.com/pompelmi/pompelmi)
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
<img src="https://img.shields.io/badge/license-ISC-blue" alt="license">
|
|
16
16
|
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml"><img src="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
17
17
|
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/release.yml"><img src="https://github.com/pompelmi/pompelmi/actions/workflows/release.yml/badge.svg" alt="npm publish"></a>
|
|
18
|
+
<img src="https://img.shields.io/badge/TypeScript-types%20included-3178c6?logo=typescript&logoColor=white" alt="TypeScript types included">
|
|
19
|
+
<a href="https://github.com/pompelmi/pompelmi"><img src="https://img.shields.io/badge/scanned%20by-pompelmi-orange?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAQxlWElmTU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAA5AAAAcgE7AAIAAAASAAAArIdpAAQAAAABAAAAvgAAAAAAAABgAAAAAQAAAGAAAAABQ2FudmEgZG9jPURBSEdqUE42M19JIHVzZXI9VUFHZVZYTlJxNEkgYnJhbmQ9QkFHZVZib2RxREkAAFRvbW1hc28gQmVydG9jY2hpAAAGkAAABwAAAAQwMjEwkQEABwAAAAQBAgMAoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAOoAMABAAAAAEAAAAOAAAAAOn+IX8AAAAJcEhZcwAADsQAAA7EAZUrDhsAAAZHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjY1NTM1PC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xNTAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6RXhpZlZlcnNpb24+MDIxMDwvZXhpZjpFeGlmVmVyc2lvbj4KICAgICAgICAgPGV4aWY6Rmxhc2hQaXhWZXJzaW9uPjAxMDA8L2V4aWY6Rmxhc2hQaXhWZXJzaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTUwMDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaT4xPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGk+MjwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpPjM8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaT4wPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9leGlmOkNvbXBvbmVudHNDb25maWd1cmF0aW9uPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkNhbnZhIGRvYz1EQUhHalBONjNfSSB1c2VyPVVBR2VWWE5ScTRJIGJyYW5kPUJBR2VWYm9kcURJPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjk2PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj45NjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPGRjOnRpdGxlPgogICAgICAgICAgICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij5Qcm9nZXR0byBzZW56YSB0aXRvbG8gLSAxPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOkFsdD4KICAgICAgICAgPC9kYzp0aXRsZT4KICAgICAgICAgPGRjOmNyZWF0b3I+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpPlRvbW1hc28gQmVydG9jY2hpPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9kYzpjcmVhdG9yPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4Kn4j/DQAAAeFJREFUKBWtUjtrFFEU/u68Z5zs7LLPhEgEG0ljETGFImgnNhKsYiEBLYU0KqiERaxEQbcRK8HCiH/AUrFVEEQQ0YiurvvIRp2d2ZnZeezJzIZZSAxi4S3uOffc73zfOede4H8vImJ/4/zj8t7Tc3MdixaYJ70RcsaL6uKd7m4E3M6g6TgrduBf7TQbF463zQX//rXDOzHJeZvixdrJTC7a89hreqdO62VntlzUZD+MAsd96HDqcuXS7X5KIqROYi8fWgom1nsFcX4SimFoYbeF4P1bXhUH54Mw+BZDbqT4can0HEIh7C4rhdx8xMtwbRv+18/wLROm08dgODz7sVaT08SRYjJB/+Wjm+KHzpWNxiswXQE/sxcYAoHVBjERJHIZs/NOiRMHSfKW4u+7hr9uLXk/XUCSwHEcqNWCVNkHafYIpHwJvKquTZ84GqaKW4nZZuS7Zhj0TbC8AfBirJqBV6+DYi7SJ6GVZuaMbOkZ/XitjRUZu2UNNXFVmVBAn9bANroQBA5CpQyKeyTPhZTVFebZByAbo/bGU6XWl2qvOE2cyo6xYtaPdPaEK04dVGR5kTxbo1+dJvrSCsuf6SWK295xVD/FMQYa+fHWfnB9v+4MDNP8Xp+qru76i1LsP9lNSkO4P3HUKYoAAAAASUVORK5CYII=" alt="Scanned by pompelmi"></a>
|
|
18
20
|
</p>
|
|
19
21
|
|
|
20
22
|
---
|
|
@@ -345,7 +347,19 @@ choco install clamav -y
|
|
|
345
347
|
|
|
346
348
|
## Examples
|
|
347
349
|
|
|
348
|
-
The [`examples/`](./examples/) directory contains standalone runnable scripts
|
|
350
|
+
The [`examples/`](./examples/) directory contains standalone runnable scripts and framework-specific starters.
|
|
351
|
+
|
|
352
|
+
### Framework starters
|
|
353
|
+
|
|
354
|
+
| Directory | Description |
|
|
355
|
+
|-----------|-------------|
|
|
356
|
+
| [`examples/express/`](./examples/express/) | Full Express app with multer + pompelmi middleware |
|
|
357
|
+
| [`examples/nextjs/`](./examples/nextjs/) | Next.js API route that scans raw upload bytes |
|
|
358
|
+
| [`examples/nestjs/`](./examples/nestjs/) | NestJS guard wrapping pompelmi for route-level protection |
|
|
359
|
+
|
|
360
|
+
### Standalone scripts
|
|
361
|
+
|
|
362
|
+
Each can be run with `node examples/<name>.js`.
|
|
349
363
|
|
|
350
364
|
| File | Description |
|
|
351
365
|
|------|-------------|
|
|
@@ -369,7 +383,7 @@ The [`examples/`](./examples/) directory contains standalone runnable scripts. E
|
|
|
369
383
|
| `scan-zip.js` | ZIP archive scan (ClamAV recurses automatically) |
|
|
370
384
|
| `install-clamav.js` | Programmatic ClamAV installation |
|
|
371
385
|
| `update-virus-database.js` | Programmatic virus DB update |
|
|
372
|
-
| `typescript-usage.ts` | TypeScript example with
|
|
386
|
+
| `typescript-usage.ts` | TypeScript example with full type declarations |
|
|
373
387
|
|
|
374
388
|
---
|
|
375
389
|
|
|
@@ -411,6 +425,7 @@ Scan any repository for viruses on every push or pull request — ClamAV is bund
|
|
|
411
425
|
|-------|-------------|---------|
|
|
412
426
|
| `path` | Directory or file to scan | `.` (full workspace) |
|
|
413
427
|
| `fail-on-virus` | Fail the workflow step when infected files are found | `true` |
|
|
428
|
+
| `comment-on-pr` | Post a PR comment listing infected files (requires `GITHUB_TOKEN`) | `true` |
|
|
414
429
|
|
|
415
430
|
### Outputs
|
|
416
431
|
|
|
@@ -456,6 +471,14 @@ Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before contributing. To r
|
|
|
456
471
|
|
|
457
472
|
---
|
|
458
473
|
|
|
474
|
+
## Coming soon
|
|
475
|
+
|
|
476
|
+
- [ ] AWS S3 integration — scan objects directly from S3 without downloading
|
|
477
|
+
- [ ] Cloudflare Workers support — edge-native scanning via the clamd TCP protocol
|
|
478
|
+
- [ ] NestJS official module — `PompelmiModule.forRoot()` with injectable `PompelmiService`
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
459
482
|
## License
|
|
460
483
|
|
|
461
484
|
[ISC](./LICENSE) — © pompelmi contributors
|
package/action/Dockerfile
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
FROM node:20-slim
|
|
2
2
|
|
|
3
|
-
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
-
|
|
5
3
|
RUN apt-get update && \
|
|
6
|
-
apt-get install -y --no-install-recommends \
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
&& sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf \
|
|
11
|
-
&& mkdir -p /var/lib/clamav /run/clamav
|
|
4
|
+
apt-get install -y --no-install-recommends clamav clamav-freshclam && \
|
|
5
|
+
rm -rf /var/lib/apt/lists/* && \
|
|
6
|
+
sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf && \
|
|
7
|
+
mkdir -p /var/lib/clamav /run/clamav
|
|
12
8
|
|
|
13
9
|
WORKDIR /action
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
COPY
|
|
11
|
+
RUN npm install pompelmi
|
|
12
|
+
|
|
13
|
+
COPY entrypoint.sh ./entrypoint.sh
|
|
14
|
+
COPY scanner.js ./scanner.js
|
|
18
15
|
|
|
19
|
-
# Action scripts
|
|
20
|
-
COPY action/entrypoint.sh ./entrypoint.sh
|
|
21
|
-
COPY action/scanner.js ./scanner.js
|
|
22
16
|
RUN chmod +x ./entrypoint.sh
|
|
23
17
|
|
|
24
18
|
ENTRYPOINT ["/action/entrypoint.sh"]
|
package/action/scanner.js
CHANGED
|
@@ -2,10 +2,136 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { scan, scanDirectory, Verdict } = require('
|
|
5
|
+
const { scan, scanDirectory, Verdict } = require('pompelmi');
|
|
6
6
|
|
|
7
7
|
const scanPath = process.argv[2] || '.';
|
|
8
8
|
const failOnVirus = process.argv[3] !== 'false';
|
|
9
|
+
const commentOnPr = process.argv[4] !== 'false';
|
|
10
|
+
|
|
11
|
+
async function writeReport(clean, malicious, errors, outputDir) {
|
|
12
|
+
const total = clean.length + malicious.length + errors.length;
|
|
13
|
+
const status = malicious.length > 0 ? 'infected' : 'clean';
|
|
14
|
+
|
|
15
|
+
const rows = [
|
|
16
|
+
...clean.map(f => ({ file: f, status: 'clean', verdict: 'Clean' })),
|
|
17
|
+
...malicious.map(f => ({ file: f, status: 'infected', verdict: 'Malicious' })),
|
|
18
|
+
...errors.map(f => ({ file: f, status: 'error', verdict: 'ScanError' })),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const json = JSON.stringify({ status, total, clean: clean.length, malicious: malicious.length, errors: errors.length, files: rows }, null, 2);
|
|
22
|
+
fs.writeFileSync(path.join(outputDir, 'report.json'), json);
|
|
23
|
+
|
|
24
|
+
const tableRows = rows.map(r => {
|
|
25
|
+
const color = r.status === 'clean' ? '#22c55e' : r.status === 'infected' ? '#ef4444' : '#f59e0b';
|
|
26
|
+
return `<tr><td>${escHtml(r.file)}</td><td style="color:${color};font-weight:bold">${r.verdict}</td></tr>`;
|
|
27
|
+
}).join('\n');
|
|
28
|
+
|
|
29
|
+
const html = `<!DOCTYPE html>
|
|
30
|
+
<html lang="en">
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="utf-8">
|
|
33
|
+
<title>Pompelmi Scan Report</title>
|
|
34
|
+
<style>
|
|
35
|
+
body { font-family: sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; }
|
|
36
|
+
h1 { font-size: 1.5rem; }
|
|
37
|
+
.summary { display: flex; gap: 24px; margin: 16px 0; }
|
|
38
|
+
.stat { background: #f1f5f9; border-radius: 8px; padding: 12px 20px; text-align: center; }
|
|
39
|
+
.stat span { display: block; font-size: 1.8rem; font-weight: bold; }
|
|
40
|
+
table { width: 100%; border-collapse: collapse; margin-top: 24px; }
|
|
41
|
+
th { background: #0f172a; color: #fff; text-align: left; padding: 8px 12px; }
|
|
42
|
+
td { padding: 8px 12px; border-bottom: 1px solid #e2e8f0; word-break: break-all; }
|
|
43
|
+
tr:hover td { background: #f8fafc; }
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<h1>${status === 'clean' ? '✅' : '❌'} Pompelmi Scan Report</h1>
|
|
48
|
+
<div class="summary">
|
|
49
|
+
<div class="stat"><span>${total}</span>Total</div>
|
|
50
|
+
<div class="stat"><span style="color:#22c55e">${clean.length}</span>Clean</div>
|
|
51
|
+
<div class="stat"><span style="color:#ef4444">${malicious.length}</span>Infected</div>
|
|
52
|
+
<div class="stat"><span style="color:#f59e0b">${errors.length}</span>Errors</div>
|
|
53
|
+
</div>
|
|
54
|
+
<table>
|
|
55
|
+
<thead><tr><th>File</th><th>Verdict</th></tr></thead>
|
|
56
|
+
<tbody>
|
|
57
|
+
${tableRows}
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</body>
|
|
61
|
+
</html>`;
|
|
62
|
+
fs.writeFileSync(path.join(outputDir, 'report.html'), html);
|
|
63
|
+
|
|
64
|
+
return { jsonPath: path.join(outputDir, 'report.json'), htmlPath: path.join(outputDir, 'report.html') };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function escHtml(s) {
|
|
68
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function postPrComment(malicious) {
|
|
72
|
+
if (!commentOnPr) return;
|
|
73
|
+
|
|
74
|
+
const token = process.env.GITHUB_TOKEN;
|
|
75
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
76
|
+
if (!token || eventName !== 'pull_request') return;
|
|
77
|
+
|
|
78
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
79
|
+
if (!eventPath || !fs.existsSync(eventPath)) return;
|
|
80
|
+
|
|
81
|
+
let event;
|
|
82
|
+
try { event = JSON.parse(fs.readFileSync(eventPath, 'utf8')); }
|
|
83
|
+
catch { return; }
|
|
84
|
+
|
|
85
|
+
const prNumber = event.pull_request && event.pull_request.number;
|
|
86
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
87
|
+
if (!prNumber || !repo) return;
|
|
88
|
+
|
|
89
|
+
const rows = malicious
|
|
90
|
+
.map(f => `| \`${escHtml(f)}\` | Malicious |`)
|
|
91
|
+
.join('\n');
|
|
92
|
+
const body =
|
|
93
|
+
`## ❌ Pompelmi: Virus Detected\n\n` +
|
|
94
|
+
`The following infected file(s) were found during the scan:\n\n` +
|
|
95
|
+
`| File | Verdict |\n|------|--------|\n${rows}\n\n` +
|
|
96
|
+
`> Scanned by [pompelmi](https://pompelmi.app)`;
|
|
97
|
+
|
|
98
|
+
const https = require('https');
|
|
99
|
+
const payload = JSON.stringify({ body });
|
|
100
|
+
|
|
101
|
+
await new Promise((resolve, reject) => {
|
|
102
|
+
const req = https.request({
|
|
103
|
+
hostname: 'api.github.com',
|
|
104
|
+
path: `/repos/${repo}/issues/${prNumber}/comments`,
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Authorization': `Bearer ${token}`,
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
110
|
+
'User-Agent': 'pompelmi-action',
|
|
111
|
+
'Accept': 'application/vnd.github+json',
|
|
112
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
113
|
+
},
|
|
114
|
+
}, (res) => {
|
|
115
|
+
res.resume();
|
|
116
|
+
if (res.statusCode >= 200 && res.statusCode < 300) resolve();
|
|
117
|
+
else reject(new Error(`GitHub API returned HTTP ${res.statusCode}`));
|
|
118
|
+
});
|
|
119
|
+
req.on('error', reject);
|
|
120
|
+
req.write(payload);
|
|
121
|
+
req.end();
|
|
122
|
+
}).catch(err => console.warn(`Could not post PR comment: ${err.message}`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function uploadArtifact(jsonPath, htmlPath) {
|
|
126
|
+
try {
|
|
127
|
+
const { DefaultArtifactClient } = require('@actions/artifact');
|
|
128
|
+
const client = new DefaultArtifactClient();
|
|
129
|
+
await client.uploadArtifact('pompelmi-scan-report', [jsonPath, htmlPath], path.dirname(jsonPath));
|
|
130
|
+
console.log('Scan report artifact uploaded: pompelmi-scan-report');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.warn(`Could not upload artifact (not running in Actions?): ${err.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
9
135
|
|
|
10
136
|
async function main() {
|
|
11
137
|
const resolved = path.resolve(scanPath);
|
|
@@ -66,6 +192,11 @@ async function main() {
|
|
|
66
192
|
fs.appendFileSync(summaryFile, rows.join('\n') + '\n');
|
|
67
193
|
}
|
|
68
194
|
|
|
195
|
+
// --- Report artifact ---
|
|
196
|
+
const reportDir = process.env.RUNNER_TEMP || '/tmp';
|
|
197
|
+
const { jsonPath, htmlPath } = await writeReport(clean, malicious, errors, reportDir);
|
|
198
|
+
await uploadArtifact(jsonPath, htmlPath);
|
|
199
|
+
|
|
69
200
|
// --- Console ---
|
|
70
201
|
console.log(`\nScan complete — ${total} file(s) scanned`);
|
|
71
202
|
console.log(` Clean: ${clean.length}`);
|
|
@@ -76,6 +207,7 @@ async function main() {
|
|
|
76
207
|
if (malicious.length > 0) {
|
|
77
208
|
console.error('\nInfected files:');
|
|
78
209
|
malicious.forEach(f => console.error(` ${f}`));
|
|
210
|
+
await postPrComment(malicious);
|
|
79
211
|
if (failOnVirus) {
|
|
80
212
|
console.error('\n::error::Virus(es) detected — failing workflow.');
|
|
81
213
|
process.exit(1);
|
package/action.yml
CHANGED
|
@@ -14,6 +14,10 @@ inputs:
|
|
|
14
14
|
description: 'Fail the workflow step when infected files are found'
|
|
15
15
|
required: false
|
|
16
16
|
default: 'true'
|
|
17
|
+
comment-on-pr:
|
|
18
|
+
description: 'Post a PR comment listing infected files when a virus is found (requires GITHUB_TOKEN)'
|
|
19
|
+
required: false
|
|
20
|
+
default: 'true'
|
|
17
21
|
|
|
18
22
|
outputs:
|
|
19
23
|
infected-files:
|
|
@@ -27,3 +31,4 @@ runs:
|
|
|
27
31
|
args:
|
|
28
32
|
- ${{ inputs.path }}
|
|
29
33
|
- ${{ inputs.fail-on-virus }}
|
|
34
|
+
- ${{ inputs.comment-on-pr }}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "ClamAV for humans — scan any file and get back Clean, Malicious, or ScanError. No daemons. No cloud. No native bindings.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "pompelmi contributors",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"type": "commonjs",
|
|
34
34
|
"main": "./src/index.js",
|
|
35
|
+
"types": "./types/index.d.ts",
|
|
35
36
|
"scripts": {
|
|
36
37
|
"test": "node --test test/unit.test.js && node test/scan.test.js",
|
|
37
38
|
"lint": "eslint src/"
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { scan, scanBuffer, scanStream, scanDirectory } = require('./ClamAVScanner.js');
|
|
2
2
|
const { Verdict } = require('./verdicts.js');
|
|
3
|
+
const { middleware } = require('./middleware.js');
|
|
3
4
|
|
|
4
|
-
module.exports = { scan, scanBuffer, scanStream, scanDirectory, Verdict };
|
|
5
|
+
module.exports = { scan, scanBuffer, scanStream, scanDirectory, Verdict, middleware };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { scanBuffer, Verdict } = require('./ClamAVScanner.js');
|
|
4
|
+
|
|
5
|
+
function middleware(opts = {}) {
|
|
6
|
+
const field = opts.uploadField || 'file';
|
|
7
|
+
|
|
8
|
+
return async function pompelmiMiddleware(req, res, next) {
|
|
9
|
+
let buffers = [];
|
|
10
|
+
|
|
11
|
+
if (req.file) {
|
|
12
|
+
buffers = [req.file.buffer];
|
|
13
|
+
} else if (req.files) {
|
|
14
|
+
const files = Array.isArray(req.files)
|
|
15
|
+
? req.files
|
|
16
|
+
: (req.files[field] || Object.values(req.files).flat());
|
|
17
|
+
buffers = files.map(f => f.buffer).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (buffers.length === 0) return next();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
for (const buf of buffers) {
|
|
24
|
+
const verdict = await scanBuffer(buf, opts);
|
|
25
|
+
if (verdict === Verdict.Malicious) {
|
|
26
|
+
return res.status(403).json({ error: 'Malicious file detected' });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
next();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
next(err);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { middleware };
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
3
|
+
|
|
4
|
+
/** Options passed to any scan function */
|
|
5
|
+
export interface ScanOptions {
|
|
6
|
+
/** clamd hostname — enables TCP mode when set */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** clamd port (default: 3310) */
|
|
9
|
+
port?: number;
|
|
10
|
+
/** Path to a clamd UNIX domain socket (e.g. /run/clamav/clamd.sock) */
|
|
11
|
+
socket?: string;
|
|
12
|
+
/** Socket idle timeout in milliseconds, clamd mode only (default: 15000) */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Options for the Express/Fastify middleware */
|
|
17
|
+
export interface MiddlewareOptions extends ScanOptions {
|
|
18
|
+
/** multer field name to look for uploaded files (default: 'file') */
|
|
19
|
+
uploadField?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Result returned by scanDirectory */
|
|
23
|
+
export interface DirectoryScanResult {
|
|
24
|
+
/** Absolute paths of files that scanned clean */
|
|
25
|
+
clean: string[];
|
|
26
|
+
/** Absolute paths of infected files */
|
|
27
|
+
malicious: string[];
|
|
28
|
+
/** Absolute paths of files that produced a scan error */
|
|
29
|
+
errors: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Opaque Symbol-based scan verdicts */
|
|
33
|
+
export declare const Verdict: {
|
|
34
|
+
readonly Clean: unique symbol;
|
|
35
|
+
readonly Malicious: unique symbol;
|
|
36
|
+
readonly ScanError: unique symbol;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** The type of any Verdict symbol */
|
|
40
|
+
export type VerdictValue = typeof Verdict[keyof typeof Verdict];
|
|
41
|
+
|
|
42
|
+
type NextFunction = (err?: unknown) => void;
|
|
43
|
+
type RequestHandler = (
|
|
44
|
+
req: IncomingMessage,
|
|
45
|
+
res: ServerResponse,
|
|
46
|
+
next: NextFunction
|
|
47
|
+
) => void | Promise<void>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Scan a file at the given path.
|
|
51
|
+
* Resolves to Verdict.Clean, Verdict.Malicious, or Verdict.ScanError.
|
|
52
|
+
* Rejects if the file is not found or clamscan is unavailable.
|
|
53
|
+
*/
|
|
54
|
+
export declare function scan(filePath: string, options?: ScanOptions): Promise<VerdictValue>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scan an in-memory Buffer.
|
|
58
|
+
* In TCP/socket mode the buffer is streamed to clamd with no disk I/O.
|
|
59
|
+
*/
|
|
60
|
+
export declare function scanBuffer(buffer: Buffer, options?: ScanOptions): Promise<VerdictValue>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Scan a Node.js Readable stream.
|
|
64
|
+
* In TCP/socket mode the stream is piped to clamd with no disk I/O.
|
|
65
|
+
*/
|
|
66
|
+
export declare function scanStream(stream: Readable, options?: ScanOptions): Promise<VerdictValue>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Recursively scan every file under dirPath.
|
|
70
|
+
* Per-file errors are caught and collected without aborting the full scan.
|
|
71
|
+
*/
|
|
72
|
+
export declare function scanDirectory(
|
|
73
|
+
dirPath: string,
|
|
74
|
+
options?: ScanOptions
|
|
75
|
+
): Promise<DirectoryScanResult>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Express / Fastify middleware that scans multer-uploaded files
|
|
79
|
+
* (req.file / req.files) and responds HTTP 403 on any infection.
|
|
80
|
+
* Call after multer, before your route handler.
|
|
81
|
+
*/
|
|
82
|
+
export declare function middleware(options?: MiddlewareOptions): RequestHandler;
|
package/pr_info.tmp
DELETED