Skip to content
yalt8826.com
← projects/
projects/phishing-detector/hackathonsecurityteam

Phishing Detector

Pull the newest email from a Gmail inbox and check it four ways: who sent it, where its links go, what its images are, and what those links look like when they're opened.

24-hour hackathon at SJBIT · team Axiom, four of us · Nov 2025

01 / overview

Is this email trying to trick me?

Most phishing emails give themselves away somewhere: an odd sender domain, a link to a page that only pretends to be your bank, an image that's actually malware. One check rarely catches all of it, so this project runs several and puts the answers side by side.

We built it in 24 hours at a hackathon at SJBIT, as team Axiom, four of us. I built the detection pipeline and the Docker worker that opens links in a sandbox.

24 hfirst commit to final commit
4independent checks on every email
1throwaway container per check, so suspicious links never open on the host
0write access to the inbox: Gmail's read-only scope only
02 / screens

The dashboard.

01The web dashboard the team demoed
03 / email to verdict

One email, four opinions.

The pipeline signs in to Gmail with OAuth, pulls the newest message in the inbox, and reduces it to plain JSON: sender, subject, date, body, inline image URLs and attachments. Every check reads that same JSON file.

fetchGmail API · read-only
newest inbox messagewalks nested MIME partssaves image attachmentsemail → JSON
senderGemini 2.5 Flash
sees only from + subjectstrict JSON replyphishing or not, and why: sender or subject
linksVirusTotal
regex out every URLsubmit, wait for the scanmalicious · suspicious · safe
imagesDocker worker
download each inline imageSHA-256VirusTotal file lookupdelete the file
visualDocker worker
headless Chromium opens each linkfull-page screenshotlogin-form keywordsbrand-logo images
04 / opening links safely

The dangerous part is clicking the link.

Reputation lookups only work for links someone has already reported. To judge a brand-new link you have to load it, and loading an attacker's page on your own machine is exactly what phishing wants. So the visual and image checks run in a separate Docker image, threat_worker, and nothing they touch stays on the host.

throwawayA fresh container each time

Every check is a docker run --rm. Whatever a page drops into the browser is gone when the container exits.

least accessOnly three folders mounted

The container can read the email JSON and write to screenshots and results. It can't see the rest of the machine.

no rootUnprivileged user

Chromium runs as a normal user (uid 1001) inside a slim Python image, never as root.

05 / my part

Who built what.

Most of the commits in the repo are mine. I wrote the end-to-end pipeline, the VirusTotal link and file checks, image extraction in the mail reader, image hashing, the visual check and its Docker worker. My teammates wrote the first mail extractor and VirusTotal link checker, and built the Flask API and the web dashboard.

06 / what didn't make it

Honest notes from hour 24.

at demo time

In the final commit, the web dashboard's API skips VirusTotal (“quota exceeded”) and the Docker checks (“Docker not available”). So the dashboard showed only the sender check, while the link, image and visual checks ran from the command-line pipeline.

The visual verdict is also blunt. It flags any page containing “password”, “login” or “sign in”, which describes every real login page too. Given more time, I'd:

headersRead SPF, DKIM and DMARC

Authentication results say far more about a sender than the display name and subject line do.

visualCompare against the real site

Hash the screenshot and compare it with the genuine brand's page. A close match on the wrong domain is the real signal.

domainsDomain age and lookalikes

A domain registered last week, or one letter off from a bank's, is worth more than a keyword match.