Skip to content

Commit 48966b6

Browse files
committed
first commit
0 parents  commit 48966b6

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

.github/workflows/scan.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: "Scan Release Artifacts and Update README"
2+
3+
on:
4+
schedule:
5+
- cron: '0 2 * * *'
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
virus-scan:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repo
17+
uses: actions/checkout@v3
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version: '3.x'
23+
24+
- name: Install dependencies
25+
run: |
26+
pip install --upgrade pip
27+
pip install requests PyGithub vt-py
28+
29+
- name: Scan & update README
30+
env:
31+
VT_API_KEY: ${{ secrets.VT_API_KEY }}
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
EXCLUDE_NAMES: 'LICENSE'
34+
EXCLUDE_PREFIXES: '_'
35+
PYTHONUNBUFFERED: '1' # ensure immediate output flushing
36+
REPOSITORY_TO_SCAN: 'simplex-chat/simplex-chat'
37+
run: python scripts/scan_and_update.py

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# simplex-virutstotal-scan

scripts/scan_and_update.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import os
2+
import sys
3+
import requests
4+
import vt
5+
import hashlib
6+
from github import Github
7+
8+
# Download release assets, compute SHA‑256, reuse existing VT report when present,
9+
# otherwise upload (wait_for_completion=True), then link VT GUI URL in README.
10+
11+
12+
def main():
13+
# Common env variables
14+
# --------------------
15+
16+
# VirusTotal API key. 500 requests per day, 15.5K requests per month.
17+
vt_key = os.getenv("VT_API_KEY")
18+
19+
# Github token. Automatically set in Github Action.
20+
gh_token = os.getenv("GITHUB_TOKEN")
21+
22+
# Repository to scan. Should be in format "simplex-chat/simplex-chat".
23+
repo_name = os.getenv("REPOSITORY_TO_SCAN")
24+
25+
# Repository to commit changes.
26+
update_repo_name = os.getenv("GITHUB_REPOSITORY")
27+
28+
# Exclude patterns and names. Can be comma seperated.
29+
exclude_names = {n.strip() for n in os.getenv("EXCLUDE_NAMES", "").split(',') if n.strip()}
30+
prefixes = [p for p in os.getenv("EXCLUDE_PREFIXES", "").split(',') if p]
31+
32+
# Check if variables exist early. If not - bail out.
33+
if not vt_key or not gh_token or not repo_name or not update_repo_name:
34+
print("ERROR: Missing VT_API_KEY, GITHUB_TOKEN, or REPOSITORY_TO_SCAN")
35+
sys.exit(1)
36+
37+
# Clients setup
38+
# -------------
39+
40+
vt_client = vt.Client(vt_key)
41+
gh = Github(gh_token)
42+
repo = gh.get_repo(update_repo_name)
43+
44+
# Fetch only latest release
45+
release = requests.get(
46+
f"https://api.github.com/repos/{repo_name}/releases/latest",
47+
headers={"Authorization": f"token {gh_token}", "Accept": "application/vnd.github.v3+json"},
48+
timeout=30,
49+
).json()
50+
51+
# Get release name
52+
release_name = release.get("name") or release.get("tag_name") or "latest"
53+
54+
# Init tuple for final table.
55+
# Format: markdown_linl, mal, sys, und
56+
results = []
57+
58+
# Now, let's prcoess each release artifact
59+
# For every artifact
60+
for asset in release.get("assets", []):
61+
# Get it's name
62+
name = asset.get("name") or ""
63+
# Safety check
64+
if not name:
65+
continue
66+
# And exclude based on predefined patterns
67+
if name in exclude_names or any(name.startswith(p) for p in prefixes):
68+
print(f"Skipping excluded asset: {name}")
69+
continue
70+
71+
# Now, get URL
72+
url = asset.get("browser_download_url")
73+
# Safety check
74+
if not url:
75+
continue
76+
77+
# Create "assets" directory
78+
os.makedirs("assets", exist_ok=True)
79+
80+
print(f"Processing {name} …")
81+
82+
# Download and compute SHA-256
83+
try:
84+
with requests.get(url, stream=True, timeout=60) as resp:
85+
resp.raise_for_status()
86+
path = os.path.join("assets", name)
87+
with open(path, "wb") as out_file:
88+
sha = hashlib.sha256()
89+
for chunk in resp.iter_content(8192):
90+
out_file.write(chunk)
91+
sha.update(chunk)
92+
file_path = path
93+
sha256 = sha.hexdigest()
94+
except Exception as e:
95+
print(f"Download error for {name}: {e}")
96+
continue
97+
98+
# VirusTotal link in final README
99+
vt_url = f"https://www.virustotal.com/gui/file/{sha256}"
100+
markdown_link = f"[{name}]({vt_url})"
101+
102+
# Check VirusTotal cache
103+
try:
104+
file_obj = vt_client.get_object(f"/files/{sha256}")
105+
# Cache hit, we can proceed
106+
stats = file_obj.last_analysis_stats
107+
print(f"VT cache hit for {name} (SHA‑256: {sha256}) → using existing report.")
108+
# No cache hit, we should upload file to VirusTotal
109+
except vt.error.APIError as e:
110+
if getattr(e, "code", None) == "NotFoundError" or getattr(e, "status_code", 0) == 404:
111+
print(f"Uploading {name} to VirusTotal …")
112+
try:
113+
with open(file_path, "rb") as f:
114+
analysis = vt_client.scan_file(f, wait_for_completion=True)
115+
except Exception as up_err:
116+
print(f"Upload failed for {name}: {up_err}")
117+
os.remove(file_path)
118+
continue
119+
# After completion, pull file object via hash
120+
try:
121+
file_obj = vt_client.get_object(f"/files/{sha256}")
122+
stats = file_obj.last_analysis_stats
123+
except Exception as fetch_err:
124+
print(f"Failed to fetch stats for {name}: {fetch_err}")
125+
os.remove(file_path)
126+
continue
127+
else:
128+
print(f"Unexpected VT error for {name}: {e}")
129+
os.remove(file_path)
130+
continue
131+
finally:
132+
os.remove(file_path)
133+
134+
# Populate tuple with results
135+
results.append((markdown_link,
136+
stats.get("malicious", 0),
137+
stats.get("suspicious", 0),
138+
stats.get("undetected", 0)))
139+
140+
# Don't forget to close the client
141+
vt_client.close()
142+
143+
# Just a safety check
144+
if not results:
145+
print("No scan results to update.")
146+
return
147+
148+
# Let's build README
149+
lines = [
150+
f"# VirusTotal Scan Results for Release: {release_name}",
151+
"",
152+
"| File | Malicious | Suspicious | Undetected |",
153+
"| --- | --- | --- | --- |",
154+
]
155+
# The actial data
156+
for link, mal, sus, und in results:
157+
lines.append(f"| {link} | {mal} | {sus} | {und} |")
158+
content = '\n'.join(lines)
159+
160+
# And commit everything to our READM
161+
try:
162+
readme = repo.get_contents("README.md")
163+
repo.update_file(
164+
readme.path,
165+
"chore: update README with VT scan results",
166+
content,
167+
readme.sha,
168+
)
169+
print("README.md successfully updated.")
170+
except Exception as e:
171+
print(f"Failed to update README.md: {e}")
172+
173+
if __name__ == "__main__":
174+
main()

0 commit comments

Comments
 (0)