Warda-DNSDocs Warda: from ward — to protect, guardian
v0.9.3

Sensitive categories

The Protection settings page (Protection → Protection settings; up to 0.8.4 the page was called Protections, under the same menu) sets, for each profile (child, teenager, adult and devices given to nobody), what Warda does with each category:

Category Child Teenager Adult, nobody
Security threats (malware, phishing, scams) block block block
VPN, proxies and encrypted DNS block block allow
Adult content block block allow
Violence and weapons block monitor allow
Hate and terrorism (apology of terrorism, hate, discrimination) block block allow
Drugs block monitor allow
Gambling block block allow
Piracy monitor allow allow
Social networks monitor allow allow
Videos and streaming allow allow allow
Online games allow allow allow
Job sites allow allow allow
New domains (registered in the last 14 days) allow allow allow

These are the defaults. Monitoring lets the name through and records an alert (at most one per device and name per hour), shown on the dashboard and in the report of the person while they are a minor; it is offered for minors only, adults are never watched. Child sexual abuse material is always blocked, even over a personal allow rule; the other categories give way to the personal allow rules. A category is applied before the pauses of "something is broken", also to the names behind a CNAME, and its blocks cannot be unblocked in one click. A device that changes address is recognised again within seconds. When a minor becomes an adult or a person is removed, their alerts are deleted.

VPN, proxies and encrypted DNS (and iCloud Private Relay) have their list, always loaded: for an adult's device (allowed), Warda records a notice shown to that adult only, in My account and on their dashboard ("this device is no longer protected"); nobody else sees it. Videos and online games use their list of warda-lists and lists kept with Warda (internal/category/builtin.go, also some social networks the downloaded lists leave out); they serve the homework mode below. A category named in WARDA_CATEGORY_LISTS drops the names kept with Warda. Job sites are allowed for everyone by default: a business blocks them for the profile of the group of its workstations (see Groups of devices).

Devices placed nowhere (those of visitors, new devices, until they are given to someone or a group, and the addresses Warda does not know yet: a router that forwards every query, a new IPv6 address) are protected as nobody by default; they are never monitored, since they may be an adult's; the Protection settings page can protect them like a child or a teenager instead (PUT /api/v1/unknown-profile {"profile": "child"}).

The other lists are downloaded only when a profile blocks or monitors the category, from warda-lists (see Lists). New domains hold about a million names and block some new legitimate sites: turn them on only where needed. Child sexual abuse material has no default source, on purpose: such lists are only given to vetted organisations, and warda-lists publishes none (its build refuses any source for csam). This category applies only to the lists you give in WARDA_CATEGORY_LISTS, which also replaces the list of any category, for example WARDA_CATEGORY_LISTS="violence=/etc/warda/violence.txt,social=" (an empty value turns a category off).

Lists of hashes (CSAM) — from 0.8.0 (free, beta; catalogue csam-hashes; internal/blocklist/hash.go). No list exists for Warda today: the box is ready to apply one, and the owner of Warda has to obtain it from an authorised body (the URL List of the Internet Watch Foundation, or the body of the country of use; membership is contractual, and its terms say how the list may be stored and refreshed). Until then the category blocks nothing. Such lists are confidential: given to vetted members only, never to be published nor readable on a device. So the category also accepts a list of SHA-256 hashes of names, which the box applies without anyone being able to read it from the box, the backups or the logs:

  • one hash per line, 64 hexadecimal characters (upper or lower case), # comments allowed; each is the SHA-256 of a name in lower-case ASCII without the final dot (an internationalised name in its xn-- form);
  • a file whose entries are all hashes is a list of hashes; in a file that mixes names and hashes, the hash lines are ignored (counted as not understood), and the format of a plain list is read as before. A list of hashes works for any category, but is meant for this one;
  • a name matches when its hash, or the hash of a parent at each label down to two labels, is listed (a.b.example.com, b.example.com, example.com; never com): an entry blocks its subdomains, as a domain of a plain list does. Names are hashed only while a list of hashes is loaded;
  • set as before: WARDA_CATEGORY_LISTS="csam=/etc/warda/csam.txt" (a local file; an https:// source works too, refreshed every hour);
  • a body that gives addresses of pages rather than names: only their host can be used, since a DNS filter cannot block a single page.

Protection settings, column List of the table of the categories (and the list on a phone), says "Always blocked — list from an authorised body: none loaded" or "… loaded, N entries" (names and hashes), never the content; GET /api/v1/categories gives status.hashes for each category. From 0.8.0 a name of this category is never kept: the debug log says blocked: csam only (it carried the name before), and the journal — so the query log, the statistics and the SIEM export — records the name csam.invalid instead of the name asked, with the origin category:csam, behind a CNAME too and also when something that decides earlier blocked it (a quarantine, the hours of Internet, a service, a tunnel, a threat; its own origin is then kept). The block is still counted in the security report.

Lists#

Every list Warda downloads by default comes from the repository warda-dns/warda-lists, which gathers, checks and publishes them on its branch dist, one file per list, one domain per line (# comments): https://gitea.tips-of-mine.com/warda-dns/warda-lists/raw/branch/dist/<id>.txt.

File Used for
base.txt Base blocklist: advertising and tracking (WARDA_LISTS)
security.txt Security threats
bypass.txt VPN, proxies and encrypted DNS
newdomains.txt New domains
adult.txt, violence.txt, hate.txt, drugs.txt, gambling.txt, piracy.txt, social.txt The categories of the same names
video.txt, games.txt Videos and online games (with the lists kept with Warda)
jobsearch.txt Job sites
oui.tsv (from 0.8.5, not a list) The makers of the hardware addresses, downloaded once a day (see Network scan)

The sources of each list and their licences are in LICENSES.md of warda-lists; the lists are downloaded by each Warda, never shipped with it. Each list is refreshed every hour (WARDA_LIST_REFRESH) with its ETag, and a download that is not a list keeps the previous copy: no entry, a web page, a list of Warda without its header, or, from 0.8.7, a list that loses more than half of its entries at once (accepted when the server still sends so small a copy 24 hours later; see Upgrading to 0.8.7). csam has no file and no default source (see above).

Two rhythms, not to mix up. warda-lists builds its lists again every night at 03:17 UTC (and after a change of its sources): a list changes about once a day. Each box asks every hour (WARDA_LIST_REFRESH, 1h by default, and up to 10 % more each time so that the boxes never ask together) whether its lists changed, and downloads only those that did: a new list reaches a box within about an hour of its build. From 0.7.11 the team of Warda has a module to verify these lists: a list is measured before it is admitted, names are proposed to be added or removed, and a person decides each one (warda-lists, warda-analyst).

Updating the lists now (free; internal/admin/listrefresh.go, function lists-refresh-now of the catalogue). An administrator does not have to wait for the next hourly check: the button Update the lists now, on the dashboard under the line Base list and in the card Updating the lists of Protection settings (which also shows the build date of the base list and the last check of the box), checks at once the base list and the lists of the categories in use, with their ETag as on the schedule. The refresh runs in the background; the button shows "Updating…", then the result:

Result Shown Meaning
ok Lists up to date at least one list changed and is applied
unchanged Already up to date every list answered 304 Not Modified, or was sent again identical (a server without ETag)
failed Failed: <reason> a list could not be downloaded or was not a list; it keeps its previous copy (the reason is the first error, its addresses cut to their host, 200 characters at most)
  • One at a time, one a minute. A second click while a refresh runs answers that it is already running (the page then follows it); a new refresh is accepted a minute after the previous one started, so that a button pressed again and again never hammers the list servers. The scheduled refreshes go on as before (a refresh asked and a scheduled one never download together: they wait for each other). A refresh asked stops after 15 minutes, or with the service.
  • Who. The accounts that change the system (system written: the administrators), with the session and CSRF token of the other actions. The accounts that only read the system see the state (and, on Protection settings, the dates) without the button.
  • Administration log. lists.refresh when asked (with the account), lists.refreshed when it ends (result=ok, result=unchanged or result=failed <reason>). Whatever started it (the hourly check or the button), each download that brings a list that changed is written, once its new rules are in use, as lists.updated (by system, detail list=base or list=<category>), which the activity chart of the dashboard marks (see Events on the activity chart).
  • High availability. The button acts on the box whose page is open. The secondary box refreshes its own lists on its own schedule; to refresh it now, open its page (its own address) and press its button.

API:

Route Right Answer
GET /api/v1/lists/refresh system read {available, running, started, finished, result, error, last_check, generated, next_at}: last_check is the last time the box read its base list (scheduled or asked), generated the build time of the base list (# Generated:, absent when unknown), next_at when another refresh may be asked (absent: now)
POST /api/v1/lists/refresh system written starts a refresh and answers the state (running: true); 409 {"error", "running": true, "refresh": {…}} while one runs; 429 {"error", "refresh": {…}} with Retry-After less than a minute after the last one
GET /api/v1/system home read list_refresh: the same state, for the accounts that read the system

The figures of a list (from 0.7.11, free; internal/blocklist/manager.go, function list-names of the catalogue). Each list of warda-lists tells two figures in its header: # Domains:, its entries, and # Names:, the distinct names of its sources it stands for (see warda-lists). An entry blocks its subdomains too: when example.com is listed, the build drops ads.example.com from the file, and Warda blocks it all the same. A tool that keeps one line per name (Pi-hole, AdGuard Home) counts both: its counter is larger for the same names blocked. Warda reads # Names: and # Generated: (the time of the build, UTC) and shows them on the dashboard, card Services of the box, line Base list:

  • when the list tells its names: "605,224 domains blocked, covering 731,487 names of the source lists" (the figures of the lists built on October 31, 2026; on docs.warda-dns.com, those of the last build), and a small line under it: "Each entry also blocks its subdomains: other tools count those names one by one. List built on … ; this box last checked on …." — the first date is the build of warda-lists, the second the last check of this box;
  • otherwise, as before 0.7.11: "605,224 domains, updated …" (the date of the last check). That is the line of a list built before # Names: existed, of a list of someone else, and of a list whose names equal its entries.

What is read, and from where:

  • only the first lines of the file (16 lines and 4096 bytes at most, the comment lines it starts with), from the download and from the copy on disk alike; a line written another way than warda-lists writes it, or met twice, tells nothing: a figure is never guessed, and a figure smaller than the entries of the file is left out;
  • only for the lists of Warda: a source whose address is under the address of the lists of Warda (…/warda-lists/raw/branch/dist/). The same lines in a third-party list set in WARDA_LISTS never change what is shown;
  • GET /api/v1/system (every account that reads the dashboard), in service.blocklist: names — the sum, over the sources that loaded, of the names of each when it tells them, of its entries otherwise; absent (0) while no source that loaded tells its names — and generated, the oldest build time known among the lists of Warda that loaded (absent when unknown). The accounts that see the addresses of the lists also get, for each source of sources, its own names (0: unknown) and generated. /healthz is unchanged: these two figures are never given without sign-in.

A first start, or an upgrade from a version that used other sources, may find a list of warda-lists out of reach with no copy on disk yet. That list then falls back on the source Warda used before it (HaGeZi Multi PRO for base.txt, HaGeZi for security, bypass and newdomains, The Block List Project for adult, drugs, gambling, piracy and social; category.Fallbacks): downloaded once, read from its copy and logged ("list not downloaded yet: its previous source is used meanwhile", the source shown with an error), and never again once the list itself is downloaded, when its copy is deleted. A list you configure yourself has no fallback.