EN

Devices

Under review — wording may still change.

One person, one phone, one filter — devices are how Warden knows whose query it just answered.

What this is

A device is a single endpoint on your LAN — a phone, laptop, console, smart TV, IoT bulb — pinned to Warden by its IP address, its MAC address, or both. Each device can carry its own profile, or piggyback on a group or subnet default.

This page covers when to add a device entry, every identifying and classifying field, and the CLI / TUI / API surfaces that read and write them. For the wider picture of how queries are resolved end-to-end, see the filtering process.

When you reach for it

Add a [[devices]] entry when you need one of these:

  • A profile that follows a specific person across MAC randomization (kids’ phones, teenager laptops).
  • A per-device exception — for example, the “kids” profile blocks YouTube but the eldest’s iPad needs it for school. Give that device its own profile with a Custom List.
  • A name in the TUI / query log that’s friendlier than 192.168.1.107.
  • A target for the block / quiet shortcuts (“ground the iPad until 21:00”).

You don’t need a device entry for every host. Unmapped hosts fall through to the subnet ([[subnets]]) or the global default_profile, which is exactly the right behaviour for a guest VLAN or a fleet of identical IoT bulbs.

Schema

A [[devices]] entry has these parts: identity (how Warden matches the device), classification (how it appears in the TUI and CLI), and the profile it points at. A device carries no filter rules of its own.

Identity dimensions

Warden matches a device by the source IP of a DNS query. The source IP identifies the device when it equals the device’s ip, or when Warden’s snapshot of the ARP table shows that IP at the device’s mac or at one of its mac_aliases. With [server].enforce_device_mac on (the default), an ip alone does not identify a device: Warden needs a MAC it can check (see the MAC check). A device with an ip and no mac keeps its profile only while the ARP table shows one of its mac_aliases at that address; otherwise its queries fall to the subnet or default_profile. IP alone works only with enforce_device_mac = false. Configuring both ip and mac lets Warden find the device again after a DHCP rebind, but not at once: see when a new lease is picked up.

Every MAC field takes one exact MAC: six pairs of hex digits separated by colons, like AA:BB:CC:DD:EE:FF. Wildcards, prefixes and ranges (02:00:00:00:00:*, 02:00:00) are not accepted, and a config that contains one is refused at load. To cover many virtual machines, see many virtual machines or containers.

SignatureTOML fieldFormatCaveats
Static IPipIPv4 (192.168.1.50) or IPv6 (fe80::1); single valueDevice loses identity if the lease is reassigned. Pin the lease on your router, or pair with mac.
Primary MACmacOne exact MAC, AA:BB:CC:DD:EE:FF (case-insensitive). No wildcard, prefix or range.Resolved through the ARP snapshot (see below). Incomplete entries are skipped. iOS Private Wi-Fi and Android randomize per-network — use mac_aliases for those.
MAC aliasesmac_aliasesList of exact MACs, each AA:BB:CC:DD:EE:FF. No wildcard, prefix or range.Any one match can identify the device. Useful for randomizing phones; capture each new MAC from the TUI’s unmapped list. An alias passes the MAC check like the primary mac (see below).

The MAC check

With [server].enforce_device_mac = true (the default, formerly enforce_client_mac), Warden does not trust a source address on its own. When the source IP of a query maps to a device (its ip pin, or an address last seen at one of its MACs), Warden looks at the MAC that its ARP snapshot shows at that address:

The ARP snapshot showsWhat Warden does
The device’s mac or one of its mac_aliasesThe device keeps its profile. No warning.
A MAC that another configured device owns, as its mac or as an aliasA MAC identifies its own device: Warden resolves the query as that other device, with its own schedule, profile and groups, and logs an audit warning.
Any other MACThe device is downgraded: its schedule, direct profile and groups are skipped, and the subnet or default_profile applies. Audit warning.
No entry for the addressA device with a mac keeps its profile: a missing entry is not a mismatch. A device with no mac is downgraded.

Before 0.44.5 the check compared with mac only: a device seen at one of its alias MACs was downgraded, and so was a device seen at an address that another device pins. Both now keep or find their own profile. The audit lines are listed on the subnets page.

The check is ergonomic, not cryptographic: a determined attacker with physical access can spoof any MAC, but it stops a curious teenager from changing their static IP to escape the kids profile.

When a new lease is picked up

After a DHCP rebind, the device’s new address matches no ip pin. Warden then looks the address up in a snapshot of the system ARP table and finds the device by its mac. The snapshot is not live. Warden re-reads the ARP table only:

  • when the daemon starts;
  • when the configuration is loaded or reloaded (every warden device, warden group or warden subnet change that hot-reloads the daemon counts);
  • when a client asks for the device list: the TUI while it shows the Dashboard, Devices or Subnets tab (every 2 to 5 s), or warden device list --live;
  • every 60 s while the configuration has at least one schedule: the schedule check rebuilds the device index from a fresh ARP read, so a device with a mac is usually found again within about a minute.

Without a schedule, nothing refreshes it on a timer. Until one of these happens, queries from the new address are not matched to the device and fall to the subnet or default_profile. A device with a mac and no ip is found only this way.

Field reference

FieldTypeRequiredDefaultPurpose
idstring, [a-z0-9-], 1–64 bytes, no leading or trailing -yes—Stable cross-reference key used by groups, schedules, IPC, stats.
display_namestringyes—Human-facing name shown in the TUI, query log, audit entries.
ipIPv4 or IPv6one of ip/mac required—Static pin. Either ip or mac (or both) must be set.
macone exact MAC, XX:XX:XX:XX:XX:XX (no wildcard, prefix or range)one of ip/mac required—Primary MAC. Verified against ARP.
mac_aliaseslist of exact MACs (no wildcard, prefix or range)no[]Additional MACs for randomizing phones. Each one identifies the device and passes the MAC check like mac.
profileprofile idnounsetDirect profile assignment. Wins over groups, subnets, default.
groupslist of group idsno[]Memberships. Highest-priority group’s profile applies if profile is unset.
ownerstring, ≤64 bytesnounsetWho owns the device (e.g. "Alice"). TUI group-by axis.
device_typestring, ≤64 bytesnounsetFree-form label ("iPhone personale", "Smart TV"). Legacy alias device still loads.
departmentstring, ≤64 bytesnounsetLogical grouping ("family", "home-office"). TUI group-by axis.
notesstring, ≤256 bytesnounsetOperator-only annotation. Not returned by the REST API.
unfilteredboolnofalseSkip filtering for this device. Resolution, cache, query log and stats stay on.
network_nameDNS namenounsetBare name that resolves to the device’s live IP (the ip pin, else the ARP address of its mac). Validated as a DNS name; a clash with another device or a local DNS record is refused.
network_name_wildcardboolnofalseEvery subdomain of network_name resolves too. Refused without network_name.

Schema 5 has no allow_rules, deny_rules or override_profile_deny on a device, and a config that contains them is refused. A host upgraded from 0.41 has its device rules moved into a Custom List and a copy of the profile; see custom rules.

Examples

A minimal entry — pin one host, point it at a profile:

/etc/purge-warden/devices.d/family.toml
toml
[[devices]]
id = "kids-tablet"
display_name = "Kids' iPad"
ip = "192.168.1.50"
mac = "11:22:33:44:55:66"
profile = "kids"

A device with alias MACs for a randomizing phone, classified for the TUI. The phone keeps its profile at any of the three MACs:

/etc/purge-warden/devices.d/family.toml
toml
[[devices]]
id = "alice-iphone"
display_name = "Alice's iPhone"
ip = "192.168.1.107"
mac = "AA:BB:CC:DD:EE:FF"
mac_aliases = [
  "22:33:44:55:66:77",
  "33:44:55:66:77:88",
]
profile = "kids"
owner = "Alice"
device_type = "iPhone"
department = "family"

A device with no direct profile — its profile comes from the highest-priority group it belongs to:

/etc/purge-warden/devices.d/iot.toml
toml
[[devices]]
id = "kitchen-bulb"
display_name = "Kitchen bulb"
mac = "FF:EE:DD:CC:BB:AA"
groups = ["iot-lite"]
device_type = "Smart bulb"
department = "iot"
Tip
For more than ~20 devices, split the config across devices.d/*.toml files (one per family member, room, or VLAN). The master config.toml pulls them in via includes. Diffs stay readable, and warden device add auto-routes new entries to the right file.

CLI

Every mutating subcommand triggers a hot reload of the running daemon (atomic config swap, no restart). Read-only subcommands (list, show) work without the daemon.

CommandWhat it does
warden device listPrint configured devices from the on-disk config.
warden device list --liveLive per-device stats from the running daemon (queries, blocked %, last seen, resolved profile).
warden device show <id>Render one device entry as key/value pairs.
warden device add <id> --ip <ip> [--mac <m>] [--profile <p>] [--owner <o>] [--device-type <t>] [--department <d>] [--display-name <n>] [--into <file>]Create a new device. At least one of --ip / --mac is required.
warden device set <id> <field> <value>Mutate a single field. Supported: ip, mac, profile, display_name, owner, device (or device_type), department, notes, groups, network_name, network_name_wildcard. Use none to clear a nullable field.
warden device remove <id>Delete a device. Refused while a group lists it or a schedule targets it.
warden device block <id>Set the device’s profile to blocked (auto-created if missing).
warden device unblock <id> [--profile <p>]Restore the device to a profile (defaults to default).
warden device quiet <id> --for 30mOne-shot temporary block via a self-expiring schedule. Accepts --for 1h30m or --until <RFC 3339>.

warden device allow, warden device deny and warden device rules prune are retired in 0.44.0. They fail without writing. See custom rules.

Common patterns:

bash
# pin a new tablet
sudo warden device add kids-tablet \
  --ip 192.168.1.50 --mac 11:22:33:44:55:66 \
  --profile kids --owner Alice --device-type iPad

# bedtime override
sudo warden device quiet kids-tablet --for 12h

# audit live activity
warden device list --live

Source: src/cli/commands/devices.rs, src/cli/mod.rs → DeviceAction.

TUI

Press 3 (or g v) in the TUI to jump to the Devices tab. The view is master/detail: configured devices and live-observed unmapped hosts merged into one list, with a detail card on the right showing vendor, cache stats, MAC aliases, and effective profile.

For the tab itself — every column, the group-by axes, and what the detail card tells you — see the Devices tab. What follows is the short version.

Key bindings on the Devices tab:

  • a — open the Add modal (form fields mirror warden device add).
  • e — Edit the focused configured device.
  • d — Delete with y/n confirm. Refused while a group lists the device or a schedule targets it.
  • Enter on an unmapped row — promote it: the Add modal opens pre-filled with the observed IP + MAC; you supply id and display_name.
  • G — cycle group-by axis: none → owner → department → profile.
  • ↑/↓ — navigate; group headers are skipped. Home/End/PgUp/PgDn also move.
  • i — open the details; Esc closes them.
  • f — focus the Subnet filter chip. / filters the list by subnet (CIDR); R clears the filter.

The list polls the daemon every 5 s via the IPC GetAllDevices command. Source: src/tui/tabs/devices.rs.

REST API

Live device stats are exposed over the local APIs.

GET /api/devices — Bearer-token authenticated. Returns a JSON object whose devices array holds one entry per device that has hit the daemon since the last snapshot:

json
{
  "devices": [
    {
      "name": "kids-tablet",
      "ip": "192.168.1.50",
      "queries": 4321,
      "blocked": 187,
      "blocked_pct": 4.3,
      "cache_hits": 2890,
      "profile": "kids",
      "last_seen": 1714820400
    }
  ]
}

The legacy path /api/clients is deprecated; responses carry Deprecation and Sunset headers pointing to /api/devices.

IPC

Over the Unix socket: IpcCommand::DeviceStats { token: None } returns IpcResponse::DeviceList { clients: Vec<DeviceStatEntry> }. Same fields as REST. It needs the admin token. Source: src/api/handlers.rs, src/ipc/protocol.rs.

Decision precedence

When a query arrives, Warden walks five levels in this order. The first match wins.

  1. Active schedule. A [[schedules]] entry whose time window covers now targets this device or one of its groups.
  2. Direct device profile. Source IP matches a device’s ip (or MAC matches via ARP), and the device sets profile = "...".
  3. Group profile. Device has no direct profile but belongs to one or more [[groups]]. The highest-priority group wins; same-priority conflicts are validator errors.
  4. Subnet longest-prefix match. No earlier level matched — no device row, a device with no profile, group or schedule, or a device the MAC check dropped — and the source IP falls inside a [[subnets]] CIDR. Longest prefix wins.
  5. Global fallback. [server].default_profile. If unset, unmapped queries are answered with REFUSED.

An active schedule is checked before the device’s own profile, so it wins inside its window. Without that order, a schedule on a device that already has a profile would do nothing. A schedule that targets the device wins over a schedule that targets one of its groups.

A device has no rules of its own. Once the profile is chosen, only that profile’s lists and Custom Lists decide the query.

If enforce_device_mac = true and ARP shows a MAC at the source address that is neither the device’s mac nor one of its mac_aliases, the device is downgraded to the subnet level for that query. The schedule, direct-profile, and group slots are skipped. The same happens, while enforce_device_mac is on (it is on by default), to a device that has an ip and no mac, unless the ARP table shows one of its mac_aliases at its address. If the MAC belongs to another configured device, the query resolves as that device instead, through all five levels. See the MAC check. Source: src/profiles/resolver.rs → ProfileResolver::resolve() (the MAC check, then schedule, direct profile and group), build_resolver_map() (the MACs and addresses that identify each device), compute_active_schedules() (device schedule before group schedule).

Security notes

  • MAC addresses are stored in plaintext in config.toml and devices.d/*.toml. They are not hashed.
  • The query log records client_ip and client_name (the device’s id). It does not record MAC addresses.
  • The audit log records device CRUD events — adds, removes, field changes — with the invoking uid, action and target id. It does not record MACs.
  • MAC enforcement is ergonomic, not cryptographic. It stops casual IP-changing, not a determined attacker with physical access. See the threat model for the precise guarantee.
  • Notes (notes = "...") stay on the host. The REST API does not return them. The local IPC socket carries them to the TUI.

Troubleshooting

The wrong profile is being applied. Ask the resolver directly:

bash
warden resolve 192.168.1.50
# Source IP:      192.168.1.50
# Matched device: kids-tablet (Kids' iPad)
# Match level:    1 (direct device profile)
# Active profile: kids

If the level shown is not the one you expected, walk up the chain — was the MAC randomized? did the lease move? is enforce_device_mac triggering a downgrade? does another device’s MAC now answer at that address?

A phone that worked yesterday now drops to the subnet profile. Almost always MAC randomization. Check the TUI Devices tab for the unmapped row sharing the same IP — it shows the new MAC. Add that MAC to the device’s mac_aliases, in the TUI Edit form (e) or in the TOML, then run sudo warden reload. An alias identifies the phone and passes the MAC check, so the old MAC keeps working too. warden device set cannot change mac_aliases; it can only replace the primary mac:

bash
sudo warden device set alice-iphone mac 22:33:44:55:66:77

A device pinned by IP only loses its profile after a router reboot. DHCP reassigned the lease. Either pin the lease on the router, or add mac to the entry so identity follows the hardware. The mac does not take effect the instant the lease changes: Warden finds the device again only after it next reads the ARP table (see when a new lease is picked up). A warden reload, or opening the TUI Devices tab, forces that read.

The journal says observed MAC belongs to another device. The router handed an address that is still pinned to device A by ip to device B, which Warden knows by its mac or an alias. Warden sees B’s MAC in the ARP table at that address and, with enforce_device_mac on, resolves the query as B, with B’s own profile, so B is not demoted. The warning tells you that A’s pin is stale. With enforce_device_mac = false there is no MAC check, and B gets A’s profile for as long as the pin points at that address. When you move a device to another address on the router, update its pin at once: sudo warden device set <id> ip <new-address>.

warden device allow or deny fails with “Legacy allow/deny rules and device overlays are retired”. That is expected since 0.44.0. For a one-device exception, give the device a profile of its own and mount a Custom List on it:

bash
sudo warden profile add alice --display-name "Alice"
sudo warden custom-list create alice-allow --display-name "Alice allow"
sudo warden custom-list add alice-allow classroom.example.com --allow
sudo warden profile mount alice --custom-list alice-allow
sudo warden device set alice-iphone profile alice

A config that still has allow_rules, deny_rules or override_profile_deny on a device is refused at load.

A dual-stack host gets the wrong profile over IPv6. The current schema stores one ip per device. If your host has separate v4 and v6 addresses, pin the v6 via a [[subnets]] entry covering its IPv6 CIDR. A mac does not help here: Warden reads only the IPv4 ARP table, so a MAC never identifies an IPv6 source.

Many virtual machines or containers need the same profile

Warden has no MAC pattern: mac and mac_aliases take one exact MAC each, so 02:00:00:00:00:* is refused. Two options work today.

Option A: one device per VM, matched by MAC, all in one group. Give each VM its own MAC (a locally administered one such as 02:00:00:00:00:01 works), declare it with --mac only, and let the group carry the profile. Do not set a profile on the devices; a direct profile would win over the group. Create the group first (the lab profile must already exist):

bash
sudo warden group add vms --display-name "Virtual machines" --profile lab
sudo warden device add vm-01 --mac 02:00:00:00:00:01 --groups vms
sudo warden device add vm-02 --mac 02:00:00:00:00:02 --groups vms

Or in TOML:

toml
[[groups]]
id = "vms"
display_name = "Virtual machines"
profile = "lab"

[[devices]]
id = "vm-01"
display_name = "vm-01"
mac = "02:00:00:00:00:01"
groups = ["vms"]

Membership can be written on either side: groups on the device or devices on the group. Warden joins both. warden device add --groups writes the device side. Each new VM still needs its own entry, and a VM is found only after Warden next reads the ARP table (see when a new lease is picked up). Do not pin a VM by ip alone: under the default enforce_device_mac that device is ignored.

Option B: give the VMs their own address range and add a subnet. Reserve a block of addresses for the VMs on your DHCP server, for example 192.168.1.192/26, then:

bash
sudo warden subnet add vm-range --display-name "VM range" --profile lab --cidrs 192.168.1.192/26

A subnet matches by source IP only. It does not look at MACs, and it needs no ARP read, so a new lease in the range is covered at once. The cost: any host that takes an address in that range gets the profile, and --profile should not be less restrictive than your default_profile. The longest prefix wins, so a /26 beats a /24 subnet that also covers it. A device entry for the same address still takes precedence. See subnets.

For more, see troubleshooting.

See also

  • Profiles — what a profile contains (blocklists, rules, block response).
  • Groups — assign one profile to many devices at once.
  • Subnets — fallbacks for unmapped hosts.
  • Schedules — time-based profile swaps.
  • Custom rules — where rules live now; a device has none of its own.
  • CLI reference — the full warden device … surface.
↑↓ to navigate · Enter to open · Esc to close See all results