Every phishing control in the stack assumes the traffic passes through it. Once a device leaves the office, most of them stop applying and the agent on the machine becomes the only thing left. This page is about getting a DNS-verified phishing domain set onto that agent, keeping it current from a daily changelog, and deciding what happens locally when the set says no.
The endpoint is not the best place to block a phishing domain. It is the only place that still works when the device is somewhere you do not control.
Draw the path a click actually takes on a corporate laptop sitting in a hotel room, then draw the same path again with the machine plugged in at a desk. The two diagrams share a user, a message and a browser. They share almost nothing else.
The user opens a message in a desktop client, clicks a link, and the browser resolves a hostname. Nothing about this first step differs between the office and the hotel room.
If the machine is on your LAN, that resolution goes to a resolver you run, the request leaves through a firewall you configured, and it may traverse a web gateway that inspects it before anything is fetched.
The resolver is whatever the hotel handed out over DHCP, or a public one baked into the browser. There is no firewall of yours anywhere in the path, and no gateway to inspect anything.
If the VPN is up at all it is very likely split-tunnelled, because full-tunnel VPN for every laptop is a bandwidth bill nobody wants to pay, and a split tunnel by design sends general web traffic straight out of the local connection.
The agent is running on the device. It sees the process, it sees the socket, and on most platforms it can see the DNS query before the resolver ever hears it.
A network control enforces on the packets it sees, and a device that is not on your network produces no such packets. That is the gap the agent exists to fill, and it cannot be closed anywhere else.
You can mandate always-on VPN and some organisations do, but the failure mode is not that the policy is wrong — the tunnel drops on a captive portal, the conference network blocks the VPN protocol, or the device wakes from sleep with the browser already loading. Every one of those windows is a few minutes long, and a credential-harvesting page needs about forty seconds.
The agent applies identically on the office LAN, on a home network, on a phone hotspot and on an aircraft connection. It does not care whether the VPN is up.
It is the only layer in the stack whose coverage does not have a geography attached to it, and that property — not superior detection — is the reason phishing enforcement belongs on the device rather than only in front of it.
The agent knows things about the request that no network control can recover. When a hostname is blocked at a resolver, all you learn is that a device asked for it.
When the same hostname is blocked at the endpoint, you can learn which process asked, what its parent was, which document or message it was launched from, and which user session it belonged to. That turns a block from a log line into an incident with a shape, and the telemetry section below is entirely about exploiting it.
A resolver-level block still stops the request for every device on the LAN including printers, conference-room panels and contractor machines that will never run your agent, and it does so with no per-device deployment at all. Most organisations that get this right run both, from the same source list, so a domain blocked by DNS filtering in the building is also blocked by the agent in the hotel, and the two produce consistent verdicts a help desk can explain.
Enforcement is only half of it. The agent is also the only sensor positioned to explain how the request came to be made.
A blocked hostname requested by a browser tab is a user who clicked. The same hostname requested by a scripting host is a loader that is already running.
Office application spawning a shell spawning a network call is a different story from a browser launched by the user, and the agent records both.
Which logged-in user, on which device, in which session — the detail that turns a domain hit into a named person to call.
A browser extension can record the page or message the click came from, which is how you find the other recipients of the same lure.
DNS over HTTPS hides queries from a network resolver, but the agent observes the request at the point where the application makes it.
Whether the user retried, whether credentials were typed elsewhere, whether a file was written — sequence, not a single event.
The encryption point deserves expanding, because it has quietly changed where enforcement can happen at all. When a browser resolves names over DNS-over-HTTPS to a resolver of its own choosing, a network-level DNS block simply does not see the query. Organisations respond with policy — disabling the browser's own resolver on managed devices — but that policy applies only where you can set it, which excludes personal devices and any browser installed outside your management. An agent that hooks the request where the application makes it is unaffected by the transport the resolution eventually uses, and that is increasingly the difference between a control that works and one that used to.
Host-file enforcement, the crudest form of endpoint blocking, is worth mentioning precisely because it is the approach teams reach for first: writing entries that map known-bad hostnames to a loopback address costs nothing and needs no agent code at all. It is also a poor fit at this scale — a file with hundreds of thousands of entries slows name resolution on some platforms, it requires administrative rights to modify, it produces a connection failure rather than an explanation, and it is bypassed entirely by a browser doing its own encrypted resolution. Use it for a handful of critical entries if you like.
The whole design hinges on one number: how much memory an agent can spend to answer a lookup without asking anybody.
Start with the raw material. The daily feed is the complete database as CSV, delivered on a separate subscription from API credits, alongside a changelog listing the domains added and removed in that rebuild. Domain names average somewhere around twenty bytes, so a 390,000-entry list is roughly eight megabytes of text — a perfectly reasonable download once a day over any connection, but not a reasonable in-memory structure to search linearly.
| Structure | Memory | Lookup cost | Removals | Verdict |
|---|---|---|---|---|
| Raw CSV, scanned in place | ~8 MB | Linear scan per request | Trivial | Shipping the feed verbatim to every agent is the naive version of this design. Correct, and far too slow to sit in front of a name resolution. |
| Sorted 64-bit hash array | ~3.1 MB | About 19 comparisons, binary search | Clean, in place | Store a 64-bit hash of each normalised hostname. Invisible on a laptop, acceptable on a phone, and where most endpoint agents should stop — simple, exact and easy to reason about when something goes wrong at two in the morning. |
| Bloom filter, 1% false positives | ~470 KB | A handful of hashes and bit tests | Not supported | About 9.6 bits per entry. Fits comfortably inside a browser extension's memory budget. Says maybe, never definitely yes. |
| Bloom filter, 0.1% false positives | ~700 KB | A handful of hashes and bit tests | Not supported | About 14.4 bits per entry: ten times fewer confirmation calls for roughly fifty percent more memory. Still cannot delete an entry without a rebuild. |
When the local filter says a hostname might be on the list, the agent asks GET /api/v1/check?domain=host&apikey=key and gets an authoritative answer in under fifty milliseconds, along with the category, the DNS status and the timestamp of the last verification.
A full eight-megabyte pull to every device every day is defensible on a corporate laptop and rude on a metered mobile connection. The changelog gives you the delta instead: apply the added list, drop the removed list, and the agent's set matches the day's rebuild after transferring a fraction of the data.
None of these have a universally right answer, but every one of them has to be answered explicitly before the agent ships.
A confirmed entry is a hostname verified as currently resolving and confirmed as phishing infrastructure, which is a strong enough signal to block without a prompt in most environments. Warning instead of blocking is defensible where a wrong call would stop a business-critical workflow.
If you do allow an override, make it a deliberate act that names the person: a typed reason, a logged event, and an expiry. An override that is one click and forgotten becomes the standing configuration within a month.
A dropped connection produces a generic browser error, which teaches the user nothing and generates a help-desk ticket that nobody can diagnose. A browser extension can render a real interstitial naming the hostname and the reason, which is worth the extra deployment effort.
Whatever the surface, put the hostname on it. Users who see which domain was blocked report the message that carried it far more often than users who see a connection error.
The local set keeps working with no network at all, which is the main argument for holding one. What ages is its currency: a laptop that has been off for three weeks holds a three-week-old list, and it will miss everything registered since.
Record the set's build timestamp and surface it in the management console. A fleet-wide view of set age is the single most useful health metric this design produces.
The local-set-first pattern means routine browsing generates no outbound query, which matters enormously on personal devices. Only candidate hits are confirmed remotely, and only the hostname is sent.
Say so in the deployment documentation. On a BYOD fleet, the difference between "every site you visit is checked with a third party" and "nothing leaves unless it matches locally" decides whether the rollout happens.
A cache miss has to resolve into something, and the two failure modes are opposite. An agent that blocks on any lookup failure will block the internet the first time the API is unreachable from a coffee-shop network with a broken captive portal. An agent that silently allows on failure has quietly disabled itself, and nobody will notice for months.
The workable middle is to fail open on the network call, mark the request as unverified in telemetry, and retry asynchronously once connectivity returns. Because the local set answered independently, failing open on the confirmation step only affects hostnames the filter already flagged as candidates — a small, reviewable population. Pair that with an alert when the proportion of unverified requests on a device crosses a threshold, and a broken /api/v1/check path becomes visible in a day rather than a quarter.
The local set does the volume. Credits are spent only on the handful of lookups the device could not answer itself.
Take a fleet of 10,000 managed devices. Each active device resolves a few thousand hostnames on a working day, but almost all of that is repetition — the same handful of business applications, the same content hosts, the same telemetry endpoints — so a per-device cache of recent verdicts collapses it to a few hundred genuinely distinct lookups. Every one of those is answered by the local set with no network call. The only traffic that reaches the API is candidate hits: entries the local filter flagged, which need an authoritative answer.
The confirmation result belongs in a shared cache at the management server, not on each device.
With a one-percent filter and a few hundred distinct hostnames per device per day, that is a handful of confirmations per device — call it five. Across 10,000 devices that is 50,000 credits a day, which is far too many, and the fix is not a bigger package. Route confirmations through your own backend, cache the verdict centrally for the rest of the day, and 10,000 devices asking about the same flagged hostname become one credit rather than ten thousand.
With central caching in place the arithmetic changes shape entirely. The draw is no longer proportional to the fleet; it is proportional to the number of distinct hostnames the fleet collectively flags, which is a few hundred to a few thousand a day regardless of whether you have a thousand devices or a hundred thousand. Against the packages above, a centrally cached fleet of this size sits inside Professional or Business for a year.
One subscription supplies the CSV and changelog that every agent's local set is built from, regardless of how many agents you have. That is the correct shape for this workload: per-lookup pricing is right when each question is a decision, a bulk list is right when the same answer is needed on ten thousand machines at once, and an endpoint fleet needs both for different parts of the same job.
The same hostname, blocked on the same device, is two completely different incidents depending on what asked for it.
Consider two events that look identical in a resolver log: the same device, the same minute, the same blocked hostname. To the agent they are not remotely the same incident.
The requesting process is the user's browser and its parent is the desktop mail client. That is a user who clicked a link in an email — an awareness and mail-filtering story, and the immediate response is to find who else received the message and confirm no credentials were entered.
The requesting process is a scripting host and its parent is an office application. That is code already executing on the machine reaching for its next stage, and the response is isolation and forensics, not a conversation about link hygiene.
A resolver cannot tell those apart. It has an IP address, a timestamp and a query. The agent has the whole tree, and recording it at block time costs nothing because the agent already collects process and network events for its primary job. These are the fields worth capturing on every phishing-domain block:
That last field is worth more than it looks. Knowing that a blocked hostname was reached from a specific webmail message, or from a particular internal page, is what turns one device's event into a campaign view: search for the same referrer across the fleet and you have every user who was shown the same lure, including the ones who have not clicked yet. That is a materially better starting point than a list of devices that happened to resolve the same name.
Feed the result into whatever aggregates the rest of your detections. A phishing-domain block is a low-volume, high-specificity signal, which makes it unusually good material for correlation rules — it pairs naturally with an authentication event from an unusual location, or with a process-creation alert on the same host within the same minute. Teams routing this into SIEM integration generally find the volume small enough to keep indefinitely, and the triage path shares most of its shape with incident response work they already run.
When the daily rebuild adds hostnames, it is tempting to replay the fleet's recorded resolution history against the added list to find devices that reached a domain before it was known. That is a genuinely valuable hunt, and it is also the point at which an endpoint product starts holding a complete browsing history of every employee. Decide deliberately how long that history lives, keep it hashed where you can, and write the retention period into the product documentation rather than leaving it to a configuration default nobody reads.
The order matters more than the speed. Every step below produces something reviewable before the next one changes user experience.
Pull the daily CSV and changelog into your build server. Normalise hostnames and produce the structure the agent will ship.
Push the built set through your existing agent update channel. Version it, sign it, and record the build date on the device.
Match without blocking for two weeks. Count hits per device and per domain, and review anything unexpected before enforcing.
Route candidate hits through your backend to the check endpoint, with a shared cache so the fleet asks once, not once per device.
Turn blocking on for a pilot group, then widen. Watch help-desk volume and set-age metrics at each ring before proceeding.
Two weeks of observe-mode data tells you the real hit rate on your fleet, which departments generate it, and whether any hostname is appearing often enough to warrant a look before it starts producing block pages. It also gives you a number to take to whoever has to approve enforcement — not a vendor claim, but a count of how many times your own users would have been stopped last fortnight.
They are how you discover that one business unit uses a marketing platform whose click tracker happens to look like something else, or that a particular department's workflow depends on a redirect service you had not seen. Widen the ring only when the previous one has been quiet for a week, and keep the set-age metric from step two on the same dashboard, because a ring where a third of devices are running a two-week-old set is not actually enforcing what you think it is.
The same domain set applies to all of them. Almost nothing else about the deployment does.
The easy case and the one every design starts from. You control the operating system image, you can install a kernel-level or system-extension agent, you can force-install a browser extension through managed browser policy, and you can guarantee the update channel.
These invert almost all of that. You cannot install a system-level agent on a machine you do not own, and attempting it is usually the fastest way to end a BYOD programme. What you can realistically deploy is a managed browser profile with a force-installed extension scoped to work accounts, covering the browsing surface — where phishing actually happens — without touching the rest of the device.
These sit between the two. Platform restrictions mean a background process cannot generally intercept arbitrary network traffic, so enforcement typically arrives as a managed browser, a per-app tunnel, or an on-device content-blocking extension supplied through the management profile.
Kiosks, shop-floor terminals, ward workstations and meeting-room panels have no user to warn and often no interactive session to warn into. Hard blocking is the only sensible policy, because there is nobody present to make a judgement call.
A managed laptop is a managed laptop whether it is at a desk or in an airport, but its effective control set changes completely when it leaves. Design the agent so that nothing about its enforcement depends on reaching your infrastructure: the local set answers, the confirmation call is an enhancement rather than a requirement, and the update is opportunistic. An agent built that way behaves the same in both places, which is the only property that makes it trustworthy in either.
The feed builds the set. The check endpoint resolves what the set could not decide. Everything else is your own distribution machinery.
Daily CSV feed + changelog
The complete database as CSV on a separate subscription, plus the daily added and removed lists that drive delta updates.
GET /api/v1/check?domain=
One hostname, one credit, answered against the current database with category, DNS status and last-verified timestamp.
POST /api/v1/batch
Up to 100 domains per request, one lookup each. Used by the management server to confirm a batch of pending candidates at once.
The database-stats endpoint is worth wiring into the build job even though it looks like a diagnostic. It returns the current database size and the last-update timestamp, which gives your ingestion process a cheap way to confirm the feed it just pulled is the rebuild it expected rather than yesterday's file served from a cache. A build that silently ships a stale set to a fleet is the kind of failure that goes unnoticed for weeks, and one comparison at build time prevents it.
Below is that path as your management server would call it — a single check for one candidate, and a batch call when several devices have flagged different hostnames within the same window.
# One candidate flagged by an agent's local filter
curl -s "https://phishingdetectionapi.com/api/v1/check\
?domain=account-security-review.example&apikey=YOUR_KEY"
# Several pending candidates confirmed in one request (max 100)
curl -s -X POST https://phishingdetectionapi.com/api/v1/batch \
-H "Content-Type: application/json" \
-d '{"apikey":"YOUR_KEY",
"domains":["account-security-review.example",
"vpn-portal-reauth.example",
"files-shared-invoice.example"]}'
# Verdict cached centrally, then pushed to every agent that asked
{
"domain": "account-security-review.example",
"is_phishing": true,
"category": "phishing",
"dns_status": "active",
"last_checked": "2026-07-27T04:22:00Z",
"confidence": "high",
"database_size": 390000
}
The API key must be used server-side only. An agent binary or browser extension distributed to thousands of devices is not a secret store — anyone can extract a string from it. Have the agent ask your own management endpoint, and let that endpoint hold the key and call the API. This also gives you the shared cache the fleet arithmetic above depends on, so the two requirements are satisfied by the same piece of infrastructure.
These come up in design review, usually in this order, and every one of them deserves a straight answer.
Three reasons, and latency is the least important of them.
The local-set-first design fixes all three at once. The device answers from memory, nothing leaves for ordinary browsing, and the API is consulted only when the local structure says a hostname might be on the list. That is the pattern the rest of this page assumes.
Exactly two weeks stale, and there is no way around that. The set on the device is a snapshot of the rebuild it last received; anything added since is invisible to it, so a laptop that spent a fortnight in a drawer will not block a domain that entered the database ten days ago until it reconnects and applies the delta. What you can do is make the staleness visible and make reconnection cheap.
Not in any form worth measuring, provided you do not search it linearly. A binary search over a sorted array of hashes resolves in about nineteen comparisons; a Bloom filter is a handful of hash computations and bit tests. Both are microsecond-scale operations that sit well below the noise floor of a DNS resolution or a TLS handshake.
The agent asks the API and gets the correct answer. That is the entire purpose of the second tier: a probabilistic filter is allowed to be wrong in one direction only, and the direction it can be wrong in is the one where a confirmation call resolves it. A clean hostname flagged by the filter is confirmed clean in well under fifty milliseconds and cached, so it never asks again.
Partially. A managed browser extension force-installed through browser policy needs no system-level installation and covers the browsing surface, which is where the great majority of phishing clicks land. That is often the only realistic option on personal devices, and it is a legitimate deployment rather than a compromise.
Route confirmations through your own management endpoint and cache the verdict there. The first device to flag a hostname causes one lookup; every other device that flags the same hostname that day is answered from your cache at no cost. This is the single most important optimisation in the whole design, and it also solves the key-handling problem, since the API key then lives only on your server.
No, and a design that assumes it does will leave gaps. Network-layer controls cover every device on the network including the ones that will never run your agent — printers, cameras, contractor laptops, conference-room hardware — and they do so with no per-device deployment. The endpoint agent covers the devices that leave. Those two populations overlap but neither contains the other.
A known-bad domain set is a floor. Describing it as anything more is how endpoint products lose the trust of the teams that run them.
Verification requires observation, so a domain in its first minutes of life is absent from every list including this one, and an agent that says clean about it is answering correctly with the information that exists. The daily rebuild shortens that window and the changelog lets you find, after the fact, which devices reached the domain before it was known — but neither prevents the first visit.
When the credential form lives on a mainstream file-sharing platform or a general-purpose hosting subdomain, the hostname is shared with an enormous volume of legitimate traffic and blocking it is not an option. The hostname is simply the wrong unit of decision for that case, and content-level inspection is what addresses it.
This happens more than endpoint teams like to acknowledge, particularly with QR-code lures and anything involving a mobile authenticator. The agent protects the device it runs on and no other, which is an obvious statement that nonetheless shapes what an endpoint control can honestly claim.
The set-age metric described above exists precisely because this failure mode is silent: nothing breaks, no alert fires, and the device simply stops being protected while continuing to appear in the console. Treat set age as a first-class health signal rather than a diagnostic curiosity.
What it does do is make a large, specific population of hostnames unreachable from your devices wherever those devices are — more than 390,000 DNS-verified active phishing domains, rebuilt every twenty-four hours so that dead infrastructure is dropped rather than accumulated, enforced with no network dependency and no query leaving the machine for ordinary browsing. That is a real reduction in exposure for the population of attacks that has already been catalogued, and it pairs naturally with the mail-path work described under email security gateways, the perimeter enforcement under firewall security, and the analyst workflows under threat intelligence. Sold as a floor, it holds up. Sold as a solution, it will not.
Build the agent-side set from the daily feed, delta it from the changelog, and resolve the candidates your local structure cannot decide.