Blog

Cutting the Detection Engine Loose

← Back to Blog

For a while now, everything behind /phish-report has lived in one place: this repo, under lib/, doing its job for exactly one web form. That's fine for a hosted tool, but it always bothered me a little that the actual detection logic, the typosquat and homograph checks, the SPF/DKIM/DMARC-aware header analysis, the Received-chain spoofing checks, the IOC extraction and MITRE mapping and Sigma generation, was only reachable by pasting an email into a text box on my own site. So I pulled it out.

Same logic, its own package

@farksecurity/phish-signals is on npm now, and the source is open on GitHub under MIT at github.com/MasonC-402/phish-signals. Feed it a URL, a headers Map, or a raw .eml or .msg file and you get back the same Signal-based findings /phish-report shows on screen, just as plain data instead of a rendered report. Same QR decoding, same ZIP central-directory listing, same everything underneath. It's not a rewrite or a trimmed-down version, it's the actual engine.

The part worth being honest about is that the site still runs its own copy of this same logic. I didn't rip the engine out of the site and leave a hole behind, /phish-report works exactly as it did before. What changed is that the engine now also exists as its own independently versioned thing, so the site's copy and the published package can drift a version apart from each other without that being a bug. If you're depending on the package directly, pin a version. Don't assume it tracks whatever's live on the site at any given moment.

Building a pipeline instead of a copy-paste

The boring way to do this would have been to copy the relevant files into a new repo by hand and update them whenever I felt like it. I didn't want that, mostly because I know exactly how that story ends: the copy goes stale, I forget it exists, and eventually it's just an artifact of a repo quietly lying about itself.

So the actual source of truth still lives inside this site's own private monorepo, in a workspace at packages/phish-signals/, right alongside everything else. A GitHub Action watches that one directory and mirrors it out to the public repo automatically, using a git subtree split rather than just copying files over, so the public history is real: actual commits, in order, scoped to just that directory, not a zip file dumped over the old one every so often.

A second workflow lives in the public repo itself and runs on every push it receives there. It publishes to npm, computing the next version fresh from whatever's actually live on the registry rather than trusting a number I hand-edited somewhere. Every push that lands in that repo is meant to be a real release, not a draft sitting around waiting on me to remember to bump something. Authentication to npm goes through OIDC-based trusted publishing rather than a long-lived API token sitting in a repo secret waiting to leak. The GitHub org, repo, and workflow file are the credential, and that credential is good for exactly one thing.

None of this was strictly necessary to put a package on npm. I could have done the copy-paste version in twenty minutes. But the site publishes its own tooling automatically now, on every change that matters, with real history behind it and no standing credential to worry about, and that felt worth the extra afternoon.

What's next

The next thing taking shape is a local indicator database, a SQLite-backed store of curated indicators I import by hand: domains, URLs, IPs, hashes, and lately suspicious text phrases pulled out of phishing kit templates and my own research writeups, something to actually check candidates against instead of relying on memory and judgment every time. It's explicitly separate from /phish-report's "nothing submitted is stored" promise, nothing pasted into that tool ever ends up in it. This one's still early and still moving, so I'm not going to pin down more than that yet.

Between the two, the theme this stretch has been less "add a new check" and more "build the infrastructure around the checks I already have." Different kind of progress, but progress.