$ -weight: 600;">sudo ufw -weight: 500;">disable
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">remove ufw
no-internet
id -u username
getent group groupname
ps -eo egid,egroup,cmd
-weight: 600;">sudo ufw -weight: 500;">status
sg groupname command
dpkg-divert
dpkg-divert --list
-weight: 600;">sudo iptables-save > ~/iptables.before
-weight: 600;">sudo mkdir -p ~/ufw_rules_backup
-weight: 600;">sudo cp /etc/ufw/before.rules ~/ufw_rules_backup/before.rules.backup
-weight: 600;">sudo cp /etc/ufw/before6.rules ~/ufw_rules_backup/before6.rules.backup
-weight: 600;">sudo iptables-save > ~/iptables.before
-weight: 600;">sudo mkdir -p ~/ufw_rules_backup
-weight: 600;">sudo cp /etc/ufw/before.rules ~/ufw_rules_backup/before.rules.backup
-weight: 600;">sudo cp /etc/ufw/before6.rules ~/ufw_rules_backup/before6.rules.backup
-weight: 600;">sudo iptables-save > ~/iptables.before
-weight: 600;">sudo mkdir -p ~/ufw_rules_backup
-weight: 600;">sudo cp /etc/ufw/before.rules ~/ufw_rules_backup/before.rules.backup
-weight: 600;">sudo cp /etc/ufw/before6.rules ~/ufw_rules_backup/before6.rules.backup
-weight: 600;">sudo cp ~/ufw_rules_backup/before.rules.backup /etc/ufw/before.rules
-weight: 600;">sudo cp ~/ufw_rules_backup/before6.rules.backup /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload
-weight: 600;">sudo cp ~/ufw_rules_backup/before.rules.backup /etc/ufw/before.rules
-weight: 600;">sudo cp ~/ufw_rules_backup/before6.rules.backup /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload
-weight: 600;">sudo cp ~/ufw_rules_backup/before.rules.backup /etc/ufw/before.rules
-weight: 600;">sudo cp ~/ufw_rules_backup/before6.rules.backup /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload
/etc/ufw/before.rules
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
β YOUR RULES GO HERE, right after these lines
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
β YOUR RULES GO HERE, right after these lines
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
β YOUR RULES GO HERE, right after these lines
# --- BEGIN no-internet block (IPv4) ---
-A ufw-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet: "
-A ufw-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv4) ---
# --- BEGIN no-internet block (IPv4) ---
-A ufw-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet: "
-A ufw-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv4) ---
# --- BEGIN no-internet block (IPv4) ---
-A ufw-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet: "
-A ufw-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv4) ---
/etc/ufw/before6.rules
ufw6-before-output
# --- BEGIN no-internet block (IPv6) ---
-A ufw6-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d fe80::/10 -j ACCEPT
# Optional: uncomment for mDNS / DLNA / SSDP LAN -weight: 500;">service discovery
# -A ufw6-before-output -m owner --gid-owner GID -d ff00::/8 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet v6: "
-A ufw6-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv6) ---
# --- BEGIN no-internet block (IPv6) ---
-A ufw6-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d fe80::/10 -j ACCEPT
# Optional: uncomment for mDNS / DLNA / SSDP LAN -weight: 500;">service discovery
# -A ufw6-before-output -m owner --gid-owner GID -d ff00::/8 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet v6: "
-A ufw6-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv6) ---
# --- BEGIN no-internet block (IPv6) ---
-A ufw6-before-output -m owner --gid-owner GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -d fe80::/10 -j ACCEPT
# Optional: uncomment for mDNS / DLNA / SSDP LAN -weight: 500;">service discovery
# -A ufw6-before-output -m owner --gid-owner GID -d ff00::/8 -j ACCEPT
-A ufw6-before-output -m owner --gid-owner GID -j LOG --log-prefix "Blocked noinet v6: "
-A ufw6-before-output -m owner --gid-owner GID -j REJECT
# --- END no-internet block (IPv6) ---
/etc/ufw/before.rules
--uid-owner
# --- BEGIN -weight: 500;">service UID block (IPv4) ---
-A ufw-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid: "
-A ufw-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv4) ---
# --- BEGIN -weight: 500;">service UID block (IPv4) ---
-A ufw-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid: "
-A ufw-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv4) ---
# --- BEGIN -weight: 500;">service UID block (IPv4) ---
-A ufw-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 127.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 10.0.0.0/8 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 172.16.0.0/12 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -d 192.168.0.0/16 -j ACCEPT
-A ufw-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid: "
-A ufw-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv4) ---
/etc/ufw/before6.rules
# --- BEGIN -weight: 500;">service UID block (IPv6) ---
-A ufw6-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d fe80::/10 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid v6: "
-A ufw6-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv6) ---
# --- BEGIN -weight: 500;">service UID block (IPv6) ---
-A ufw6-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d fe80::/10 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid v6: "
-A ufw6-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv6) ---
# --- BEGIN -weight: 500;">service UID block (IPv6) ---
-A ufw6-before-output -m owner --uid-owner UID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d ::1 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -d fe80::/10 -j ACCEPT
-A ufw6-before-output -m owner --uid-owner UID -j LOG --log-prefix "Blocked uid v6: "
-A ufw6-before-output -m owner --uid-owner UID -j REJECT
# --- END -weight: 500;">service UID block (IPv6) ---
/var/log/kern.log
# 1. Create a permanent backup
-weight: 600;">sudo cp /etc/ufw/before.rules /root/before.rules.backup # 2. Copy to a temporary file for editing
-weight: 600;">sudo cp /etc/ufw/before.rules /tmp/before.rules.edit
-weight: 600;">sudo nano /tmp/before.rules.edit # paste your rules # 3. Syntax check (safe, doesn't apply)
-weight: 600;">sudo iptables-restore --test < /tmp/before.rules.edit # 4. Apply the rules
-weight: 600;">sudo mv /tmp/before.rules.edit /etc/ufw/before.rules
-weight: 600;">sudo chown root:root /etc/ufw/before.rules
-weight: 600;">sudo chmod 644 /etc/ufw/before.rules
-weight: 600;">sudo ufw reload
# 1. Create a permanent backup
-weight: 600;">sudo cp /etc/ufw/before.rules /root/before.rules.backup # 2. Copy to a temporary file for editing
-weight: 600;">sudo cp /etc/ufw/before.rules /tmp/before.rules.edit
-weight: 600;">sudo nano /tmp/before.rules.edit # paste your rules # 3. Syntax check (safe, doesn't apply)
-weight: 600;">sudo iptables-restore --test < /tmp/before.rules.edit # 4. Apply the rules
-weight: 600;">sudo mv /tmp/before.rules.edit /etc/ufw/before.rules
-weight: 600;">sudo chown root:root /etc/ufw/before.rules
-weight: 600;">sudo chmod 644 /etc/ufw/before.rules
-weight: 600;">sudo ufw reload
# 1. Create a permanent backup
-weight: 600;">sudo cp /etc/ufw/before.rules /root/before.rules.backup # 2. Copy to a temporary file for editing
-weight: 600;">sudo cp /etc/ufw/before.rules /tmp/before.rules.edit
-weight: 600;">sudo nano /tmp/before.rules.edit # paste your rules # 3. Syntax check (safe, doesn't apply)
-weight: 600;">sudo iptables-restore --test < /tmp/before.rules.edit # 4. Apply the rules
-weight: 600;">sudo mv /tmp/before.rules.edit /etc/ufw/before.rules
-weight: 600;">sudo chown root:root /etc/ufw/before.rules
-weight: 600;">sudo chmod 644 /etc/ufw/before.rules
-weight: 600;">sudo ufw reload
no-internet
# Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID (e.g., 1001)
# Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID (e.g., 1001)
# Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID (e.g., 1001)
# Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER
# Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER
# Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER
-weight: 600;">sudo tee /usr/local/bin/no-internet > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet "$@"
EOF
-weight: 600;">sudo chmod 755 /usr/local/bin/no-internet
-weight: 600;">sudo tee /usr/local/bin/no-internet > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet "$@"
EOF
-weight: 600;">sudo chmod 755 /usr/local/bin/no-internet
-weight: 600;">sudo tee /usr/local/bin/no-internet > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet "$@"
EOF
-weight: 600;">sudo chmod 755 /usr/local/bin/no-internet
no-internet firefox &
no-internet steam &
no-internet keepassxc &
no-internet firefox &
no-internet steam &
no-internet keepassxc &
no-internet firefox &
no-internet steam &
no-internet keepassxc &
# Should be BLOCKED:
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β" # Should still work:
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL"
# Should be BLOCKED:
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β" # Should still work:
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL"
# Should be BLOCKED:
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β" # Should still work:
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL"
cp /usr/share/applications/firefox.desktop ~/.local/share/applications/
nano ~/.local/share/applications/firefox.desktop
# Change: Exec=firefox %u
# To: Exec=/usr/local/bin/no-internet firefox %u
cp /usr/share/applications/firefox.desktop ~/.local/share/applications/
nano ~/.local/share/applications/firefox.desktop
# Change: Exec=firefox %u
# To: Exec=/usr/local/bin/no-internet firefox %u
cp /usr/share/applications/firefox.desktop ~/.local/share/applications/
nano ~/.local/share/applications/firefox.desktop
# Change: Exec=firefox %u
# To: Exec=/usr/local/bin/no-internet firefox %u
no-internet
which firefox # might be /usr/bin/firefox
readlink -f "$(which firefox)" # resolves symlinks
file "$(readlink -f "$(which firefox)")" # should say "ELF 64-bit"
which firefox # might be /usr/bin/firefox
readlink -f "$(which firefox)" # resolves symlinks
file "$(readlink -f "$(which firefox)")" # should say "ELF 64-bit"
which firefox # might be /usr/bin/firefox
readlink -f "$(which firefox)" # resolves symlinks
file "$(readlink -f "$(which firefox)")" # should say "ELF 64-bit"
-weight: 600;">sudo chown root:no-internet /path/to/real/elf/binary
-weight: 600;">sudo chmod 750 /path/to/real/elf/binary
-weight: 600;">sudo chmod g+s /path/to/real/elf/binary # the magic: setgid bit
-weight: 600;">sudo chown root:no-internet /path/to/real/elf/binary
-weight: 600;">sudo chmod 750 /path/to/real/elf/binary
-weight: 600;">sudo chmod g+s /path/to/real/elf/binary # the magic: setgid bit
-weight: 600;">sudo chown root:no-internet /path/to/real/elf/binary
-weight: 600;">sudo chmod 750 /path/to/real/elf/binary
-weight: 600;">sudo chmod g+s /path/to/real/elf/binary # the magic: setgid bit
no-internet
firefox & sleep 1
ps -eo pid,uid,egid,cmd | grep firefox
# EGID column should show your no-internet GID number
firefox & sleep 1
ps -eo pid,uid,egid,cmd | grep firefox
# EGID column should show your no-internet GID number
firefox & sleep 1
ps -eo pid,uid,egid,cmd | grep firefox
# EGID column should show your no-internet GID number
-weight: 600;">sudo chmod g-s /path/to/real/elf/binary
-weight: 600;">sudo chown root:root /path/to/real/elf/binary
-weight: 600;">sudo chmod 755 /path/to/real/elf/binary
-weight: 600;">sudo chmod g-s /path/to/real/elf/binary
-weight: 600;">sudo chown root:root /path/to/real/elf/binary
-weight: 600;">sudo chmod 755 /path/to/real/elf/binary
-weight: 600;">sudo chmod g-s /path/to/real/elf/binary
-weight: 600;">sudo chown root:root /path/to/real/elf/binary
-weight: 600;">sudo chmod 755 /path/to/real/elf/binary
flatpak override --user --unshare=network com.app.Name
snap connections app-name
snap disconnect app-name:network
id -u jellyfin # e.g., 112
id -u jellyfin # e.g., 112
id -u jellyfin # e.g., 112
--uid-owner 112
--gid-owner
before.rules
before6.rules
-weight: 600;">sudo ufw reload
-weight: 600;">sudo ufw reload
-weight: 600;">sudo ufw reload
# Internet should be blocked:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # LAN should work (reaches a local Python HTTP server):
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 http://192.168.1.10 && echo "LAN works β" || echo "FAIL"
# Internet should be blocked:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # LAN should work (reaches a local Python HTTP server):
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 http://192.168.1.10 && echo "LAN works β" || echo "FAIL"
# Internet should be blocked:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # LAN should work (reaches a local Python HTTP server):
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 http://192.168.1.10 && echo "LAN works β" || echo "FAIL"
-weight: 600;">sudo ufw allow from 192.168.0.0/16 to any port 8096 proto tcp
-weight: 600;">sudo ufw allow from 192.168.0.0/16 to any port 8096 proto tcp
-weight: 600;">sudo ufw allow from 192.168.0.0/16 to any port 8096 proto tcp
-weight: 600;">sudo adduser --system --group --no-create-home --shell /usr/sbin/nologin myservice
-weight: 600;">sudo passwd -l myservice
id -u myservice # use this UID in rules
-weight: 600;">sudo adduser --system --group --no-create-home --shell /usr/sbin/nologin myservice
-weight: 600;">sudo passwd -l myservice
id -u myservice # use this UID in rules
-weight: 600;">sudo adduser --system --group --no-create-home --shell /usr/sbin/nologin myservice
-weight: 600;">sudo passwd -l myservice
id -u myservice # use this UID in rules
dpkg-divert
dpkg-divert
# 1. Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID # Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER # 2. Divert the real binary to a new location
-weight: 600;">sudo mkdir -p /usr/lib/chromium
-weight: 600;">sudo dpkg-divert --local --add --rename \ --divert /usr/lib/chromium/chromium.distrib /usr/bin/chromium # 3. Reinstall so the diverted file lands at the new path
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium # 4. Lock down the real binary
-weight: 600;">sudo chown root:no-internet /usr/lib/chromium/chromium.distrib
-weight: 600;">sudo chmod 0750 /usr/lib/chromium/chromium.distrib # 5. Put a shell wrapper at the original path
-weight: 600;">sudo tee /usr/bin/chromium > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet /usr/lib/chromium/chromium.distrib "$@"
EOF
-weight: 600;">sudo chmod 0755 /usr/bin/chromium
# 1. Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID # Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER # 2. Divert the real binary to a new location
-weight: 600;">sudo mkdir -p /usr/lib/chromium
-weight: 600;">sudo dpkg-divert --local --add --rename \ --divert /usr/lib/chromium/chromium.distrib /usr/bin/chromium # 3. Reinstall so the diverted file lands at the new path
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium # 4. Lock down the real binary
-weight: 600;">sudo chown root:no-internet /usr/lib/chromium/chromium.distrib
-weight: 600;">sudo chmod 0750 /usr/lib/chromium/chromium.distrib # 5. Put a shell wrapper at the original path
-weight: 600;">sudo tee /usr/bin/chromium > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet /usr/lib/chromium/chromium.distrib "$@"
EOF
-weight: 600;">sudo chmod 0755 /usr/bin/chromium
# 1. Create the group
-weight: 600;">sudo groupadd -f no-internet
getent group no-internet # note the GID # Add your user to the group so 'sg' doesn't prompt for a password
-weight: 600;">sudo usermod -aG no-internet $USER # 2. Divert the real binary to a new location
-weight: 600;">sudo mkdir -p /usr/lib/chromium
-weight: 600;">sudo dpkg-divert --local --add --rename \ --divert /usr/lib/chromium/chromium.distrib /usr/bin/chromium # 3. Reinstall so the diverted file lands at the new path
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium # 4. Lock down the real binary
-weight: 600;">sudo chown root:no-internet /usr/lib/chromium/chromium.distrib
-weight: 600;">sudo chmod 0750 /usr/lib/chromium/chromium.distrib # 5. Put a shell wrapper at the original path
-weight: 600;">sudo tee /usr/bin/chromium > /dev/null <<'EOF'
#!/bin/bash
exec sg no-internet /usr/lib/chromium/chromium.distrib "$@"
EOF
-weight: 600;">sudo chmod 0755 /usr/bin/chromium
/tmp/sg-wrapper.c
/* sg-wrapper.c β execv /bin/sg no-internet -- /usr/lib/chromium/chromium.distrib */
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> int main(int argc, char *argv[]) { const char *group = "no-internet"; const char *sg_path = "/bin/sg"; const char *real_binary = "/usr/lib/chromium/chromium.distrib"; int extra = argc - 1; /* count: sg_path + group + "--" + real_binary + extra_args + NULL */ int sg_argc = 1 + 1 + 1 + 1 + extra + 1; char **sg_argv = calloc(sg_argc, sizeof(char *)); if (!sg_argv) { fprintf(stderr, "calloc failed\n"); return 127; } int i = 0; sg_argv[i++] = (char *)sg_path; sg_argv[i++] = (char *)group; sg_argv[i++] = (char *)"--"; sg_argv[i++] = (char *)real_binary; for (int j = 1; j < argc; ++j) sg_argv[i++] = argv[j]; sg_argv[i] = NULL; execv(sg_path, sg_argv); fprintf(stderr, "execv(%s) failed: %s\n", sg_path, strerror(errno)); /* free is technically unreachable if execv succeeds, but kept for completeness */ free(sg_argv); return 126;
}
/* sg-wrapper.c β execv /bin/sg no-internet -- /usr/lib/chromium/chromium.distrib */
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> int main(int argc, char *argv[]) { const char *group = "no-internet"; const char *sg_path = "/bin/sg"; const char *real_binary = "/usr/lib/chromium/chromium.distrib"; int extra = argc - 1; /* count: sg_path + group + "--" + real_binary + extra_args + NULL */ int sg_argc = 1 + 1 + 1 + 1 + extra + 1; char **sg_argv = calloc(sg_argc, sizeof(char *)); if (!sg_argv) { fprintf(stderr, "calloc failed\n"); return 127; } int i = 0; sg_argv[i++] = (char *)sg_path; sg_argv[i++] = (char *)group; sg_argv[i++] = (char *)"--"; sg_argv[i++] = (char *)real_binary; for (int j = 1; j < argc; ++j) sg_argv[i++] = argv[j]; sg_argv[i] = NULL; execv(sg_path, sg_argv); fprintf(stderr, "execv(%s) failed: %s\n", sg_path, strerror(errno)); /* free is technically unreachable if execv succeeds, but kept for completeness */ free(sg_argv); return 126;
}
/* sg-wrapper.c β execv /bin/sg no-internet -- /usr/lib/chromium/chromium.distrib */
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> int main(int argc, char *argv[]) { const char *group = "no-internet"; const char *sg_path = "/bin/sg"; const char *real_binary = "/usr/lib/chromium/chromium.distrib"; int extra = argc - 1; /* count: sg_path + group + "--" + real_binary + extra_args + NULL */ int sg_argc = 1 + 1 + 1 + 1 + extra + 1; char **sg_argv = calloc(sg_argc, sizeof(char *)); if (!sg_argv) { fprintf(stderr, "calloc failed\n"); return 127; } int i = 0; sg_argv[i++] = (char *)sg_path; sg_argv[i++] = (char *)group; sg_argv[i++] = (char *)"--"; sg_argv[i++] = (char *)real_binary; for (int j = 1; j < argc; ++j) sg_argv[i++] = argv[j]; sg_argv[i] = NULL; execv(sg_path, sg_argv); fprintf(stderr, "execv(%s) failed: %s\n", sg_path, strerror(errno)); /* free is technically unreachable if execv succeeds, but kept for completeness */ free(sg_argv); return 126;
}
gcc -O2 -s -o /tmp/sg-wrapper /tmp/sg-wrapper.c
-weight: 600;">sudo mv /tmp/sg-wrapper /usr/bin/chromium
-weight: 600;">sudo chown root:no-internet /usr/bin/chromium
-weight: 600;">sudo chmod 2751 /usr/bin/chromium # setgid(2) + rwx(7) + r-x(5) + --x(1)
gcc -O2 -s -o /tmp/sg-wrapper /tmp/sg-wrapper.c
-weight: 600;">sudo mv /tmp/sg-wrapper /usr/bin/chromium
-weight: 600;">sudo chown root:no-internet /usr/bin/chromium
-weight: 600;">sudo chmod 2751 /usr/bin/chromium # setgid(2) + rwx(7) + r-x(5) + --x(1)
gcc -O2 -s -o /tmp/sg-wrapper /tmp/sg-wrapper.c
-weight: 600;">sudo mv /tmp/sg-wrapper /usr/bin/chromium
-weight: 600;">sudo chown root:no-internet /usr/bin/chromium
-weight: 600;">sudo chmod 2751 /usr/bin/chromium # setgid(2) + rwx(7) + r-x(5) + --x(1)
-weight: 500;">apt -weight: 500;">upgrade
# Tell dpkg to enforce ownership/permissions
-weight: 600;">sudo dpkg-statoverride --add root no-internet 0750 /usr/lib/chromium/chromium.distrib # Create a script that reapplies permissions
-weight: 600;">sudo tee /usr/local/sbin/reapply-noinet.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
GROUP=no-internet
[ -e /usr/bin/chromium ] && chown root:$GROUP /usr/bin/chromium && chmod 2751 /usr/bin/chromium || true
[ -e /usr/lib/chromium/chromium.distrib ] && chown root:$GROUP /usr/lib/chromium/chromium.distrib && chmod 0750 /usr/lib/chromium/chromium.distrib || true
EOF
-weight: 600;">sudo chmod 755 /usr/local/sbin/reapply-noinet.sh # Hook it into APT so it runs after every package -weight: 500;">update
-weight: 600;">sudo tee /etc/-weight: 500;">apt/-weight: 500;">apt.conf.d/99-reapply-noinet > /dev/null <<'EOF'
DPkg::Post-Invoke {"[ -x /usr/local/sbin/reapply-noinet.sh ] && /usr/local/sbin/reapply-noinet.sh";};
EOF
# Tell dpkg to enforce ownership/permissions
-weight: 600;">sudo dpkg-statoverride --add root no-internet 0750 /usr/lib/chromium/chromium.distrib # Create a script that reapplies permissions
-weight: 600;">sudo tee /usr/local/sbin/reapply-noinet.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
GROUP=no-internet
[ -e /usr/bin/chromium ] && chown root:$GROUP /usr/bin/chromium && chmod 2751 /usr/bin/chromium || true
[ -e /usr/lib/chromium/chromium.distrib ] && chown root:$GROUP /usr/lib/chromium/chromium.distrib && chmod 0750 /usr/lib/chromium/chromium.distrib || true
EOF
-weight: 600;">sudo chmod 755 /usr/local/sbin/reapply-noinet.sh # Hook it into APT so it runs after every package -weight: 500;">update
-weight: 600;">sudo tee /etc/-weight: 500;">apt/-weight: 500;">apt.conf.d/99-reapply-noinet > /dev/null <<'EOF'
DPkg::Post-Invoke {"[ -x /usr/local/sbin/reapply-noinet.sh ] && /usr/local/sbin/reapply-noinet.sh";};
EOF
# Tell dpkg to enforce ownership/permissions
-weight: 600;">sudo dpkg-statoverride --add root no-internet 0750 /usr/lib/chromium/chromium.distrib # Create a script that reapplies permissions
-weight: 600;">sudo tee /usr/local/sbin/reapply-noinet.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
GROUP=no-internet
[ -e /usr/bin/chromium ] && chown root:$GROUP /usr/bin/chromium && chmod 2751 /usr/bin/chromium || true
[ -e /usr/lib/chromium/chromium.distrib ] && chown root:$GROUP /usr/lib/chromium/chromium.distrib && chmod 0750 /usr/lib/chromium/chromium.distrib || true
EOF
-weight: 600;">sudo chmod 755 /usr/local/sbin/reapply-noinet.sh # Hook it into APT so it runs after every package -weight: 500;">update
-weight: 600;">sudo tee /etc/-weight: 500;">apt/-weight: 500;">apt.conf.d/99-reapply-noinet > /dev/null <<'EOF'
DPkg::Post-Invoke {"[ -x /usr/local/sbin/reapply-noinet.sh ] && /usr/local/sbin/reapply-noinet.sh";};
EOF
-weight: 600;">sudo rm -f /usr/bin/chromium
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
-weight: 600;">sudo rm -f /usr/bin/chromium
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
-weight: 600;">sudo rm -f /usr/bin/chromium
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
GID=1001 # your no-internet group ID -weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner $GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 2 -m owner --gid-owner $GID -d 127.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 3 -m owner --gid-owner $GID -d 10.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 4 -m owner --gid-owner $GID -d 172.16.0.0/12 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 5 -m owner --gid-owner $GID -d 192.168.0.0/16 -j ACCEPT
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j LOG --log-prefix "NOINTERNET: "
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j REJECT
GID=1001 # your no-internet group ID -weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner $GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 2 -m owner --gid-owner $GID -d 127.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 3 -m owner --gid-owner $GID -d 10.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 4 -m owner --gid-owner $GID -d 172.16.0.0/12 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 5 -m owner --gid-owner $GID -d 192.168.0.0/16 -j ACCEPT
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j LOG --log-prefix "NOINTERNET: "
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j REJECT
GID=1001 # your no-internet group ID -weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner $GID -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 2 -m owner --gid-owner $GID -d 127.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 3 -m owner --gid-owner $GID -d 10.0.0.0/8 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 4 -m owner --gid-owner $GID -d 172.16.0.0/12 -j ACCEPT
-weight: 600;">sudo iptables -I OUTPUT 5 -m owner --gid-owner $GID -d 192.168.0.0/16 -j ACCEPT
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j LOG --log-prefix "NOINTERNET: "
-weight: 600;">sudo iptables -A OUTPUT -m owner --gid-owner $GID -j REJECT
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install iptables-persistent
-weight: 600;">sudo netfilter-persistent save
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install iptables-persistent
-weight: 600;">sudo netfilter-persistent save
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install iptables-persistent
-weight: 600;">sudo netfilter-persistent save
/etc/nftables.conf
table inet lanlock { chain output { type filter hook output priority 0; meta skgid 1001 ct state related,established accept meta skgid 1001 ip daddr 127.0.0.0/8 accept meta skgid 1001 ip daddr 10.0.0.0/8 accept meta skgid 1001 ip daddr 172.16.0.0/12 accept meta skgid 1001 ip daddr 192.168.0.0/16 accept meta skgid 1001 ip6 daddr ::1 accept meta skgid 1001 ip6 daddr fe80::/10 accept meta skgid 1001 counter log prefix "NOINTERNET: " meta skgid 1001 drop }
}
table inet lanlock { chain output { type filter hook output priority 0; meta skgid 1001 ct state related,established accept meta skgid 1001 ip daddr 127.0.0.0/8 accept meta skgid 1001 ip daddr 10.0.0.0/8 accept meta skgid 1001 ip daddr 172.16.0.0/12 accept meta skgid 1001 ip daddr 192.168.0.0/16 accept meta skgid 1001 ip6 daddr ::1 accept meta skgid 1001 ip6 daddr fe80::/10 accept meta skgid 1001 counter log prefix "NOINTERNET: " meta skgid 1001 drop }
}
table inet lanlock { chain output { type filter hook output priority 0; meta skgid 1001 ct state related,established accept meta skgid 1001 ip daddr 127.0.0.0/8 accept meta skgid 1001 ip daddr 10.0.0.0/8 accept meta skgid 1001 ip daddr 172.16.0.0/12 accept meta skgid 1001 ip daddr 192.168.0.0/16 accept meta skgid 1001 ip6 daddr ::1 accept meta skgid 1001 ip6 daddr fe80::/10 accept meta skgid 1001 counter log prefix "NOINTERNET: " meta skgid 1001 drop }
}
-weight: 600;">sudo nft -f /etc/nftables.conf
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now nftables
-weight: 600;">sudo nft -f /etc/nftables.conf
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now nftables
-weight: 600;">sudo nft -f /etc/nftables.conf
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now nftables
--gid-owner
sg no-internet ...
no-internet
/usr/lib/chromium/chromium.distrib
no-internet
no-internet
strings /usr/bin/chromium
/proc/PID/exe
# Create a restricted user
-weight: 600;">sudo adduser --disabled-password --gecos "" --shell /usr/sbin/nologin chromium-user
-weight: 600;">sudo passwd -l chromium-user
id -u chromium-user # use this UID in UFW rules (same format as Option C) # Allow X11 display access
xhost +SI:localuser:chromium-user # Launch
-weight: 600;">sudo -u chromium-user chromium
# Create a restricted user
-weight: 600;">sudo adduser --disabled-password --gecos "" --shell /usr/sbin/nologin chromium-user
-weight: 600;">sudo passwd -l chromium-user
id -u chromium-user # use this UID in UFW rules (same format as Option C) # Allow X11 display access
xhost +SI:localuser:chromium-user # Launch
-weight: 600;">sudo -u chromium-user chromium
# Create a restricted user
-weight: 600;">sudo adduser --disabled-password --gecos "" --shell /usr/sbin/nologin chromium-user
-weight: 600;">sudo passwd -l chromium-user
id -u chromium-user # use this UID in UFW rules (same format as Option C) # Allow X11 display access
xhost +SI:localuser:chromium-user # Launch
-weight: 600;">sudo -u chromium-user chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install firejail # No network at all β this works reliably
firejail --net=none chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install firejail # No network at all β this works reliably
firejail --net=none chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install firejail # No network at all β this works reliably
firejail --net=none chromium
firejail --net=none
firejail --netfilter=/etc/firejail/lan-only.net chromium
firejail --netfilter=/etc/firejail/lan-only.net chromium
firejail --netfilter=/etc/firejail/lan-only.net chromium
/etc/firejail/lan-only.net
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
-A OUTPUT -d 127.0.0.0/8 -j ACCEPT
-A OUTPUT -d 10.0.0.0/8 -j ACCEPT
-A OUTPUT -d 172.16.0.0/12 -j ACCEPT
-A OUTPUT -d 192.168.0.0/16 -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
-A OUTPUT -d 127.0.0.0/8 -j ACCEPT
-A OUTPUT -d 10.0.0.0/8 -j ACCEPT
-A OUTPUT -d 172.16.0.0/12 -j ACCEPT
-A OUTPUT -d 192.168.0.0/16 -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
-A OUTPUT -d 127.0.0.0/8 -j ACCEPT
-A OUTPUT -d 10.0.0.0/8 -j ACCEPT
-A OUTPUT -d 172.16.0.0/12 -j ACCEPT
-A OUTPUT -d 192.168.0.0/16 -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Create a namespace with no external network
-weight: 600;">sudo ip netns add no-inet # Run the app inside it
-weight: 600;">sudo ip netns exec no-inet -weight: 600;">sudo -u $USER chromium # Optional: Add LAN-only access via a veth pair
-weight: 600;">sudo ip link add veth-host type veth peer name veth-jail
-weight: 600;">sudo ip link set veth-jail netns no-inet
-weight: 600;">sudo ip addr add 192.168.100.1/24 dev veth-host
-weight: 600;">sudo ip link set veth-host up
-weight: 600;">sudo ip netns exec no-inet ip addr add 192.168.100.2/24 dev veth-jail
-weight: 600;">sudo ip netns exec no-inet ip link set veth-jail up
-weight: 600;">sudo ip netns exec no-inet ip link set lo up
# Create a namespace with no external network
-weight: 600;">sudo ip netns add no-inet # Run the app inside it
-weight: 600;">sudo ip netns exec no-inet -weight: 600;">sudo -u $USER chromium # Optional: Add LAN-only access via a veth pair
-weight: 600;">sudo ip link add veth-host type veth peer name veth-jail
-weight: 600;">sudo ip link set veth-jail netns no-inet
-weight: 600;">sudo ip addr add 192.168.100.1/24 dev veth-host
-weight: 600;">sudo ip link set veth-host up
-weight: 600;">sudo ip netns exec no-inet ip addr add 192.168.100.2/24 dev veth-jail
-weight: 600;">sudo ip netns exec no-inet ip link set veth-jail up
-weight: 600;">sudo ip netns exec no-inet ip link set lo up
# Create a namespace with no external network
-weight: 600;">sudo ip netns add no-inet # Run the app inside it
-weight: 600;">sudo ip netns exec no-inet -weight: 600;">sudo -u $USER chromium # Optional: Add LAN-only access via a veth pair
-weight: 600;">sudo ip link add veth-host type veth peer name veth-jail
-weight: 600;">sudo ip link set veth-jail netns no-inet
-weight: 600;">sudo ip addr add 192.168.100.1/24 dev veth-host
-weight: 600;">sudo ip link set veth-host up
-weight: 600;">sudo ip netns exec no-inet ip addr add 192.168.100.2/24 dev veth-jail
-weight: 600;">sudo ip netns exec no-inet ip link set veth-jail up
-weight: 600;">sudo ip netns exec no-inet ip link set lo up
-weight: 600;">sudo groupadd -f no-internet
:ufw-before-output
-weight: 600;">sudo ufw reload
-weight: 600;">sudo iptables-restore --test < /etc/ufw/before.rules
iptables-persistent
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">remove iptables-persistent
readlink -f $(which app)
flatpak override --user --unshare=network com.app.Name
snap connections app-name
snap disconnect app-name:network
systemd-resolved
127.0.0.0/8
# 1. Group exists and GID is correct?
getent group no-internet
# Expected: no-internet:x:<GID>: # 2. Service UID correct? (Option C only)
id -u jellyfin
# Expected: numeric UID, e.g., 107 # 3. File ownership and permissions correct? (Options B/D)
stat -c "%n: %U %G %a" /usr/lib/chromium/chromium.distrib /usr/bin/chromium
# Expected: real binary β root:no-internet 0750, wrapper β per your policy # 4. Running processes have correct EGID/UID?
ps -eo pid,ppid,uid,euid,gid,egid,cmd | egrep 'chromium|jellyfin|firefox'
# Look for: EGID == no-internet GID (Options A/B/D) or UID == -weight: 500;">service UID (Option C) # 5. Internet blocked?
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β"
# For services:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # 6. LAN still works?
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL" # 7. Check firewall logs (if LOG rules added)
-weight: 600;">sudo journalctl -k --since "10 minutes ago" | grep -i 'Blocked\|NOINTERNET'
# 1. Group exists and GID is correct?
getent group no-internet
# Expected: no-internet:x:<GID>: # 2. Service UID correct? (Option C only)
id -u jellyfin
# Expected: numeric UID, e.g., 107 # 3. File ownership and permissions correct? (Options B/D)
stat -c "%n: %U %G %a" /usr/lib/chromium/chromium.distrib /usr/bin/chromium
# Expected: real binary β root:no-internet 0750, wrapper β per your policy # 4. Running processes have correct EGID/UID?
ps -eo pid,ppid,uid,euid,gid,egid,cmd | egrep 'chromium|jellyfin|firefox'
# Look for: EGID == no-internet GID (Options A/B/D) or UID == -weight: 500;">service UID (Option C) # 5. Internet blocked?
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β"
# For services:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # 6. LAN still works?
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL" # 7. Check firewall logs (if LOG rules added)
-weight: 600;">sudo journalctl -k --since "10 minutes ago" | grep -i 'Blocked\|NOINTERNET'
# 1. Group exists and GID is correct?
getent group no-internet
# Expected: no-internet:x:<GID>: # 2. Service UID correct? (Option C only)
id -u jellyfin
# Expected: numeric UID, e.g., 107 # 3. File ownership and permissions correct? (Options B/D)
stat -c "%n: %U %G %a" /usr/lib/chromium/chromium.distrib /usr/bin/chromium
# Expected: real binary β root:no-internet 0750, wrapper β per your policy # 4. Running processes have correct EGID/UID?
ps -eo pid,ppid,uid,euid,gid,egid,cmd | egrep 'chromium|jellyfin|firefox'
# Look for: EGID == no-internet GID (Options A/B/D) or UID == -weight: 500;">service UID (Option C) # 5. Internet blocked?
sg no-internet -c '-weight: 500;">curl -I -m 10 https://example.com' && echo "FAIL" || echo "BLOCKED β"
# For services:
-weight: 600;">sudo -u jellyfin -weight: 500;">curl -I -m 10 https://example.com && echo "FAIL" || echo "BLOCKED β" # 6. LAN still works?
sg no-internet -c '-weight: 500;">curl -I -m 10 http://192.168.1.1' && echo "LAN works β" || echo "FAIL" # 7. Check firewall logs (if LOG rules added)
-weight: 600;">sudo journalctl -k --since "10 minutes ago" | grep -i 'Blocked\|NOINTERNET'
# Restore UFW backups
-weight: 600;">sudo cp /root/before.rules.bak /etc/ufw/before.rules
-weight: 600;">sudo cp /root/before6.rules.bak /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload # If you need immediate connectivity recovery
-weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner <GID> -j ACCEPT
# Remove when fixed:
-weight: 600;">sudo iptables -D OUTPUT -m owner --gid-owner <GID> -j ACCEPT # Last resort β -weight: 500;">disable the entire firewall
-weight: 600;">sudo ufw -weight: 500;">disable
# Fix your rules, then: -weight: 600;">sudo ufw -weight: 500;">enable # Undo dpkg-divert (Option D)
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
# Restore UFW backups
-weight: 600;">sudo cp /root/before.rules.bak /etc/ufw/before.rules
-weight: 600;">sudo cp /root/before6.rules.bak /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload # If you need immediate connectivity recovery
-weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner <GID> -j ACCEPT
# Remove when fixed:
-weight: 600;">sudo iptables -D OUTPUT -m owner --gid-owner <GID> -j ACCEPT # Last resort β -weight: 500;">disable the entire firewall
-weight: 600;">sudo ufw -weight: 500;">disable
# Fix your rules, then: -weight: 600;">sudo ufw -weight: 500;">enable # Undo dpkg-divert (Option D)
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
# Restore UFW backups
-weight: 600;">sudo cp /root/before.rules.bak /etc/ufw/before.rules
-weight: 600;">sudo cp /root/before6.rules.bak /etc/ufw/before6.rules
-weight: 600;">sudo ufw reload # If you need immediate connectivity recovery
-weight: 600;">sudo iptables -I OUTPUT 1 -m owner --gid-owner <GID> -j ACCEPT
# Remove when fixed:
-weight: 600;">sudo iptables -D OUTPUT -m owner --gid-owner <GID> -j ACCEPT # Last resort β -weight: 500;">disable the entire firewall
-weight: 600;">sudo ufw -weight: 500;">disable
# Fix your rules, then: -weight: 600;">sudo ufw -weight: 500;">enable # Undo dpkg-divert (Option D)
-weight: 600;">sudo dpkg-divert ---weight: 500;">remove --rename /usr/bin/chromium
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install --reinstall chromium
ufw -weight: 500;">disable - Mark the app's processes with a specific UID or GID
- Write firewall rules that allow that UID/GID to reach LAN addresses but reject everything else - RELATED,ESTABLISHED β Don't break existing connections mid-stream
- Loopback (127.x) β App can still talk to localhost
- LAN ranges (10.x, 172.16.x, 192.168.x) β App can reach your home network
- LOG β Audit blocked attempts in /var/log/kern.log or journalctl
- REJECT β Everything else (the actual internet) gets blocked - β
Self-discipline β you want YOUR OWN app to -weight: 500;">stop phoning home (telemetry, metadata downloads, auto-updates)
- β
Services and daemons β Option C uses UID matching, which IS unbypassable since processes can't change their own UID
- β
Non-technical users β people who won't think to look for the diverted binary - β Technical users who actively want to bypass your restrictions
- β Multi-user machines where you're enforcing policy
- β Any scenario where "security through obscurity" isn't acceptable - ufw -weight: 500;">disable β restores full internet access
- ufw -weight: 500;">enable β locks everything down again