This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
What I Built
When I read "build for a friend", my first thought was that none of my friends had a problem I could fix in a weekend. Then I thought about what most of my batch is doing right now. We're all applying for our first jobs. And if you're a fresher looking for a job, you're exactly who fake job offers are aimed at.
They look something like this. I wrote this one from public fraud advisories, so it isn't a real message, but every line in it is something those advisories warn about:
From: infosys.recruitment.cell@gmail.com
Subject: Offer Letter - Infosys LtdDear Candidate, congratulations! You are selected for System Engineer at Infosys, Pune.
Your interview will be on Telegram, message our HR @infosys_hr_desk.
Pay a refundable registration fee of Rs 1,999 within 2 hours to confirm your seat.
If you've seen a few of these, you'd laugh at it. But if it's the first offer you've ever got, it says Infosys, it says congratulations, and it wants an answer in two hours. ₹1,999 to lock in a job at Infosys doesn't sound like much.
So I built OfferProof. You paste the message in and it tells you which parts look like a scam, quotes the exact words it's worried about, and gives you a question to send back. Something like "can you email me from your official Infosys address?" That's an easy question for a real recruiter and a hard one for a scammer.
It runs on your own laptop with Gemma 3 4B through Ollama. Offer letters have your phone number, address and PAN on them, and I didn't want anyone pasting that into some website.
Demo
The first message is the one above: a Gmail address pretending to be Infosys, an interview on Telegram, and a "refundable" fee due in two hours. It flags all three, and the two-hour deadline too.
The second is a normal Infosys email that happens to say "Infosys never charges a registration fee". A plain keyword search would flag it for the word "fee". OfferProof doesn't. It also says Not verified, not Genuine. No message can prove an offer is real, so it never claims that.
(Gemma takes 15 to 30 seconds per message on my GTX 1650. I cut that wait out of the GIFs.)
Code
Surge77
/
offerproof
Check a job offer for scam signs, locally, with an open-weight model.
OfferProof
Check a job offer for scam signs before you reply, pay, or share documents. It runs on your own computer with an open-weight model, so your offer letter, phone number and PAN never leave it.
Fake job offers mostly target people with little or no work experience. Most of them repeat a few tells a fee to "confirm your seat", an interview held over Telegram, a Gmail address claiming to be a large company. OfferProof looks for those tells and shows you the exact words that triggered each one.
$ offerproof check offer.txt
LIKELY SCAM: This message has clear signs of a job scam.
[severe] Personal email claiming to be Infosys
"infosys.recruitment.cell@gmail.com"
Large employers recruit from their own company domain, never from Gmail, Yahoo or Outlook.
Ask them: Can you write to me from your official Infosys email address?
[severe] Asks you to pay money
"Pay a refundable registration…How I Built It
I didn't want the model deciding "scam or not". A 4B model will give you a confident answer either way, and you can't tell why it said it. So Gemma only reads the message. Plain code makes the call.
There are four parts:
- Pattern rules in
signals.yamlfor known scam wording. They skip negated sentences, which is why the "never charges a fee" email comes through clean. - Domain checks in
companies.yaml. If a message claims to be from TCS but comes from a Gmail address, or from something liketcs-careers.in, that gets flagged. - Gemma answers a few narrow questions. Does it ask for money? An OTP? Documents? How is the interview being done? Every answer has to come with a quote from the message, and if that quote isn't actually in the message, the answer gets thrown away.
- A few lines of Python decide. Any severe sign means likely scam. Anything else is not verified.
Where it went wrong
I wrote 20 test messages, 12 scams and 8 genuine ones, and ran everything locally.
The first run caught all 12 scams. Looked great, until I noticed it had also flagged 3 of the 8 genuine messages.
That confused me, because I was already checking that Gemma's quotes existed in the message. They did. The problem was what it used them for. It called a "quick call" a chat-only interview. It called a rejection email a chat-only interview too. And it decided a HackerRank assessment was a task scam. Every quote was real. None of them had anything to do with the claim.
So a quote being in the message isn't enough, it has to be about the thing being claimed. Each signal now has a list of words its quote must contain. If Gemma says "interview only over chat", the quote has to mention Telegram, WhatsApp or chat:
def quote_supports(signal_id: str, quote: str) -> bool:
"""The quote must be about the claim, not just appear somewhere in the message."""
evidence = load_signals()[signal_id].evidence
return evidence is None or evidence.search(quote) is not None
I also spelled out in the prompt the things it kept getting wrong. Phone and video calls aren't chat-only. Coding tests aren't task scams. If a message doesn't say how the interview happens, the answer is "not mentioned".
After that:
| Layer | Scams caught (of 12) | Genuine wrongly flagged (of 8) |
|---|---|---|
| Rules only | 9 | 0 |
| Model only | 9 | 0 |
| Combined | 11 | 0 |
The one scam it still misses says the "training material costs 999". I could add a rule for it, but it's one message I wrote myself, and tuning rules until my own test set passes wouldn't prove much.
That's the real limitation right now: all 20 messages are mine. Next I want my friends to run it on messages they've actually received, and add those to the test set with names and numbers removed.
Why Does Open Innovation Matter?
For this project it's pretty practical.
I'd only trust a tool with my offer letter if the letter never leaves my laptop. That works because Gemma is open and small enough to run on my GTX 1650.
Scam scripts also change faster than one person can keep up with. The patterns are plain YAML, so if you've seen a scam OfferProof misses, adding it is a couple of lines and a test. CONTRIBUTING.md has the steps.
And you don't have to take my word for the numbers. The test messages and the script are in the repo. Run them yourself, or point OFFERPROOF_MODEL at a different model and see how it does.
Prize Categories
Best Use of Gemma. Gemma 3 4B does all the reading, locally through Ollama.
If someone has sent you one of these and you're in India: don't pay, don't share OTPs or documents, and report it at cybercrime.gov.in or call 1930.


Top comments (0)