EN
  • English
  • Deutsch
  • Polski
  • Italiano
  • Español

Custom rules

Under review — wording may still change.

Rules you write yourself, kept in a Custom List and mounted on a profile.

What this step does

Imported lists are other people’s judgement calls. Sooner or later you need your own: unblock the domain a list got wrong, block the one every list missed, keep a short household deny-list no feed will ever carry.

Since 0.44.0 there is one way to do that: a Custom List. It is a text file of rules that you declare in the config and mount on a profile. Wildcards, regular expressions and $important all work in it.

If you upgraded from 0.41, 0.42 or 0.43
Older releases had a second mechanism, [[admin_rules]], and the warden profile allow / deny commands. Both are gone. The upgrade moves the rules you had into Custom Lists for you, and the old commands now refuse to write anything. See Hosts upgraded from 0.41.

The file

A custom list is one text file at packs/<id>.txt, beside your master config. On a fresh install that is:

text
/etc/purge-warden/packs/minecraft.txt

A host installed before 0.43.0 keeps its config in /var/lib/purge-warden/ until you run warden config relocate. Its packs sit in /var/lib/purge-warden/packs/.

The path comes from the id and cannot be changed. A custom list has no file field, so a traversal path, an absolute path or two lists sharing one file cannot be written. A symlink in that slot is refused.

You rarely edit the file by hand. The warden custom-list commands below create the file, declare it and change its rules in one step.

A pack is part of the configuration, like config.toml. Do not delete a pack file by hand while the config still declares it: the running daemon keeps serving, but the next start refuses the config, and the node serves no DNS until you fix it. To remove a list, run warden custom-list delete <id> --cascade-unmount, which removes the declaration, the mounts and the file in one step. Keep other files, such as editor swap or backup files, out of packs/. See Troubleshooting and Prevent an outage.

The rule grammar

One rule per line. Anything after # on its own line is a comment, and blank lines are ignored.

LineMeaning
||ads.example.com^deny ads.example.com and everything under it
ads.example.comthe same, written as a bare domain
@@||cdn.example.com^allow cdn.example.com and everything under it
*.example.comdeny every subdomain, and the apex example.com too
*.example.com$noapexdeny every subdomain, but not example.com itself
/^ads[0-9]+\.example\.com$/regular expression, matched without regard to case
||example.com^$importanta rule that ranks above ordinary rules (see below)
@@*.example.com$importantmodifiers combine with allow and wildcard rules

Details that catch people:

  • An exact rule matches the name and everything below it. ||example.com^ also blocks www.example.com.
  • The only modifiers are $important and $noapex. Any other modifier is an error.
  • A stray * anywhere except a leading *. is an error.
  • A hosts-file line such as 0.0.0.0 ads.example.com is not a rule.
  • Domains must be ASCII letters, digits and hyphens. For an internationalised name, write the xn-- form. For example, münchen.de becomes xn--mnchen-3ya.de. Warden matches and displays names in that form, so the Query Log and warden output show xn-- as well.
One bad line rejects the whole change

A line that does not parse is not skipped. The whole candidate policy is refused. On a reload, Warden keeps serving the previous policy. At cold start it does not serve a partial one. Run warden config lint after you edit a pack by hand.

Save a pack as UTF-8 without a byte order mark (BOM). A BOM at the start of the file makes the first line fail the same way.

Limits

LimitValue
Custom lists256
One pack file1 MiB
All packs together32 MiB
Rules in one list25,000
Wildcard and regex rules256 per profile, 2,048 in total
Regex rules32 per profile, 128 in total
One rule4,096 bytes

Imported lists are different, whether they come from a URL or from a local file. They cannot use @@, wildcards, regex or $important. Those stay in your own Custom Lists. See Import lists.

Create one, then mount it

Every mutating command except apply takes --dry-run to show the change without writing it.

bash
# Create the list (file and declaration)
warden custom-list create minecraft --display-name "Minecraft servers"

# Add rules
warden custom-list add minecraft mc.example.com --deny
warden custom-list add minecraft cdn.example.com --allow
warden custom-list add-rule minecraft --rule '*.realms.example.com'

# Mount it on a profile
warden profile mount kids --custom-list minecraft

The result in the config is a declaration and a mount:

/etc/purge-warden/config.toml
toml
[[custom_lists]]
id = "minecraft"
display_name = "Minecraft servers"

[profiles.kids]
custom_lists = ["minecraft"]
lists = { ads = "deny" }

custom_lists must come before lists in that table. TOML cannot write a bare value after a table has started, so the other order is a parse error.

A profile that mounts an id nobody declared is refused at load.

Deleting a list removes the declaration and its pack file. It is refused while a profile mounts the list, unless you pass --cascade-unmount, which unmounts it from every profile in the same step. In 0.44.5, delete does not work once the pack file is already missing: see Troubleshooting for that case.

Which rule wins

Only the profile’s own policy decides. For one query, Warden collects every matching rule from the mounted Custom Lists and picks the winner in this order:

  1. $important allow
  2. $important deny
  3. ordinary allow
  4. ordinary deny

If no custom-list rule matches, the imported lists decide: a deny-direction list blocks, an allow-direction list forwards. If nothing matches at all, the query is forwarded. Under block_all, only a Custom List allow forwards: an allow-direction imported list does not lift block_all.

Two consequences:

  • An allow beats a deny for the same domain, whether both are in one list or in two lists on the same profile.
  • Under block_all, a custom-list allow still forwards. That is how you build an allow-only profile.

Profiles own the full chain, including how the profile itself is chosen: decision precedence.

Hosts upgraded from 0.41

The upgrade rewrites what you had:

  • Rules attached to a profile become a Custom List for that profile, mounted on it.
  • Rules on a device become a Custom List plus a copy of the profile that mounts it, and the device points at the copy.
  • Rules nothing referenced are kept in an archive list that no profile mounts.

If your old rules used wildcards, regular expressions or $important, the upgrade check lists each one as a finding and waits for your choice. You can answer with the WARDEN_UPGRADE_MIGRATION_CHOICES file. When the check finds something to review explains the format.

These commands now fail before they read or write anything, with the message Legacy allow/deny rules and device overlays are retired. Create a Custom List and mount it on the target profile instead.

  • warden profile|device|default allow|deny
  • warden rule undo
  • warden device rules prune
  • warden profile admin-rule add|remove
  • warden group|subnet profile-allow|profile-deny

A schema 5 config that still contains [[admin_rules]], or a device with allow_rules, deny_rules or override_profile_deny, is refused.

From the Query Log

In the Query Log, press Enter on a row to open the rule dialog. The action is inferred from the row: a blocked row offers Allow, an allowed row offers Deny. Hold rule keeps the rule for the Custom Lists you pick. Press v to review the held rules, then Apply writes them all in one change with one reload. Details are in Query Log.

CLI reference

warden custom-list accepts --offline to act on the config file without the daemon.

CommandWhat it does
warden custom-list listlist the declared custom lists
warden custom-list show <id>show one list
warden custom-list rules <id>show its rules, each with a row reference
warden custom-list create <id> [--display-name <name>]create the file and the declaration. --description and --into <path> are optional
warden custom-list set <id> ...change the display name or description
warden custom-list add <id> <domain> --allow|--denyadd one exact rule
warden custom-list add-rule <id> --rule <text>add one rule in full grammar
warden custom-list replace-rule <id> --row-ref <ref> --rule <text>replace one rule
warden custom-list remove-rule <id> --row-ref <ref>remove one rule
warden custom-list export <id>print the pack
warden custom-list delete <id> [--cascade-unmount]delete the declaration and the pack file
warden custom-list plan --operations <file>check a batch of changes and print its plan hash
warden custom-list apply --plan <file> --expect-plan-hash <hash>apply a checked plan
warden custom-list operation <operation_id>show the result of an earlier change
warden profile mount <profile> --custom-list <id>mount a list on a profile
warden profile unmount <profile> --custom-list <id>unmount it

Changing commands other than apply also take --expect-revision, --request-id and --json; apply takes only --plan, --expect-plan-hash and --json. Run warden custom-list <verb> --help for the exact flags in your binary.

In the TUI

Launch warden dashboard, then 4 for Filters. g t opens Custom Lists.

KeyPaneDoes
aListsCreate a custom list
Enter or eListsEdit the display name and description
d or DelListsRemove the list: its declaration and its pack file with every rule. Refused while a profile mounts it (unmount with m first); otherwise type the id to confirm
mListsMount or unmount on profiles. Space toggles, Enter applies, Esc cancels
aRulesAdd one rule
Enter or eRulesEdit the rule under the cursor
d or DelRulesRemove the rule under the cursor
ibothInspect the selected list or rule
vbothSwitch between the Lists and Rules panes
← or EscRulesReturn to the Lists pane

There is no rename: the id is the file name.

See also

↑↓ to navigate · Enter to open · Esc to close See all results