-
Notifications
You must be signed in to change notification settings - Fork 0
Security Guardrails
Passive safety nets in this repo. Everything on this page is automated so the security posture doesn't depend on any human noticing an event in time.
What it does. Every issues.opened, issue_comment.created, and pull_request_review_comment.created event fires a workflow that:
- Extracts every GitHub user-attachment URL from the body.
- HEADs each URL and downloads (up to 16 MB) to hash it.
- Sends the SHA256 to VirusTotal's
/api/v3/files/{sha256}endpoint. Hash-only, no upload of content. - Flags any attachment where:
- VirusTotal has one or more malicious engine hits, OR
- The file starts with a Windows PE header (
MZ) regardless of extension, OR - The extension is in the known-executable set (
.exe,.msi,.scr,.ps1,.jar,.apk,.dmg,.sh, ...).
- On a flag:
- Hides the offending comment via GraphQL
minimizeComment(classifier:ABUSE). - Applies the
security-reviewlabel to the issue. - Posts a summary on the issue pinging the repo owner with SHA256s and reason strings.
- Hides the offending comment via GraphQL
The repo owner's own attachments are exempt (github.actor != github.repository_owner) so the workflow can't lock you out of your own repo.
What it does NOT do.
- Never uploads issue content anywhere. VirusTotal receives only a hash.
- Never executes the file. Analysis is limited to header sniffing + hash lookup.
- Doesn't touch existing accepted attachments; only fires on new / edited posts.
- Doesn't block first-time contributors on legitimate content (screenshots, logs, plain-text traces, PDFs, archives -- all left alone).
Files.
-
.github/workflows/scan-issue-attachments.yml-- trigger + permissions. -
.github/scripts/scan-attachments.mjs-- runtime logic (HTTP, GraphQL, decisions). -
.github/scripts/scan-attachments-lib.mjs-- pure helpers, unit-tested. -
tests/scanAttachments.test.js-- pins URL extraction, extension policy, VirusTotal response parsing, and the workflow YAML contract.
Secret required.
-
VT_API_KEY-- VirusTotal API key. Free tier gives 500 hash lookups per day, which is far above the traffic this project sees. See Secrets and Environment for how to set it and where to get one.
If VT_API_KEY is unset, the workflow degrades gracefully: it still applies the extension-only policy (blocking bare .exe etc. from non-owners), but skips VirusTotal lookups.
Signature of an incident. The wapebacoko submission on issue #226 is the reference example:
- 2.9 MB
metadata_fix_v2.exe. - Static analysis showed a Windows credential stealer (Go-compiled,
LogonUserW,AdjustTokenPrivileges,NetUserAdd,DuplicateTokenEximports, obfuscated main package name, zeroed compile timestamp). - SHA256:
d85d164e46fabb085609f2586e8fec364539a6ec81f74659f0cb28ac76e7880b.
The scanner is designed to catch that exact class of upload: extension-only alone would flag it, and VirusTotal has multiple engines that recognize the payload.
False positives. If the scanner hides a legitimate attachment:
- Re-open the hidden comment via the GitHub comment
...menu. - Remove the
security-reviewlabel from the issue. - If the attachment was a legitimate non-owner upload of an executable, add a note to the issue explaining why it was expected.
- Consider adding the URL host or an extension exemption in
scan-attachments-lib.mjsif the same false positive pattern shows up more than once.
The hide is always reversible; there is no automated ban or ownership removal.
Related.
- Memory rule "never extract/execute untrusted GH issue attachments" (session policy for me).
- Issue #225 (resilience -- same "don't rely on humans catching every issue" theme).
-
Secrets-and-Environment -- where
VT_API_KEYlives.