Kosher Linux — Architecture¶
A family-friendly, filtered Linux distribution: Fedora bootc/ostree immutable base, GNOME desktop (three per-account layouts — see desktop.md), per-user filter modes, and administration without root.
The core idea: admin without root¶
Nobody on the machine has root: no sudo is shipped, the root account is locked, and a polkit rule removes all polkit admin identities, which kills every stock privileged action (adding flatpak remotes, rebasing the OS, system network changes).
The only privilege surface is kosherd, a root daemon exposing a D-Bus API
(org.kosherlinux.Daemon1). Members of the kosher-admin group are granted
its org.kosherlinux.* polkit actions outright from their own signed-in
session — signing in is the proof, and an administrator is never asked for a
password to manage users, install catalog apps, connect wifi, or apply
updates. On any other account those actions ask for an administrator's
password (GNOME's polkit agent handles the prompt), which polkit keeps for
the sitting and kosherd extends as a sliding session. kosherd leans on
existing privileged services (accountsservice, flatpak, bootc, NetworkManager)
instead of reimplementing them. There are otherwise no polkit admin identities
at all, so every stock action that defaults to auth_admin is unsatisfiable —
except a named list of everyday ones (time zone, language, device name, joining
a Wi-Fi network, printers, colour profiles) that
45-kosher-admin-system.rules grants back to kosher-admin without a
prompt, so GNOME Settings works for the parent without ever reaching
services, packages or the image.
Guardian dual-control: optionally, filter-weakening calls
(SetFilterMode, SetWhitelist, SetUrlRules, SetGuestConfig,
DisableGuardian) additionally require a
second password (e.g. the other spouse's), stored as a yescrypt hash in
/etc/kosher/guardian.shadow, rate-limited (5 tries → 15 min lockout).
Device security, and what GNOME's panel can say¶
Settings → Privacy & Security → Device Security reports fwupd's Host
Security ID. Three of its checks are an operating system's to answer, and
the image sets all three in /usr/lib/bootc/kargs.d/20-kosheros-security.toml:
| kernel argument | what it answers |
|---|---|
lockdown=integrity |
the kernel refuses the paths that would let root rewrite the running kernel. Secure Boot turns this on by itself; saying it explicitly means a machine booted without Secure Boot still gets the same floor |
intel_iommu=on |
devices sit behind the IOMMU. Modern kernels do this where the firmware exposes VT-d; older Intel parts need asking, and it costs nothing where it is already on |
mem_sleep_default=s2idle |
suspend to idle rather than deep S3, which fwupd marks down because S3 leaves the memory image where firmware attacks can reach it |
Nothing in the image loads an out-of-tree kernel module — no proprietary drivers ship, and none can be installed — so lockdown costs the family nothing.
The rest of that panel is not ours. Secure Boot, the TPM, VT-d and the firmware revision are the owner's settings, in the machine's firmware setup; Intel BootGuard, SPI flash write protection, the ME's manufacturing mode, pre-boot DMA protection and the CPU's CET and SMAP status are the vendor's, fixed when the machine was built. On ordinary consumer hardware the panel will keep saying "checks failed" however well the OS behaves, and one item stays red by design: Fedora swaps to zram, which fwupd counts as unencrypted swap.
So the panel is worth reading for the four firmware settings a person can actually change, and is not a verdict on the filter. Nothing on that screen affects whether KosherOS is filtering.
Filtering: the DNS and packet planes¶
Per-user modes, enforced in two planes:
DNS plane. systemd-resolved is masked. dnsmasq (kosher-dns.service)
is the only resolver: 127.0.0.1:53, upstream hardcoded to Cloudflare family
(1.1.1.3). nftables redirects every human user's port-53 traffic to it, so
picking another resolver is impossible. Everyone therefore gets
family-filtered DNS as a baseline; per-user differences happen at the IP
layer.
Packet plane. table inet kosher (rendered by kosherd.nft, loaded
fail-closed before the network by kosher-firewall.service):
| mode | enforcement |
|---|---|
none |
loopback + LAN print/mDNS only; everything else rejected |
whitelist |
only IPs in the @wl4/@wl6 sets (populated by dnsmasq's nftset= as it resolves whitelisted domains — direct-IP browsing is blocked for free) plus @sys4/@sys6 system domains |
dnsfilter |
open, behind family DNS + evasion blocking |
inspect |
as dnsfilter, plus URL rules applied by the local proxy (below) |
Users are dispatched by meta skuid; unknown human UIDs fall through to
mode_none (fail closed). A shared evasion_block chain rejects DoT (853),
QUIC/HTTP3 (udp 443 — also blocks DoH3/ECH), and a curated set of
DoH-on-tcp-443 resolver IPs, including the unfiltered 1.1.1.1/8.8.8.8.
Rootless containers don't escape this: a user netns egresses via pasta/slirp4netns, an ordinary process owned by that user in the host netns, so skuid rules still match.
Captive portals: an admin can open a temporary per-UID window
(SetCaptiveMode) implemented as an nft set element with a timeout.
Inspect mode: URL-level filtering¶
At the DNS/IP layer a request is only ever "some host" — the path is
encrypted, so site.com/videos cannot be told from site.com/learn.
Inspect mode is the fourth filter mode, and the only one that can act on
paths: it terminates TLS locally so the full URL is visible.
- Rules (
kosherd/urlrules.py) are an ordered allow/block list, first match wins, matched case-insensitively and ignoring scheme andwww(a rule blocking/videosmust not be dodged with/Videos). Bare hosts cover the whole site,*.hostincludes subdomains,host/dir/*covers the directory itself. Unmatched requests are allowed — inspect mode sits on the family-DNS baseline — so a trailingblock *makes it deny-by-default. - Plumbing: nftables redirects only inspected users' tcp/80,443 into
a local mitmproxy (
kosher-mitm.service), which runs as the unprivilegedkosher-mitmuser. kosherd starts it when someone is in the mode and stops it when nobody is. - Whose request is it? Packets carry no user identity, so the addon
resolves the client's source port through
/proc/net/tcp{,6}to the owning uid, then applies that user's rules and serves a branded block page. The proxy never reads the policy: kosherd renders onlyuid -> rulesinto/var/lib/kosher-mitm/rules.json. - The certificate: reading URLs requires the proxy to present its own certificates, so kosherd generates a CA once, installs it in the system trust store, and enables Firefox's enterprise-roots policy. The honest cost — this user's HTTPS is decrypted on this machine — is stated in the admin app next to the mode.
Manage rules with kosherctl rules <uid> list|allow|block|remove|clear, or
the Page rules editor in each profile. scripts/inspect-verify.sh drives
the whole path in a VM (11 checks).
Apps: the whole store, decided per account¶
KosherOS does not host a package repository. Upstream Flathub is the source, and only kosherd installs:
- polkit denies the Flatpak system-helper actions to every non-root subject
(a user running
flatpak installgets "system operation Deploy not allowed"), and malcontent blocks user-scope installs; - the KosherOS Store (
store-app/) is open to every user, including supervised ones — safety comes from kosherd's decision, not from hiding the store. It asks kosherd what this account may have (ListStoreApps, judged for the uid on the connection), asks it to install, and shows live progress streamed back over D-Bus (AppProgress/AppFinishedsignals); - kosherd resolves the real remote ref (branches aren't always
stable) and runs a libflatpak transaction on a worker thread.
What an account may have is one rule, appaccess.decide, asked by the
Store, by the installer and by malcontent (so what cannot be installed
cannot be run either):
- Blocks first.
blocked_apps(single Flatpak ids) andblocked_app_kinds(the Store's shelves — Internet, Work, Learning, Games, Music, Pictures & video, Developer tools, Utilities, Everything else — one vocabulary inkosherd.appkinds, folded from Flathub's freedesktop categories) apply whatever else says. - An approval settles it. The approved list
(
/etc/kosher/catalog.json, later portal-managed) is what an administrator has said yes to by name. app_access.approved— the default for every account, and today's allowlist — refuses anything else.store— what a new administrator gets, and what a parent can give an account or a group — goes on to judge the app.- The content ceiling. Flathub rates every app with OARS; kosherd
parses
<content_rating>from appstream and refuses anything above a fixed ceiling per attribute (appaccess.CEILING: nudity, sexual themes, profanity, gambling, narcotics and graphic violence at none; cartoon and fantasy violence up to moderate; alcohol, tobacco and realistic violence up to mild). A short list of filter-circumvention tools (Tor launchers, VPN clients) is refused the same way. An app the index does not know is refused too — offline fails closed.
The app index (name, summary, categories, icon file, rating for every app
on the remote) is parsed once per appstream download on a worker thread
at daemon start and cached in memory and at /var/lib/kosher/appindex.json,
so neither the Store nor a policy apply ever waits on a forty-megabyte
parse; malcontent is re-applied once the index is warm. Two things about
the download matter. flatpak cuts the apps a remote filter denies out of
the appstream it deploys, and names the deployed directory
<commit>-<sha256 of the filter>, so kosherd fetches again whenever the
deployed copy was built under a different filter than the one in force
(the upgrade from the allow-list filter otherwise left a fifty-app index
for a day), as well as when it is a day old. And the first fetch at boot
usually runs before the network is up, so a failed refresh is tried again
in two minutes rather than at the six-hour tick, and opening the Store
with no index asks at once.
The flatpak remote filter is deny-only: it keeps the circumvention tools
out of enumeration unless one is approved. It is not a boundary against
root — but nobody can become root here. kosherctl apps shows and sets an
account's access and blocks; kosherctl check-catalog verifies every
approved app exists on the remote.
Policy¶
/var/lib/kosher/policy.json (schema in policy/schema/) is the single
contract between kosherd, the admin app, and the portal. revision and
source are what make a second writer safe.
Portal sync¶
The portal (portal/, FastAPI + SQLite, self-hostable) is a second
writer of that policy, so the device has to distinguish a genuine
document from anything else. It does that with signatures, not trust in the
connection:
- the portal generates an Ed25519 key on first start and signs every policy document; a device pins the public half when it enrols with a one-time code;
- the device (
kosherd/sync.py) accepts a document only if the signature matches that pinned key and the revision is higher than the one it already applied — which is what stops an old, looser policy being replayed at it; - verification only lives on the device: it never signs and never holds the private key, so a stolen device cannot forge policy for another one;
- sync is outbound-only —
kosher-sync.timerpolls every 15 minutes, so no family machine opens an inbound port.kosherctl syncpulls now.
Enrolling and unenrolling are guardian-gated, because they hand filter control to a portal and take it back. Losing the portal does not unlock a device: the last applied policy keeps being enforced, and unenrolling leaves it in place.
Installation & first boot¶
just iso produces an Anaconda installer ISO (bootc-image-builder) that
installs KosherOS with no preset users and no passwords — the installer's
user-creation screens are disabled, because accounts are created by the
first-boot wizard instead.
On first boot kosher-firstboot.service runs instead of the login screen
(there is nobody to log in as yet): a cage kiosk session showing only
kosher-setup, so the machine cannot be used before it is configured. The
wizard creates the administrator (a real password, added to kosher-admin,
filter mode dnsfilter), optionally sets the guardian password and a GRUB
boot-menu password (pbkdf2 into /boot/grub2/user.cfg), and shows the
firmware checklist KosherOS cannot enforce itself (UEFI password, disable
USB/network boot, keep Secure Boot). Finishing writes
/var/lib/kosher/setup-complete, disables the unit, and starts GDM.
The setup D-Bus interface is the one path that runs without an authorized
admin — necessarily, since none exists yet. It is bounded precisely:
CreateFirstAdmin refuses once any admin is in the policy, and every setup
method is refused once the stamp exists, so it is not a standing escalation
path. (A wizard interrupted after creating the admin can still resume,
because completion is the stamp — not the mere existence of an admin.)
Update & release¶
The OS is a container image (os-image/Containerfile) on fedora-bootc.
CI builds, cosign-signs, and pushes it; devices auto-update atomically via
bootc with ostree rollback. /etc/containers/policy.json will pin the
device to images signed for our registry (stage 2).
Developer loop¶
| loop | command | speed |
|---|---|---|
| policy engine | just test, just render |
< 1 s |
| daemon in a dev VM | just deploy-kosherd VM |
~1 s |
| stock-VM stack install | just dev-install VM |
~1 min |
| OS image | just build → just vm-upgrade |
~minutes, layer-cached |
| full disk image | just vm |
~10 min |