EN

SafeSearch

Work in progress — content may be incomplete.

One bool on a profile, eight strict rewrites injected at resolve time. The kids’ tablet now sees Google through forcesafesearch.google.com, not because the browser asked nicely.

What this is

SafeSearch is a per-profile switch that forces Strict-mode SafeSearch on Google, YouTube, Bing, and DuckDuckGo for every device assigned to that profile — at the DNS layer, with no client cooperation. Setting safe_search = true on a profile makes Warden inject eight preset rewrite rules into that profile’s resolved rewrite table at startup, SIGHUP, and the 60-second schedule recompute tick. Every query for www.google.com from a device on the profile resolves as if the client had asked for forcesafesearch.google.com.

The mechanism is the same ProfileRewriteRules engine that powers operator-authored [[profiles.<id>.rewrite_rules]]. SafeSearch just happens to seed it with eight curated entries that the operator never has to maintain.

When you reach for it

  • You manage a kids profile and want the household’s Chromebooks and tablets to stop returning explicit image results from Google Images and Bing, without installing client-side extensions.
  • You operate a classroom or small library Pi running Warden and want SafeSearch enforced even on guest devices that won’t honour an operator-set browser preference.
  • You want YouTube’s Restricted Mode enforced for kids, recognising that the protection is Strict only (restrict.youtube.com) — there is no Moderate tier today.

You don’t reach for SafeSearch when:

  • You want to block search engines entirely. Add google.com, bing.com, youtube.com to the profile’s deny_rules or to a blocklist — blocklists evaluate before SafeSearch rewrites, so the deny wins.
  • You want a per-device override of a profile that already has SafeSearch on. SafeSearch follows the profile; if a device is on the kids profile, SafeSearch is on for that device. To exempt a device, give it a different profile.

How it works

SafeSearch is not a separate filter stage. It plugs into the existing rewrite engine through two seams:

  1. At profile resolve time (startup, warden reload, every 60s when schedules recompute), ResolvedProfile::build_v1 looks at Profile.safe_search. When true, it calls safesearch::populate() on the profile’s rewrite-rule table, which appends the eight presets unless an operator-authored entry already claims the same from host.
  2. On the hot path, each query walks: filter (allow / deny / blocklist) → rewrite → cache lookup → upstream. SafeSearch lives in the rewrite step, after the filter, before the cache key is computed. So:
    • A blocklist entry for www.youtube.com wins over SafeSearch — the query is blocked, never rewritten.
    • A cache hit for forcesafesearch.google.com is reused across every client on the same profile.
    • The DNS response sent back to the client still carries the original qname; only the upstream lookup is swapped. The client never sees the rewrite.
The rewrite is silent
The user sees www.google.com in their browser and in dig answers. Warden resolves forcesafesearch.google.com upstream and stamps the answer back under the original name. This is by design — no broken TLS, no certificate mismatches, no client-visible side effects.

Sources: src/profiles/safesearch.rs:1-234, src/profiles/profile.rs:486-489, src/dns/handler.rs:894-903, src/dns/rewrite.rs:1-90.

Providers covered

Every preset is an exact match (match_subdomains = false). The vendor-controlled hosts on the right perform the actual SafeSearch lock at their end.

Original qnameRewritten toTier
google.comforcesafesearch.google.comStrict
www.google.comforcesafesearch.google.comStrict
www.youtube.comrestrict.youtube.comStrict
m.youtube.comrestrict.youtube.comStrict
www.youtube-nocookie.comrestrict.youtube.comStrict
www.bing.comstrict.bing.comStrict
edgeservices.bing.comstrict.bing.comStrict
duckduckgo.comsafe.duckduckgo.comStrict

What’s not covered:

  • Bare apex youtube.com and bing.com. Only the www. hosts — plus m.youtube.com, www.youtube-nocookie.com, and edgeservices.bing.com — are rewritten. The bare apex names are deliberately left alone: Google and Microsoft document the SafeSearch lock only for those specific hostnames, and pointing an undocumented apex at the SafeSearch host risks breaking the site. Google’s google.com apex is the one documented exception and stays covered. In practice this is rarely a hole — browsers and the search apps resolve the www. host — but a client that asks for the bare apex gets a normal answer.
  • YouTube Moderate tier (restrictmoderate.youtube.com). Deferred — Strict (restrict.youtube.com) is the only YouTube option today.
  • YouTube API surface (youtubei.googleapis.com, youtube.googleapis.com). A child watching the official YouTube Kids app uses the in-app content filter, not a web hostname, so DNS-layer enforcement doesn’t reach it.
  • Regional Google domains (google.co.uk, google.it, etc.). If a client speaks google.co.uk explicitly, the rewrite list will not match. Subdomain-walking presets were deliberately skipped to keep the table predictable; add explicit per-profile rewrites if you need country-specific coverage.

Source: src/profiles/safesearch.rs:80-113.

Schema

SafeSearch is a single boolean field on the profile:

FieldTypeRequiredDefaultPurpose
safe_searchboolnofalseWhen true, the eight preset rewrites are injected at resolve time.

There is no [safesearch] table. There are no per-provider toggles. The whole point of the feature is that it’s one switch.

Source: src/config/schema/profile.rs:123-124.

Examples

The smallest possible kids profile with SafeSearch on:

/etc/purge-warden/profiles.d/kids.toml
toml
[profiles.kids]
display_name = "Kids"
safe_search  = true

A full kids profile that combines SafeSearch with content blocklists — the two stack:

/etc/purge-warden/profiles.d/kids.toml
toml
[profiles.kids]
display_name     = "Kids"
safe_search      = true
categories       = ["adult-content"]
blocklists       = ["security-malicious"]
block_response   = "nxdomain"
blocked_ttl_secs = 300

Override the preset for one specific host while keeping the other seven — operator-authored rewrites take precedence:

/etc/purge-warden/profiles.d/kids.toml
toml
[profiles.kids]
display_name = "Kids"
safe_search  = true

# Redirect www.google.com to the household intranet instead of forcesafesearch.
# The remaining seven SafeSearch presets still apply.
[[profiles.kids.rewrite_rules]]
from             = "www.google.com"
to               = "intranet.local"
match_subdomains = false

Precedence

The order matters when SafeSearch coexists with other features.

  1. Blocklists and admin deny win. SafeSearch sits after the filter stage. If www.youtube.com is on a deny list active for the profile, the query is blocked. SafeSearch never gets a chance to rewrite it.
  2. Operator-authored rewrites win over the preset. When the same (from, match_subdomains) pair is present in both, populate() skips the preset and keeps the operator entry. This is how the example above replaces only one of the eight rules.
  3. Schedules swap the whole profile. A device that switches from default to kids because of a 21:00 schedule picks up SafeSearch with the rest of the profile; switching back at 07:00 drops it. The handover is bounded by the 60-second schedule tick.
  4. Per-device overlay does not see SafeSearch. [[devices]].allow_rules and deny_rules are filter-stage rules, not rewrites. A device’s allow rule cannot exempt one search host from SafeSearch — the rewrite happens after the filter, and the device overlay only participates in the filter step.

Sources: src/dns/handler.rs:894-903 (hot-path order), src/profiles/safesearch.rs:119-134 (collision logic).

CLI

There is no dedicated warden safesearch subcommand. Activation is a TOML edit plus a reload:

# 1. Edit the profile
sudo $EDITOR /etc/purge-warden/profiles.d/kids.toml
#    add or flip: safe_search = true

# 2. Reload
sudo warden reload

To inspect:

# Profile flag (the source of truth — shown verbatim from config)
warden profile show kids | grep -i safe

# Runtime verification — should answer with forcesafesearch.google.com's address
dig @127.0.0.1 -p 53 www.google.com

# What the rewrite engine returns for the resolved profile.
# Note: warden rewrite list shows OPERATOR-AUTHORED rules only; the eight
# SafeSearch presets are injected at resolve time and intentionally hidden
# from listings to keep the operator's mental model uncluttered.
warden rewrite list --profile kids
Why presets stay hidden
warden rewrite list and warden profile show deliberately omit the eight SafeSearch rewrites even when safe_search = true. The bool is the contract; the presets are an implementation detail. If you want to know what’s enforced, this page is the reference — the rule set is fixed and ships with the daemon.

Sources: src/profiles/safesearch.rs:21-24.

TUI

No dedicated SafeSearch tab today. The flag does not yet appear in the Devices edit modal or the Profiles drilldown — flip it from the TOML and warden reload.

This is a known gap; the field is a primitive bool and slots into the existing profile-edit form trivially when the TUI side catches up.

REST API

Reading the flag:

MethodPathWhat
GET/api/configFull merged config. The boolean appears as profiles.<id>.safe_search in the JSON payload.

There is no dedicated POST /api/profile/<id>/safesearch endpoint and there is no write surface for arbitrary profile fields over REST — profile mutations are CLI- or hand-edit-only.

Limitations

Worth knowing before promising parents that SafeSearch is on:

  • DNS-only enforcement. A device that uses DNS-over-HTTPS (DoH) to a public resolver (e.g. browser-level https://dns.google/dns-query) bypasses Warden entirely. Pair SafeSearch with a network-level DoH block if this matters. See DoH blocking recipes (forthcoming).
  • DNS-over-TLS (DoT) on port 853 to a public resolver is the same story — a client that asks Quad9 directly never hits Warden.
  • Strict tier only. YouTube has no Moderate option in Warden today. Some families want Restricted-Moderate; that’s a config gap to flag, not a workaround.
  • No regional Google domains. google.co.uk, google.it, etc. pass through unrewritten. Add explicit [[profiles.<id>.rewrite_rules]] entries if your household uses them.
  • Mobile-app SafeSearch surfaces are out of scope. Android’s Google Search app and the iOS YouTube Kids app embed their own content filter; DNS-layer SafeSearch only intercepts the web-facing hostnames listed above.
  • The IP returned by the upstream is not validated. If a hostile resolver returns a non-SafeSearch IP for forcesafesearch.google.com, Warden will hand it to the client. Use a trustworthy upstream (Cloudflare, Quad9, your ISP) and consider enabling DNSSEC on the forwarder.

Troubleshooting

A device on the kids profile still sees adult image results.

  1. Confirm the device is actually on the profile: warden device show <id> and check the profile field.
  2. Confirm the flag is set: grep safe_search /etc/purge-warden/profiles.d/*.toml.
  3. Confirm the daemon picked up the change: journalctl -u purge-warden -n 50 | grep -i reload.
  4. Verify the rewrite: dig @127.0.0.1 -p 53 www.google.com should answer with the IP that forcesafesearch.google.com would resolve to.
  5. If step 4 shows the real www.google.com IP, the device is querying upstream directly — check the router DHCP advertises Warden as the DNS server and that the device hasn’t pinned its own DoH endpoint.

SafeSearch rewrites and an operator-authored rewrite both target the same host.

The operator entry wins; the preset is silently skipped. This is intentional. If you want to confirm: edit your TOML to remove the operator rewrite, run warden reload, then dig again — the answer should swing back to the SafeSearch host.

YouTube Restricted Mode shows the warning bar but lets some content through anyway.

That’s YouTube’s behaviour, not Warden’s. restrict.youtube.com is YouTube’s Strict-mode hostname; the platform decides what falls inside Strict. Warden’s contract is “route the user to YouTube’s strictest tier”, not “guarantee the strictest tier blocks everything”.

warden reload accepted the change but dig still returns the unrewritten host.

The 60-second schedule tick is the upper bound on profile rebuilds, but warden reload should rebuild immediately. Confirm the daemon is actually running the new config:

warden status | grep -i config
journalctl -u purge-warden -n 20 --no-pager

If the timestamps don’t match the reload, the daemon may not have ack’d the SIGHUP — restart with systemctl restart purge-warden as a last resort.

For deeper diagnosis, see troubleshooting.

See also

  • Profiles — the entity that holds the safe_search field.
  • Local DNS records — the other way to swap upstream answers, but per-record rather than per-vendor preset.
  • Blocklists — the deny-list stage that runs before SafeSearch in the hot path.
  • Recipes — household — end-to-end kids-profile example.
  • TOML reference — every field, every constraint.