Inspiration

Hiring runs on one assumption. The person who applies is who they say they are, and the company that reaches out is who it says it is. Generative AI broke both halves of that assumption at the same time.

The candidate side is the side almost nobody defends. A scammer registers a look-alike domain, writes a recruiter email that reads better than a real one, and sends a fake offer letter. The candidate loses money, or a Social Security number, or both. The company that was impersonated never finds out it happened.

The advice we give job seekers is to look closely and use judgment. Against a model that writes clean corporate English for free, that advice is worthless. A candidate needs an answer, not a feeling. fakeblock gives the answer.

What it does

fakeblock checks the two things a scammer must fake: the message, and the job.

The email scanner

The email scanner is an add-in inside Outlook. The candidate opens a recruiting email and clicks Scan. A line sweeps down a copy of the message. Suspicious text lights up red. Text we can positively confirm lights up green. The scan ends on one of three verdicts: Legitimate, Suspicious, or Likely Scam.

Every highlight carries its reason. A sender address on gmail.com that claims to be Tesla is a hard flag and reaches Likely Scam on its own. Misspellings, pressure language, and an offer that arrives before any interview are soft flags and reach Suspicious. A sender domain that matches the company it claims is the green highlight.

The job posting verifier

Everyone else looks at a suspect job posting and asks whether it seems fake. That is guesswork, and it gets harder every month.

A company already publishes its real open roles, and that list is public. So we do not guess. We check the list. The left column holds every posting on the internet that claims to be from Coinbase. The right column holds the roles Coinbase actually has open. Postings that match a real req go green. Postings with no matching req turn red and open a case file.

Everyone else holds the twenty dollar bill up to the light. We call the bank and ask whether the serial number exists.

How we built it

The email scanner has three parts.

The add-in runs on Office JS inside Outlook. It reads the sender name and address, the subject, and the body of the open message, then posts them to our backend.

The backend is Node and TypeScript on Express. Rules run first, because rules are deterministic and cheap. The backend resolves which company the email claims to represent, then compares that company's real domains against the sender address. It spellchecks the body with nspell and dictionary-en, behind an allowlist that protects proper nouns, product names, and industry words.

After the rules, one call goes to a language model for tone and plausibility. The model returns JSON: a quoted span and a reason for each finding. We locate each quote in the original text and convert it into a flag with character offsets. The model runs on our own hardware, an NVIDIA Jetson AGX Thor devkit running llama3:70b under Ollama. Email content never goes to a third-party API.

The task pane renders its own copy of the message and uses those offsets to place the highlights, because Outlook does not let an add-in write into the reading pane.

The verifier is Next.js App Router with TypeScript and Tailwind. It reads a company's live roles through adapters for three job boards: Greenhouse, Lever, and Ashby. The matcher normalizes each title, which strips seniority words and stop words, then scores the pair with bigram Dice similarity. Above 0.85 is verified. Above 0.55 is unverified. Below that, no such req exists. Every company's role list is cached, so a board outage does not take the product down.

Challenges we ran into

A false positive costs far more than a false negative. If we flag a real offer, a candidate walks away from a job. So the spellchecker needed guards. Our first calibration run flagged "observability", "iCIMS", and the company name "localhost:nyc". We built a calibration harness that runs every email in our labelled set through the real scanner and checks the verdict against the label. It now passes 30 of 30, across 13 legitimate emails and 17 scams.

Job titles do not match cleanly. Aggregator sites reword the title of a real role, so a strict matcher calls a genuine posting fake. We made the matcher conservative on purpose. A verdict of no such req requires that nothing in the company's list is even loosely similar. Against 217 live Coinbase roles, the self test mis-verifies none of them, and scrambled locations produce no false negatives. A location difference shows as a note and never changes the verdict.

Mail servers block the obvious approach. Outlook blocks IMAP at the mailbox level on many accounts, so any scanner that reads mail from the server side fails before it starts. We moved the read into the client. The add-in reads the message that is already open, so it never needs mailbox credentials at all.

A 70B model is slow. A cold call takes about 22 seconds, and nobody waits that long for a verdict. We keep the model resident, warm it up at start, and cap every call with a timeout. If the model is unreachable, the rules engine returns the verdict on its own in milliseconds and the pane reports that the AI check is unavailable. The scan never hangs.

Accomplishments that we're proud of

We are proud of four things:

  1. The scanner degrades safely. A domain mismatch reaches Likely Scam from the rules alone, so a model outage costs detail and never costs safety.
  2. The numbers hold. 30 of 30 on the labelled email set. 0 of 217 real Coinbase roles mis-verified.
  3. The model is ours. Email text stays on hardware we control, which matters when the text is a person's offer letter.
  4. It fits where people already are. The scanner lives in Outlook, and the verifier reads the job boards companies already publish to. Neither product asks anyone to move.

What we learned

Certainty beats probability. A company publishes its real job list, so checking that list is a lookup and not a judgment call. Wherever a fact is already published, checking it beats scoring it.

A verdict without an explanation does not change behavior. The red banner is not what makes a candidate stop. The four highlighted words, and the sentence sitting next to them, are.

Rules and models do different jobs. Rules decide the facts: does the domain match, is the word real, does the req exist. The model reads what a rule cannot write down: urgency, flattery, and an offer that arrives before an interview. Merging both into one flag list, on one severity scale, is what makes the result readable in five seconds.

What's next for fakeblock

  1. Verify the transport, not only the content. SPF, DKIM, and DMARC checks let the scanner read the envelope as well as the words.
  2. Catch look-alike domains on sight. A live domain registry replaces the fixed company list, and new domains get scored for typosquatting against known employers.
  3. Give employers a way to sign. A company that publishes a verification endpoint lets any offer letter be checked back to the source.
  4. Close the loop with the impersonated company. Today the company never learns its name is being used. A confirmed red posting must become a report it can act on.
  5. Go where the rest of the candidates are. Gmail next, then a browser extension that verifies a posting on the job board itself.

Built With

Share this project:

Updates

Submission history