Skip to content
machinemail

Links

How MachineMail analyses every link in inbound mail for mismatched text, lookalike and punycode domains, raw IPs, shorteners, brand impersonation and other phishing signals.

Every link in a message gets an entry in trust.links. Links come from HTML anchors (<a> and <area>) and from bare http://, https:// and www. URLs in the text. Entries are deduplicated by target and text, up to 500 per message.

json
{
  "href": "http://198.51.100.23/paypal/login.php?session=8f7a6c",
  "text": "https://www.paypal.com/signin",
  "domain": "198.51.100.23",
  "display_mismatch": true,
  "shortener": false,
  "suspicious": true,
  "reasons": [
    "Unencrypted http link to a login/account page",
    "Link points to a raw IP address (198.51.100.23)",
    "PayPal login-style path on 198.51.100.23",
    "Link text says www.paypal.com, href is 198.51.100.23"
  ]
}

Links are analysed as written. MachineMail does not fetch them, so final_url is not currently set and no request ever reaches the sender’s server because of a check.

Mismatched text: display_mismatch

When the visible text of a link looks like a URL or a domain, its domain is compared with the real destination. display_mismatch is true, and the link counts as suspicious, when:

  • the text names a different organisational domain than the target (Link text says www.paypal.com, href is 198.51.100.23)
  • the text names a domain but the link goes through a URL shortener (Link text says stripe.com, but it goes through shortener bit.ly)
  • the text and target share a domain only once lookalike letters are normalised (Link text says paypal.com, href is a lookalike domain ...)
  • a mailto: link shows one address or domain and mails another

Links through known email click-trackers (SendGrid, Mailchimp, Mandrill, HubSpot, Mailgun, Postmark, SparkPost, Customer.io, Substack, beehiiv, Amazon SES tracking, Proofpoint URL Defense, Outlook Safe Links and others) are expected to differ from their text and are not marked as mismatched.

Link text that is not a URL (“View it in your browser”) is not compared. Subdomains of the same organisation (www.stripe.com text, pay.stripe.com target) match.

Structural red flags: suspicious

suspicious is true when any of these fire. Each adds a line to reasons:

SignalExample reason
javascript: or vbscript: URLjavascript: URL
data: URLdata: URL (can embed a full phishing page)
Unparseable URLMalformed URL
Scheme other than http, https, ftpUnusual scheme ...
Plain http to a login, verify, account, billing or password pathUnencrypted http link to a login/account page
Raw IPv4 or IPv6 hostLink points to a raw IP address (198.51.100.23)
Userinfo before the host, as in https://paypal.com@evil.exampleURL contains userinfo before the host (the real host is evil.example)
Port other than 80, 443, 8080, 8443Non-standard port 8888
Punycode or mixed-script hostDomain uses lookalike characters (pаypal.com imitates paypal.com) or Internationalised (punycode) domain ...
Four or more labels above the registrable domainExcessive subdomains (7 labels)
Hostname over 60 charactersVery long hostname
A brand name in a domain the brand does not ownPayPal name used in a domain it does not own (xn--pypal-4ve.com)
A brand name in a login-style path on someone else’s domainPayPal login-style path on 198.51.100.23
A link inside hidden HTMLLink is hidden from the human reader

mailto:, tel:, sms:, cid: and # fragment links are not checked for structural flags.

Brand impersonation

MachineMail keeps a list of brands commonly impersonated in phishing and the domains each one really uses: PayPal, Microsoft, Office 365, Outlook, OneDrive, Google, Gmail, Apple, iCloud, Amazon, Netflix, Facebook, Instagram, WhatsApp, LinkedIn, Dropbox, DocuSign, Adobe, Chase, Wells Fargo, Bank of America, Stripe, Coinbase, Binance, MetaMask, DHL, FedEx, USPS, HMRC, GitHub, Okta, Salesforce, Slack, Zoom, Venmo and Wise.

A brand name inside a host it does not own (paypal-secure-login.example, microsoft.account-verify.example) is flagged, and so is the brand’s name in a credential-style path on a foreign domain. Hosts are compared after lookalike letters are normalised, so a Cyrillic а does not hide the brand. The same list is used by the classifier to catch a display name such as PayPal <service@paypa1-security.com>.

Shorteners

shortener is true for about fifty URL shorteners, including bit.ly, t.co, tinyurl.com, goo.gl, ow.ly, is.gd, rebrand.ly, cutt.ly, dub.sh, lnkd.in, amzn.to and youtu.be. A shortener alone does not make a link suspicious, but it adds URL shortener hides the destination to the link’s reasons, and the classifier counts shortened links against a message that also asks for credentials or payment.

  • Any link with display_mismatch makes the message at least suspicious.
  • The links component is 10% of the score: 1 minus 0.6 per mismatched link and 0.3 per other suspicious link, never below 0.
  • Mismatched links and suspicious links raise is_phishing.
  • Reasons for suspicious and mismatched links are copied into the top-level trust.reasons, prefixed with the link’s domain where needed.

An agent that browses should not follow a link from mail just because the text looks right:

ts
const safeLinks = message.trust.links.filter((l) => !l.suspicious && !l.display_mismatch && !l.shortener);

Better still, only follow links from verified or known mail that the task needs, such as a sign-up confirmation you just triggered. See Using trust in agents.