Devices
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/quietshortcuts (“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.
| Signature | TOML field | Format | Caveats |
|---|---|---|---|
| Static IP | ip | IPv4 (192.168.1.50) or IPv6 (fe80::1); single value | Device loses identity if the lease is reassigned. Pin the lease on your router, or pair with mac. |
| Primary MAC | mac | One 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 aliases | mac_aliases | List 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 shows | What Warden does |
|---|---|
The device’s mac or one of its mac_aliases | The device keeps its profile. No warning. |
A MAC that another configured device owns, as its mac or as an alias | A 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 MAC | The device is downgraded: its schedule, direct profile and groups are skipped, and the subnet or default_profile applies. Audit warning. |
| No entry for the address | A 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 grouporwarden subnetchange 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
macis 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
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
id | string, [a-z0-9-], 1–64 bytes, no leading or trailing - | yes | — | Stable cross-reference key used by groups, schedules, IPC, stats. |
display_name | string | yes | — | Human-facing name shown in the TUI, query log, audit entries. |
ip | IPv4 or IPv6 | one of ip/mac required | — | Static pin. Either ip or mac (or both) must be set. |
mac | one exact MAC, XX:XX:XX:XX:XX:XX (no wildcard, prefix or range) | one of ip/mac required | — | Primary MAC. Verified against ARP. |
mac_aliases | list 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. |
profile | profile id | no | unset | Direct profile assignment. Wins over groups, subnets, default. |
groups | list of group ids | no | [] | Memberships. Highest-priority group’s profile applies if profile is unset. |
owner | string, ≤64 bytes | no | unset | Who owns the device (e.g. "Alice"). TUI group-by axis. |
device_type | string, ≤64 bytes | no | unset | Free-form label ("iPhone personale", "Smart TV"). Legacy alias device still loads. |
department | string, ≤64 bytes | no | unset | Logical grouping ("family", "home-office"). TUI group-by axis. |
notes | string, ≤256 bytes | no | unset | Operator-only annotation. Not returned by the REST API. |
unfiltered | bool | no | false | Skip filtering for this device. Resolution, cache, query log and stats stay on. |
network_name | DNS name | no | unset | Bare 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_wildcard | bool | no | false | Every 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:
[[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:
[[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:
[[devices]]
id = "kitchen-bulb"
display_name = "Kitchen bulb"
mac = "FF:EE:DD:CC:BB:AA"
groups = ["iot-lite"]
device_type = "Smart bulb"
department = "iot"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.
| Command | What it does |
|---|---|
warden device list | Print configured devices from the on-disk config. |
warden device list --live | Live 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 30m | One-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:
# 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 --liveSource: 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 mirrorwarden 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.Enteron an unmapped row — promote it: the Add modal opens pre-filled with the observed IP + MAC; you supplyidanddisplay_name.G— cycle group-by axis: none →owner→department→profile.↑/↓— navigate; group headers are skipped.Home/End/PgUp/PgDnalso move.i— open the details;Esccloses them.f— focus the Subnet filter chip./filters the list by subnet (CIDR);Rclears 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:
{
"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.
- Active schedule. A
[[schedules]]entry whose time window covers now targets this device or one of its groups. - Direct device profile. Source IP matches a device’s
ip(or MAC matches via ARP), and the device setsprofile = "...". - Group profile. Device has no direct
profilebut belongs to one or more[[groups]]. The highest-priority group wins; same-priority conflicts are validator errors. - 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. - 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.tomlanddevices.d/*.toml. They are not hashed. - The query log records
client_ipandclient_name(the device’sid). 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:
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: kidsIf 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:
sudo warden device set alice-iphone mac 22:33:44:55:66:77A 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:
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 aliceA 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):
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 vmsOr in 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:
sudo warden subnet add vm-range --display-name "VM range" --profile lab --cidrs 192.168.1.192/26A 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.