Someone Spammed My DEV Post. I Traced It to a Wombat.
Illustration generated for this article. Every prop is a finding: the sack of blank name badges is the Faker persona namespace, the rubber stamp is the inert tracking parameter, the three coins are the break-even, and the red yarn connects nothing because attribution failed. TL;DR — A spam comment on my article led to a TinyURL, a throwaway .store domain, and finally a legitimate SaaS product with an affiliate code stapled to it. No malware, no cloaking, no exploit. The account that posted it has a name generated by faker.js and a 19th-century engraving of a wombat for a face. I costed the whole operation out: three signups a year pays for it. That's why it will never stop. Full evidence, raw captures and a reproduce script: github.com/copyleftdev/dev-to-comment-hustle Act I: The comment I published Migrating Legacy LLM Infrastructure to an AI Gateway on September 1st. Eight days later, underneath two thoughtful comments about shared-key blast radius and provider failover, this appeared: Stop wasting time applying manually Let AI handle your job applications every single day Increase your chances of getting interviews fast tinyurl.com/36nsecn5 No punctuation. No engagement with the post. A shortener. It's funny in the way all low-effort spam is funny — it's not even trying. But a shortener is a closed door, and I have a shell. Let's open it, then let's go find who knocked. Act II: Where the link goes Never click. Ask for headers and refuse the redirect: curl -sS -I 'https://tinyurl.com/36nsecn5' HTTP/2 301 location: https://zenviapro.store/massapply?whose=yahoo x-robots-tag: noindex zenviapro.store. A route called /massapply, and a whose=yahoo parameter that looks like campaign segmentation. Follow it all the way: curl -sSL -D - -o /dev/null 'https://zenviapro.store/massapply?whose=yahoo' | grep -iE '^HTTP/|^location:' HTTP/1.1 302 Found Location: https://loopcv.pro/?via=md HTTP/2 301 location: https://www.loopcv.pro/?via=md HTTP/2 200 And there it is. LoopCV. A real, functioning job-application-automation SaaS. Not a phishing kit. Not a credential harvester. A product you can buy with a credit card — with ?via=md on the end. ?via= is Rewardful's referral parameter, and LoopCV's own affiliate page points registrations at loopcv.getrewardful.com. So md is somebody's affiliate token, and every person who clicks that comment and later subscribes puts money in a stranger's pocket. This is not a malware campaign. It's affiliate marketing with the manners removed. Act III: The infrastructure is held together with tape The sloppiness is the signal. It's a stock Express app with the wrapper still on HTTP/1.1 302 Found X-Powered-By: Express Content-Type: text/plain; charset=utf-8 Content-Length: 48 Found. Redirecting to https://loopcv.pro/?via=md Found. Redirecting to is the literal default body of Express's res.redirect(). X-Powered-By: Express is the header every hardening guide tells you to strip in the first five minutes. Neither was touched. Ask for anything else and you get the stock 404: curl -sS 'https://zenviapro.store/' # ErrorCannot GET / There is no website here. No landing page, no cloaked content, no fake blog. One domain, one route, about ninety seconds of JavaScript. The tracking parameter is a prop whose=yahoo looks like segmentation. Which list? Which provider? Let's ask: for w in yahoo gmail outlook devto reddit '' XXtest; do printf '%-8s -> ' "${w:-}" curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' \ "https://zenviapro.store/massapply?whose=$w" done yahoo -> 302 https://loopcv.pro/?via=md gmail -> 302 https://loopcv.pro/?via=md outlook -> 302 https://loopcv.pro/?via=md devto -> 302 https://loopcv.pro/?via=md reddit -> 302 https://loopcv.pro/?via=md -> 302 https://loopcv.pro/?via=md XXtest -> 302 https://loopcv.pro/?via=md Inert. Every value routes identically. It splits no traffic and sub-tags nothing. The one piece of the URL that looks like operational sophistication is decoration. There is no cloaking whatsoever Real malicious redirectors fingerprint you — benign page for the researcher, payload for the victim. This one returns the same 302 to Googlebot, to curl, to an iPhone, and to a request with no User-Agent at all. No cloaking is itself a finding. This operator has no threat model, because nothing in the chain is illegal. It's a terms-of-service violation wearing a trench coat. The mail server is the real pivot dig +short zenviapro.store MX # 10 mail.beeservices.shop. The mail exchanger lives on a different domain — which resolves right back to the same box: zenviapro.store A 50.114.206.36 mail.beeservices.shop A 50.114.206.36 beeservices.shop A (nothing) beeservices.shop has no A record and no certificate in Certificate Transparency, ever. It's a mail-only domain. One box, two domains, two roles — that's a portfolio, not a one-off. And the box is listening. Port 80 closed; 443 and 25 open: 220 mail.zenviapro.store ESMTP 250-PIPELINING 250-8BITMIME 250 SMTPUTF8 No STARTTLS. No AUTH. A minimal MTA that does nothing but move mail in cleartext. The stale HELO is a fingerprint My favourite detail. The banner announces mail.zenviapro.store. dig +short mail.zenviapro.store A # (nothing) That hostname does not resolve. It's a leftover from an earlier config, before the MX was swapped to beeservices.shop. They rebuild the domains; they don't rebuild the server. A banner that disagrees with DNS is a durable pivot you can hunt across an entire fleet. Act IV: Who posted it This is the part I actually enjoyed. DEV has a public API, so the commenter isn't a mystery: curl -s 'https://dev.to/api/comments?a_id=4547359' | jq -r '.[].user.username' max_quimby mudassirworks jaylonstiedemannterry78-993 Meet jaylonstiedemannterry78-993, display name Jaylon_Stiedemann-Terry78, user ID 3748129, joined February 2, 2026. The profile is a vacuum: { "username": "jaylonstiedemannterry78-993", "name": "Jaylon_Stiedemann-Terry78", "joined_at": "Feb 2, 2026", "summary": null, "location": null, "website_url": null, "twitter_username": null, "github_username": null } Zero published articles. No bio, no location, no links, no socials. Seven months of membership and a single comment to show for it. A note on durability. If DEV removes this account — which it may well do — the profile lookup above starts returning 404 and the account page goes dead. That doesn't retract anything: the raw captures are committed in the evidence repo, checksummed, and dated. Read a 404 as the platform doing its job, not as a claim being withdrawn. The infrastructure findings are independent of the account either way, and the pattern — a Faker-templated name, an empty profile, an aged dormant account — outlives any single username. The name is machine-generated, and I can prove it Say Jaylon_Stiedemann-Terry78 out loud. Something's off — Stiedemann-Terry is a double-barrelled surname that doesn't sound like a family, it sounds like a draw. It is. Those tokens come straight out of Faker, the library every developer on earth uses to generate fake test data. Let's check the actual source: curl -sO https://raw.githubusercontent.com/faker-js/faker/next/src/locales/en/person/last_name.ts curl -sO https://raw.githubusercontent.com/faker-js/faker/next/src/locales/en/person/first_name.ts grep -n "'Stiedemann'" last_name.ts # 408: 'Stiedemann', grep -n "'Terry'" last_name.ts # 417: 'Terry', grep -n "'Jaylon'" first_name.ts # 2514: 'Jaylon', All three. Jaylon from the first-name list, Stiedemann and Terry both from the surname list. And here's the kicker: grep -c -- '-' last_name.ts # 0 Faker's 466-entry English surname list contains zero hyphens. So Stiedemann-Terry isn't one surname from the list — it's two independent draws that the operator joined with a hyphen. This isn't stock faker.internet.username(). It's a custom template: {firstName}_{lastName}-{lastName}{2 digits} Which means we can size their supply: 3,185 first names × 466 surnames × 466 surnames × 100 = 69,164,186,000 Sixty-nine billion distinct personas. Roughly eight per human being alive. They will never run out of names, and no blocklist of usernames will ever catch up. (One thing I checked so I wouldn't over-claim: the un-suffixed jaylonstiedemannterry78 returns a 404 — nobody has it. So the -993 is DEV's own username normalization, not evidence of a prior collision.) The avatar is a wombat The profile image is a 400×400 PNG, 8-bit grayscale-plus-alpha, stripped of all metadata. I downloaded it expecting a GAN face or a default monogram. It is a Victorian-era scientific engraving of a wombat. Not a stock photo of a person. Not an AI-generated headshot. A piece of public-domain 19th-century natural-history line art of a stout Australian marsupial, serving as the face of a fake job-spam persona. Whoever built this pipeline wired the avatar slot to some public-domain clipart source and never looked at the output. I want to be precise about something: there is no real person here to name. The name is provably synthetic, the face is a public-domain animal illustration, and the profile is empty. That's not me protecting anyone's privacy — it's the finding. The timeline says something the Express config doesn't Line the dates up: Date Event Δ 2026-01-27 zenviapro.store registered (Namecheap) — 2026-02-02 DEV account created +6 days 2026-07-30 First TLS certificate issued +184 days 2026-09-09 Spam comment posted +41 days The domain and the account were provisioned six days apart — same procurement burst. Then both sat completely dormant for six months before the certificate was issued and the thing went live. That's aged-asset tradecraft. New domains and new accounts trip reputation heuristics; seven-month-old ones don't. And it sits in genuine tension with everything in Act III: sloppy at the application layer, disciplined at the account-aging layer. Which makes sense once you think about who this is. Aging assets doesn't take skill. It takes patience, and a calendar. Stripping X-Powered-By takes knowing what it is. It wasn't a blast I swept the comments on all 30 of my published articles for the pattern: for id in $(jq -r '.[]|select(.comments_count>0)|.id' mine.json); do curl -s "https://dev.to/api/comments?a_id=$id" \ | jq -r '.. | objects | select(has("id_code")) | ((.body_html // "") | gsub("]*>";"")) as $t | select($t | test("tinyurl|applying manually|job application";"i")) | "HIT \(.id_code) @\(.user.username)"' done Exactly one hit. Not a shotgun across my whole back catalogue — one comment, on one post, eight days after it went up. Whether that's targeting or just a slow drip, I can't tell from one sample. But it isn't volume. Act V: I went looking for them on GitHub. That search is the finding. Affiliate spammers leave GitHub artifacts more often than you'd think, because GitHub repos rank well and a repo full of referral links is free SEO. So I went hunting with gh. Start with the direct IOCs for q in '36nsecn5' '50.114.206.36' 'zenviapro.store' 'beeservices.shop'; do gh search code "$q" --limit 5 done Zero hits. All four. The TinyURL slug, the origin IP, the redirector domain, the mail domain — none of it appears anywhere in GitHub's index. The operator has published nothing. Then check whether anyone else has flagged them PhishDestroy maintains destroylist, a curated blocklist of phishing and scam domains. I pulled the whole thing: curl -sL .../destroylist/HEAD/list.txt -o dl.txt wc -l dl.txt # 202659 grep -ixc 'zenviapro.store' dl.txt # 0 grep -ixc 'beeservices.shop' dl.txt # 0 202,659 curated malicious domains, and ours isn't one of them. Combined with the NOT_OBSERVED reputation verdict, that's now two independent sources agreeing: nobody is tracking this, because by every technical definition there's nothing to track. The lead that looked incredible and wasn't Here's where I nearly fooled myself, so I'm showing my work. Searching zenviapro turned up a hit in phishdestroy/namesilo-evidence — a registrar-abuse investigation filed with ICANN. In a list of flagged NameSilo domains: zenviaetc.info zenviahub.info zenviapro.info ← same second-level label as ours zenvias.info zenviatime.info zenviazone.info A six-domain family, same generation pattern, one of them sharing our exact label. My pulse went up. And they really are one operator — all six resolve through the identical Cloudflare nameserver pair: zenviapro.info asa.ns.cloudflare.com harley.ns.cloudflare.com zenviahub.info asa.ns.cloudflare.com harley.ns.cloudflare.com zenviaetc.info asa.ns.cloudflare.com harley.ns.cloudflare.com ... all six identical But it isn't our operator, and the contrast kills it: zenvia*.info cluster zenviapro.store (ours) Registrar NameSilo Namecheap DNS Cloudflare (asa/harley) registrar-servers.com Hosting Cloudflare proxy Linveo direct, no proxy TLD .info × 6 .store Nothing shared but six letters. Both are almost certainly riding the name of Zenvia, a real Brazilian CPaaS company — which is exactly why the label collides. Two unrelated operators reaching for the same brandable string. A matching name is not a matching operator. If I'd stopped at the grep I'd have published a confident, wrong attribution to a phishing cluster that has nothing to do with this. What GitHub did give me The affiliate ecosystem, in the open. LoopCV referral links are scattered across GitHub in exactly the SEO-backlink genre I expected: Repo Token Ramas68/LoopCV-Promo-Codes — *"LoopCV Promo Codes \ 50% Off Discount"* diaodiaozhuye/awesome-ai-startups ?via=toolify heukshow/aicity-os ?via=sang-kwon Look at those tokens. abdul. toolify. sang-kwon. A first name, a company, a full handle. The affiliates who promote LoopCV in public sign their work. Ours is md. Two characters, no name, nothing to search. That's the only genuinely deliberate piece of operational security in this entire campaign — and it's not on the server, the domain, or the account. It's on the one string that would have led back to a person. So: no attribution. And the shape of the failure is the story. Someone who leaves X-Powered-By: Express on, ships a fake tracking parameter, and picks a wombat for an avatar still knew to make the payout token anonymous. They didn't secure the operation. They secured the part that gets paid. Act VI: The economics, which are the actual vulnerability Stop thinking like a defender and think like the operator. LoopCV's affiliate program pays 25% commission. Publicly reported figures put subscriptions at $50–$200 with customers staying 6–12 months. Exact interval arithmetic, gross revenue per referred customer: [$50, $200] × [6, 12] = [$300, $2,400] At 25%: [$300, $2,400] ÷ 4 = [$75, $600] ← commission per conversion Cost side: a .store domain plus a year of budget hosting — call it $38–$180 all-in. Break-even: [$38, $180] ÷ [$75, $600] = [19/300, 12/5] = [0.06, 2.4] Between one-sixteenth of a signup and three signups per year covers the entire operation. That's the whole thesis. Nothing here needs to work well. The copy can be terrible. The tracking parameter can be fake. The 404s can leak the framework. The HELO can be stale. The mascot can be a wombat. Three conversions and the year is paid for, and everything after that is margin on infrastructure that costs less than lunch. You cannot out-moderate that math. Reputation feeds have nothing, and they're right I ran the origin IP through an offline reputation lens: { "target": { "type": "ip", "value": "50.114.206.36" }, "verdict": { "disposition": "unknown", "confidence": 0.0, "reason_codes": ["NOT_OBSERVED"] }, "observations": [] } Not observed. No feed has it — and that's correct. It hosts no malware, no C2, no phishing. Threat intel is tuned for technical harm, and this campaign's harm is economic and reputational. It will sit below every threshold you own, forever. Who the victim actually is Not me. I scrolled past it. LoopCV is the victim. A real company with a real product is having its brand welded to comment spam by an affiliate they've likely never spoken to. They eat the reputational damage, they pay commission on the conversions, and the operator's total exposure is a $12 domain and a wombat. To be explicit, because it matters: I found no evidence that LoopCV is running or is aware of this campaign. Open affiliate programs get abused; that's the risk of the model. The fix belongs to the vendor: Require affiliates to declare traffic sources, and enforce it Ban unsolicited comment/forum posting in the program terms, in writing Flag referral tokens whose traffic arrives overwhelmingly via shorteners with no referrer Kill tokens on abuse reports — fast, without requiring a lawyer ?via=md is a token. Tokens can be revoked. That's a one-line fix that permanently ends this specific campaign, and exactly one party can perform it. IOCs Campaign infrastructure — safe to blocklist: Indicator Type Notes tinyurl.com/36nsecn5 URL Shortener entry point zenviapro.store Domain Redirector; Namecheap; created 2026-01-27 https://zenviapro.store/massapply URL Only live route beeservices.shop Domain Mail-only sibling; no A record, no CT history mail.beeservices.shop Hostname MX for zenviapro.store 50.114.206.36 IPv4 Origin; 443 + 25 open, 80 closed AS62564 / oh2.linveo.com ASN / rDNS Linveo, Ohio jaylonstiedemannterry78-993 DEV account user_id 3748129; joined 2026-02-02; 0 articles Behavioural signatures — this is what actually generalises: Signature Value Affiliate token via=md (Rewardful format) Inert campaign param whose= — routing-neutral Server fingerprint X-Powered-By: Express + body Found. Redirecting to Root response Cannot GET / (Express default 404) SMTP banner 220 mail.zenviapro.store ESMTP — does not resolve SMTP capabilities No STARTTLS, no AUTH SPF v=spf1 mx ip4:50.114.206.36 ~all DMARC Absent on both domains Persona template {firstName}_{lastName}-{lastName}{2 digits}, all tokens ∈ Faker en Avatar class Public-domain engraving, grayscale+alpha PNG, metadata stripped Provisioning pattern Domain + account within 7 days, then ~6 months dormant ⚠️ loopcv.pro is NOT an indicator of compromise. It's a legitimate destination being abused by a third-party affiliate. Do not blocklist it. Blocklisting the victim is how threat intel gets a bad name. What to actually do If you run a comment platform: the highest-signal feature isn't the text, it's the shape. Zero-engagement first comments containing a shortener, from accounts with no posts and an empty profile, are trivially clusterable. Resolve shorteners server-side at submission time and score the destination. And the persona template is a gift — a name whose tokens all appear in Faker's en locale, with a structure Faker itself doesn't emit, is close to a free classifier feature. If you write on DEV: don't click, and don't just delete. curl -I takes four seconds. Then report the destination to the vendor, not the comment to the platform. The platform can remove one comment; the vendor can revoke the token behind all of them. If you run an affiliate program: you are one unsupervised token away from your brand appearing under a headline like this one. Read your traffic sources. The takeaway I went in expecting a dropper. I found four lines of Express, a fake tracking parameter, a name drawn from a test-data library, and a 19th-century wombat. That's the uncomfortable part. The most durable spam on the internet isn't sophisticated — it's cheap and legal. There's no CVE here, no payload to reverse, no C2 to sinkhole. Every traditional defensive tool I own returns NOT_OBSERVED, and every one of them is right to. The economics are the vulnerability. Three conversions a year, and the whole thing pays for itself forever. The wombat is just a bonus. All findings are from passive reconnaissance — DNS, WHOIS, Certificate Transparency, HTTP headers, a TCP banner grab, and DEV's own public API — against infrastructure and accounts the operator published for public consumption. No systems were accessed, no credentials used, nothing exploited.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to