Profiles
A profile is the policy. Devices, groups, and subnets are how Warden decides which policy to apply.
What this is
A profile is a named bundle of filter behaviour: which subject areas to filter, which admin rules to layer on top, what shape the canned answer takes when a query is denied, and — optionally — static DNS records, name rewrites, SafeSearch, and an EDNS Client Subnet policy. Every entity that resolves to a filter decision — devices, groups, subnets, and the global default_profile — points at a profile by id.
A profile does not enumerate blocklists. It declares tags, and a list applies when its own tags intersect the tag set the query resolved to. That indirection is the single most important thing on this page; everything else is detail.
This page covers when to add a [profiles.<id>] entry, every dimension you can tune on it, the CLI / TUI / API surfaces that read and write profiles, and how a profile’s contents interact with the per-device overlay. For the bigger picture of how a query gets matched to a profile in the first place, see filtering process and decision precedence below.
When you reach for it
Create one profile per filter behaviour you want to express, not one per device. A typical setup ends up with three to five:
- A
defaultprofile for anything on the LAN, tagged for the privacy and security subject areas. - A
kidsprofile that adds the adult-content tag, denies a few specific domains, turns on SafeSearch, and switches the block response tonxdomainso iOS apps stop retrying. - A
kids-nightprofile (block_all = trueplus a narrow admin allowlist), bound by a schedule between 21:00 and 07:00. - An
iotprofile tagged for manufacturer telemetry, with a fewlocal_recordspinned so the smart-TV apps stop calling home. - A
workprofile carved out of the kids profile with classroom domains added back.
You don’t need a separate profile for one-off exceptions. A single domain that should be blocked or allowed for one person belongs in [[devices]].allow_rules / deny_rules — see devices.
You do need a separate profile whenever an exception must not bleed. Profiles are flat — there is no extends, no inheritance, no merging of two profiles. warden group allow and warden group deny write to the group’s underlying profile, so the rule reaches everything else bound to that profile too.
Schema
A profile is a TOML map keyed by id — unlike [[devices]], [[blocklists]], or [[subnets]], profiles use [profiles.<id>], not array-of-tables. The id (the part after the dot) is what every other entity references.
[profiles.kids]
display_name = "Kids profile"
tags = ["ads", "tracking", "malicious", "adult-content"]
block_response = "nxdomain"
blocked_ttl_secs = 300
admin_rules = ["kids-allow-classroom"]
block_all = false
safe_search = trueHow lists attach: tag intersection
Both sides declare subject areas. The engine intersects them.
effective_tags(device) = device.tags ∪ profile.tags ∪ group.tags (source has a [[devices]] row)
= profile.tags ∪ subnet.tags (anonymous source, no device row)
group.tags = union across every group the device belongs to
a blocklist applies <=> blocklist.tags ∩ effective_tags is non-emptyRead the two branches carefully — they are not cumulative. A source with an explicit [[devices]] row takes device.tags ∪ profile.tags ∪ group.tags and the subnet contributes nothing, even when the IP falls inside a configured CIDR. Subnet tags exist for sources Warden has never seen before.
Three consequences worth internalising:
- Either side empty means the list never applies. A profile with
tags = []filters no lists at all; so does a blocklist with no tags. The intersection short-circuits on emptiness before anything else. - Adding a list is a zero-touch operation on profiles. Tag a new feed
adsand every profile already asking foradspicks it up on the next reload. You never edit a profile to subscribe it. - A typo is not an error. Tags have no registry — they exist by being used.
tags = ["addult-content"]is a perfectly valid slug that intersects nothing. Warden emits a WARN and keeps running; see troubleshooting.
List state gates the intersection as a second step. A list whose fetch failed keeps protecting from its cached copy; a list with no cache contributes nothing. Source: src/profiles/profile.rs:448-478 (list_applies).
warden profile tag add|remove <id> <tag> writes profile.tags — idempotent, the slug is validated at the CLI boundary, and a real change triggers a hot reload. The IPC layer backs it: ProfileUpdatePatch carries a tags field, so the TUI profile editor’s tags chip picker sets them too. The supply side has warden blocklist tag add|remove, and the other demand sides have warden device tag, warden group tag, and warden subnet tag. Use warden tags list to see the live tag landscape before you type a slug.Full tag reference: tags. Slug grammar is ^[a-z][a-z0-9-]{0,31}$ — lowercase-letter-led, no trailing dash, 32 bytes max.
Dimensions
A profile is a vector across these independent axes. Every dimension has a sensible default and is optional except the id.
| Group | Dimension | TOML field | What it tunes |
|---|---|---|---|
| Identity | id | (map key) | Stable cross-reference. Every device, group, subnet, and schedule that wants this profile names this id. |
| Display name | display_name | Operator-facing label shown in the TUI and CLI listings. | |
| List selection | Subject-area tags | tags | The only lever on which blocklists apply. Intersected against each list’s own tags. |
| Custom rules | Admin-rule references | admin_rules | The only path for @@ allow, $important, and regex patterns — external blocklists are sandboxed away from those powers. |
| Wire-level response | Answer shape on a block | block_response | zero (default), nxdomain, refused, soa_nodata. See the table below. |
| TTL on the block answer | blocked_ttl_secs | Per-profile override; falls back to [server].default_blocked_ttl_secs. | |
| Mode | Deny-by-default switch | block_all | When true, every query is denied except those covered by an admin allow rule. |
| Static DNS | Per-profile records | local_records | A/AAAA/CNAME entries served before going upstream. Consulted before the global [local_dns] table. |
| Rewrites | Name-to-name redirects | rewrite_rules | Applied after the block check and before the upstream forward, so a rewrite cannot bypass filtering on the original name. |
| SafeSearch preset | safe_search | One bool; injects eight vendor-documented search-engine rewrites at resolve time. | |
| Upstream | ECS policy | [profiles.<id>.ecs] | Per-profile EDNS Client Subnet override. Gated by the [upstream.ecs].enabled master switch. |
| Schedule binding (external) | Schedules pointing at this profile | [[schedules]].profile | A schedule swaps a device’s or group’s profile to this one inside its window. |
| Resolver bindings (external) | Devices, groups, subnets, and [server].default_profile referencing this id | (other entities) | Profiles are referenced; they don’t reference back. Removing a referenced profile is refused. |
Things that look like profile dimensions but aren’t:
- Explicit blocklist subscriptions. Removed.
blocklistsandcategoriesare no longer fields; tags replaced both. - Profile inheritance. Profiles are flat — there is no
extends. Composition happens at the tag layer: a device-backed source unionsdevice ∪ profile ∪ group, a device-less source unionsprofile ∪ subnet. Device and subnet never contribute together. - Cache TTL is global (
[cache]) — only the block-response TTL is per-profile. - Upstream forwarder choice is global (
[[forwarding]]) — you can’t pin “kids uses Quad9, work uses Cloudflare”. ECS is the one upstream knob that is per-profile. - Per-profile rate limits — RRL is global (
[security]).
Field reference
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
<id> (map key) | string, [a-z0-9-], 1–64 bytes | yes | — | Stable cross-reference. |
display_name | string | no | "" | TUI / CLI label. |
tags | list of tag slugs, ^[a-z][a-z0-9-]{0,31}$ | no | [] | Subject areas. Intersected with each blocklist’s tags to decide applicability. |
admin_rules | list of admin-rule ids | no | [] | Operator-curated rules; the only place @@, $important, and regex are honoured. |
block_response | enum | no | inherits [server].default_block_response | zero | nxdomain | refused | soa_nodata. |
blocked_ttl_secs | u32 (seconds) | no | inherits [server].default_blocked_ttl_secs | TTL stamped on the canned block answer. |
block_all | bool | no | false | When true, denies every query except admin-allow matches. |
local_records | list of LocalDnsRecord | no | [] | Per-profile A/AAAA/CNAME, with optional ttl_secs and match_subdomains. Consulted before the global table. |
rewrite_rules | list of RewriteRule | no | [] | Name-to-name rewrites, applied post-filter and pre-forward. |
safe_search | bool | no | false | Injects the curated search-engine rewrite set. See SafeSearch. |
ecs | sub-table | no | inherits [upstream.ecs] | mode (off | coarse | subnet), plus source_prefix_v4 / source_prefix_v6 under subnet. Each field falls back independently. |
#[serde(deny_unknown_fields)] is on — typos fail at config load with unknown field. That includes the retired v1 keys: a profile carrying blocklists or categories is rejected, not migrated in place. Use warden migrate to convert a pre-tag config; it maps every kind = "deny" list to tags = ["uncategorized"] and every kind = "allow" list to tags = []. Source: src/config/schema/profile.rs:60-130.
Block response variants
| TOML value | Wire effect | When to use |
|---|---|---|
zero (default) | 0.0.0.0 for A, :: for AAAA, NOERROR. | Fast client give-up. Browsers stop retrying within milliseconds. The right default. |
nxdomain | RCODE=NXDOMAIN. | Apps treat the name as missing and cache that aggressively. iOS apps especially stop retrying. Some stub resolvers fall through to a secondary DNS — bypassable in that case. |
refused | RCODE=REFUSED. | Stub resolvers fall through to the next configured DNS. Useful only when Warden is one of several upstreams on purpose. Always used by safety paths regardless of profile setting. |
soa_nodata | NOERROR + empty answer + authority SOA. | RFC 2308 negative-cache friendly. Best for noisy queriers (tablets, IoT) that honour the SOA minimum TTL and stop re-asking. |
All four are settable from the CLI, plus clear to fall back to the server global.
Examples
A household default — privacy and security subject areas, fast give-up:
[profiles.default]
display_name = "Home default"
tags = ["ads", "tracking", "malicious"]
block_response = "zero"The lists that satisfy it live on the other side of the intersection:
[[blocklists]]
id = "oisd-basic"
url = "https://big.oisd.nl/domainswild"
kind = "deny"
tags = ["ads", "tracking"]
[[blocklists]]
id = "urlhaus"
url = "https://urlhaus.abuse.ch/downloads/hostfile/"
kind = "deny"
tags = ["malicious"]A kids profile — one extra tag, NXDOMAIN to short-circuit retries, SafeSearch on, one school allow on top:
[profiles.kids]
display_name = "Bambini"
tags = ["ads", "tracking", "malicious", "adult-content"]
admin_rules = ["kids-allow-classroom"]
block_response = "nxdomain"
blocked_ttl_secs = 300
safe_search = trueA kids-night profile bound to a schedule — deny everything except a narrow admin allowlist. The profile declares no tags at all: under block_all the list layer is irrelevant, because nothing reaches it. The local_records entry still resolves, because local records are answered before the filter runs.
[profiles.kids-night]
display_name = "Bambini, notte"
tags = []
block_all = true
admin_rules = ["kids-night-bedtime-audio"]
[[profiles.kids-night.local_records]]
name = "homework.lan"
type = "A"
value = "10.10.1.20"[[schedules]]
id = "kids-bedtime"
target_type = "group"
target_id = "kids"
profile = "kids-night"
days = ["mon", "tue", "wed", "thu", "sun"]
start = "21:00"
end = "07:00"An IoT profile that filters telemetry, suppresses ECS so the vendor CDN learns nothing about the LAN, and pins one local override:
[profiles.iot]
display_name = "IoT"
tags = ["telemetry", "malicious"]
block_response = "soa_nodata"
[profiles.iot.ecs]
mode = "off"
[[profiles.iot.local_records]]
name = "samsungcloudsolution.com"
type = "A"
value = "0.0.0.0"
match_subdomains = trueprofiles.d/*.toml (one file per persona — default.toml, kids.toml, iot.toml, work.toml). The master config.toml pulls them in via includes. warden profile allow and warden profile deny take --into <file> to route the write to the right file.CLI
The subcommand is warden profile (singular). Every mutating verb triggers a hot reload of the running daemon (atomic config swap, no restart). Read-only verbs work without the daemon.
| Command | What it does |
|---|---|
warden profile list | List configured profiles with a one-line summary. |
warden profile show <id> | Full detail dump of one profile. |
warden profile create <id> --display-name <n> | Create a profile. Refuses if the id already exists. |
warden profile update <id> --display-name <n> | Change the label. |
warden profile block-response <id> <variant> | Set the wire-level block answer. Variants: zero | nxdomain | refused | soa_nodata | clear. |
warden profile blocked-ttl <id> <secs> | Set the block-answer TTL. 0 clears it (inherit the server global). |
warden profile block-all <id> <true|false> | Toggle deny-by-default. |
warden profile admin-rule-add <id> <rule_id> | Reference an existing [[admin_rules]] row. Create the row first with warden rules. |
warden profile admin-rule-remove <id> <rule_id> | Drop the reference. The rule row itself stays. |
warden profile tag add|remove <id> <tag> [--into <file>] | Add or remove a subject-area tag — the writer for profile.tags. Idempotent; the slug is validated at the CLI boundary; a real change triggers a hot reload. |
warden profile allow <id> <domain> [--id <rule_id>] [--remove] [--into <file>] | Synthesise an `@@ |
warden profile deny <id> <domain> [...same flags] | Symmetric — synthesises ||domain^. |
warden profile ecs <id> --mode <m> [--prefix-v4 N] [--prefix-v6 N] | Set the per-profile ECS policy. Prefixes apply only under --mode subnet. |
warden profile ecs-clear <id> | Drop the [profiles.<id>.ecs] subtree so the profile inherits [upstream.ecs]. |
warden profile remove <id> | Delete the profile. Refused if any device, subnet, or schedule still references it. |
There is no verb for local_records or rewrite_rules — those are hand-edited TOML plus warden reload. tags, by contrast, has the dedicated warden profile tag verb above. There is no warden profile blocklists either; it was removed with the field it wrote to.
Common patterns:
# Tag a list so every profile asking for that subject area picks it up
sudo warden blocklist tag add oisd-basic ads
# Check the live tag landscape before typing a new slug
warden tags list
# Block a domain on the kids profile only
sudo warden profile deny kids tiktok.com
# Switch RCODE shape so iOS stubs stop falling through to a secondary
sudo warden profile block-response kids nxdomain
# Inspect what would happen for an IP, no daemon needed
warden resolve 10.10.1.107After every hot-reload-aware mutation, exactly one of four messages is printed — the same set used by every CLI write path:
daemon reloaded — change is livedaemon not running — change will take effect on next startnote: change landed on disk but no admin token is available to request a daemon reload. Run warden token generate or restart the daemon to activate.warning: change landed on disk but the daemon rejected the reload (<msg>). Check journalctl -u purge-warden and consider systemctl restart purge-warden.
Source: src/cli/mod.rs:536-630, src/cli/commands/profiles.rs.
TUI
There is a dedicated Profiles tab — the fourth leaf of the Network section, an offline-backed master/detail view over [profiles].
| Tab | Profile relevance | Useful keys |
|---|---|---|
| Profiles | Master list plus detail card. Add / Edit / Delete drive the ProfileCreate / ProfileUpdate / ProfileDelete IPC verbs directly. The edit modal covers display_name, block_response, blocked_ttl_secs, block_all, admin_rules, ecs, and a tags chip picker. | a add, e edit, d delete. |
| Tags | Every slug in use across blocklists, devices, profiles, and subnets, with usage counts. Create, rename, delete for non-system tags; uncategorized refuses both. | c create, R rename, d delete unused, / search. |
| Devices | Profile column per row. The detail card separates the device’s own tags from those inherited via the profile, and flags UNFILTERED. Group-by profile is one of the cycle options. | e edit, a add, G cycle group-by axis. |
| Lists | Each blocklist row shows which profiles reach it through tag intersection. | Enter edit, a add, B catalog picker, K toggle block/allow. |
| Local DNS | Two panels — the global table and the per-profile records. | o switch panels, n/N cycle profile, a add. |
| Rules | Per-profile and per-device admin rules. | f cycle filter. |
The Resolver inspector (s from any leaf) takes a source IP and shows the full chain — matched device, match level, active profile, active schedule. It’s the fastest way to answer “why does this client get that profile?”.
The edit modal’s tags chip picker writes profile.tags over IPC (ProfileUpdatePatch.tags), so you can change which lists a profile filters straight from the TUI — or with warden profile tag …. Source: src/tui/app.rs:117-149, src/tui/ui.rs:592-600, src/tui/profile_modal.rs:129-158, src/ipc/protocol.rs:407.
REST API
| Method | Path | What |
|---|---|---|
GET | /api/config | Full merged config including [profiles.*] as JSON. token_hash is redacted. |
GET | /api/whitelist | The default profile’s allow list, reconstructed from its admin_rules references whose rule text starts with @@. |
POST | /api/whitelist/add | Add an allow rule to the default profile. 409 if already present. |
DELETE | /api/whitelist/remove | Symmetric. 404 if absent. |
All /api/* routes are gated by Bearer token (Authorization: Bearer ps_<64hex>), constant-time-verified, with a 10-failure / 5-minute lockout. Each authenticated route carries a 30-second timeout.
There is no REST CRUD for arbitrary profiles. The whitelist endpoints are a compatibility shape over the default profile only; everything else goes through the CLI, the TUI, or a hand-edit plus reload.
IPC
Three profile verbs exist on the Unix socket, all in the Mutating tier (admin token required):
ProfileCreate { id, display_name }— refuses if the id exists.ProfileUpdate { id, patch }— the patch carriesdisplay_name,block_response,blocked_ttl_secs,block_all,admin_rules(add / remove deltas),ecs, andtags(a delta overProfile.tags).ProfileDelete { id }— refuses if any device, subnet, or schedule still references the id.
Hand-edited fields land on disk and the daemon picks them up via IpcCommand::Reload { token }, the same path every CLI mutation uses. DeviceUpdate and DevicePromote carry an optional profile field for rebinding a device. Source: src/api/handlers.rs:863-895, src/ipc/protocol.rs:267-300,407.
Decision precedence
A query goes through two ordered passes: choose the profile, then evaluate inside it.
Choosing the profile
Five levels, first match wins. The table gives the runtime evaluation order alongside the label warden resolve and the audit log print — the two do not run in the same sequence, which trips people up.
| Evaluated | Level label | Condition |
|---|---|---|
| 1st | schedule | A schedule window is active for the matched device or one of its groups. |
| 2nd | device-direct | The [[devices]] row sets profile = "...". |
| 3rd | group | The device’s highest-priority group. Same-priority-different-profile is a validator error, so the choice is never ambiguous. |
| 4th | subnet | Longest-prefix match against [[subnets]]. Only for sources with no [[devices]] row — or for a device downgraded by MAC enforcement. |
| 5th | global-default | [server].default_profile. If unset, the answer is REFUSED. |
The schedule-before-direct ordering is deliberate: an operator who writes a schedule for a device that also pins a profile is clearly asking the schedule to win inside its window, otherwise the schedule would do nothing. Tests pin this choice.
enforce_device_mac = true plus an ARP mismatch downgrades the device to the subnet level for that query — schedule, direct, and group are all skipped.
This is the same chain documented under devices and groups. Source: src/profiles/resolver.rs:440-520.
Evaluating inside the profile
Before any of this, the handler probes the profile’s local_records. A hit is answered straight away and bypasses the filter, the cache, and upstream entirely — it is not “allowed past” the filter, it never reaches it. That holds under block_all too, which is what keeps homework.lan reachable in the bedtime example above. Only A, AAAA, and CNAME participate; any other qtype falls through to the global [local_dns] table and then upstream. Source: src/dns/handler.rs:940-985.
Once that misses, the filter walks these stages. The first decision wins.
block_allshort-circuit. Ifblock_all = true: forward only when an admin allow rule matches (exact-domain set or a matching allow rule in the priority scan). Everything else is denied, attributed to the profile policy rather than to whichever rule was the proximate match. Allow-direction lists deliberately do not pierceblock_all— a sandboxed external allowlist must not weaken an explicit operator “deny everything”.- Admin-rule priority scan, four tiers, highest wins:
| Priority | Rule shape |
|---|---|
| 3 | $important allow — short-circuits everything below |
| 2 | $important deny |
| 1 | normal allow |
| 0 | normal deny |
- Unified subdomain walk, one byte pass probing every enabled set per dot position:
- exact-domain allow set hit → forward (outranks a tier-0 deny that already matched)
- exact-domain deny set hit → block (only consulted when no rule produced a result)
- list allow-direction mask hit → forward
- list deny-direction mask hit → block
- Default forward — anything that matched nothing goes upstream.
After the verdict and before the forward, rewrite_rules and the SafeSearch presets apply. That ordering is what stops a rewrite from being used to escape a blocklist match on the original name.
Allow beats deny at equal strength: $important allow > $important deny > normal allow > list-level allow > list-level deny ~ normal deny. Source: src/filter/engine.rs:401-560, src/filter/evaluator.rs:22-83.
Device overlay (per-query, on top of the resolved profile)
A device may carry allow_rules and deny_rules of its own, plus override_profile_deny: bool. Across the nine combinations of (profile decision × device decision × override flag), one row is load-bearing:
Profile denies, device allows, override_profile_deny = true → the query is allowed, attributed to RuleSource::Device(<id>) [OVERRIDE].
When override_profile_deny = false the same situation falls back to block — defensive on drift; the CLI and TUI also refuse to write a contradicting allow without flipping the flag. Per-device deny is always additive: it can block what the profile would allow, but the profile cannot override it.
The device cannot override block_response, blocked_ttl_secs, block_all, local_records, or rewrite_rules — those come from the resolved profile only. What the device can contribute is tags, which union into the effective set before list selection runs. Source: src/profiles/profile.rs:629-741.
Security notes
- Only
admin_ruleshonour@@,$important, and regex. External blocklists are sandboxed — they can deny but cannot override or use anchored / regex patterns. This keeps a hostile or compromised list source from punching through your overrides. local_recordsare unfiltered by construction. A profile-scoped record is served before the filter, the cache, and upstream — no blocklist, no admin rule, and noblock_allapplies to it. Treat the list as a small, deliberate allowlist that also happens to answer, not as a convenience for pinning names you still want checked.- A profile with no tags filters nothing. There is no implicit default subscription.
tags = []on a profile whose devices also carry no tags means every query is forwarded — the validator warns, it does not refuse. Check the WARN lines after any reload that touched tags. block_unmapped_clientsis gone. Express “deny unmapped clients” by leaving[server].default_profileunset, so the chain returnsREFUSEDat the global-default level. The legacy key is rejected asunknown field.- The query log records
client_ip,client_name, queried domain, RCODE, and the resolved profile name. It does not record MAC. - Profile lifecycle is unaudited — tag edits through the verb are not. Create, update, remove, and ECS changes are not written to the audit log. What is audited:
rule.add/rule.remove/rule.undo(whichwarden profile allowanddenygo through, carryingscope,target_id, and the effective profile),profile.tag_add/profile.tag_remove(fromwarden profile tag), plus blocklist auto-promotion and the zero-intersection tag warnings. To trace “who changed the kids profile’s block mode?”, use the file’s git history; tag changes made through the verb are on the audit trail. warden audit tail [-n N]prints recent audit lines as tab-aligned rows: timestamp, event tag, uid, ok / rejected, detail.- MAC enforcement is ergonomic, not cryptographic. A determined attacker on the LAN can spoof. See the threat model.
Troubleshooting
Nothing is being blocked, but the profile looks right. Almost always an empty intersection. The daemon warns at reload on the audit channel:
profile "kids" contributes no tags — devices using it rely entirely on device-level tags profile "kids" has tags but none match any enabled deny list blocklist "oisd-basic" tags match no device, profile, or subnet — the list applies to nothing device "anna-iphone" has tags but none match any enabled deny list — no list filtering applies
All four are WARN-only and never block the reload — a tag staged for a list you haven’t added yet is legitimate. Read them after every tag change:
journalctl -u purge-warden | grep -E 'no tags|match no|matches nothing'
warden tags listA tag with a usage count of 1 in warden tags list is nearly always a typo.
A reload fails with unknown field: blocklists. The profile still uses the retired v1 schema. blocklists and categories were replaced by tags; deny_unknown_fields rejects them rather than ignoring them. Run warden migrate on a pre-tag config, or convert by hand — each old blocklist id becomes a tag shared by the list and the profiles that used to subscribe to it.
A reload fails with “profile X not found”. Some entity references a profile that no longer exists — usually a typo, sometimes a deleted profile. The validator refuses the reload and the daemon stays on the previous good config. Run warden config lint, or grep across the config tree:
grep -RnE 'profile\s*=\s*"[^"]+"' /etc/purge-wardenwarden profile remove is refused. Some device, subnet, or schedule still references it. The error names them — reassign those first. The same applies if [server].default_profile points at the profile being deleted.
[server].default_profile is unset and unmapped clients fail. The last level returns REFUSED for everything. Symptom: a guest laptop gets RCODE=REFUSED for every name. Either set default_profile to an explicit id, or accept that as the policy — it does keep an open resolver from forwarding for strangers.
A device with override_profile_deny = true keeps slipping past the kids profile. That’s what the flag is for, but check whether you meant to leave it on:
warden device show anna-iphone | grep -E 'allow|override'A device is resolving to the right profile but skipping every list. Check unfiltered. A device with unfiltered = true must have tags = [] (the validator enforces the mutex) and short-circuits to an empty effective set — DNS resolution, query logging, and stats all keep working, so the device looks healthy while filtering nothing.
For more, see troubleshooting.
See also
- Tags — the slug layer that decides which lists a profile filters.
- Blocklists — the other side of the intersection.
- Devices — pin a host to a profile, layer per-device tags and overlays.
- Groups — assign one profile to many devices at once.
- Subnets — fallback profile and tags for sources with no device row.
- Schedules — time-based profile swaps.
- Admin rules — what
admin_rulesreferences; the only place@@/$important/ regex live. - SafeSearch — what the
safe_searchbool switches on. - Local DNS records — global table that
local_recordsshadows per profile. - TOML reference — every field, every constraint.
- CLI reference — the full
warden profile …surface.