Know the attack, stop the attack

Find the weak spots before an attacker does

You read the health check's PASS/WARN/UNCONFIRMED/N/A verdicts and how the score is counted, then run the weakened-setting notice, the domain check, active scanning on 127.0.0.1 with its exit codes, and doctor's check of RoamSwitch itself on a disposable VM.

  • Level: Basics
  • About 40 min
  • 2026-11-12

#Find the weak spots before an attacker does

An attacker who wants into a server first looks for weak spots: SSH that takes passwords, a dev server reachable from outside, name lookups that can be hijacked. Once inside, they may also loosen settings to move more freely.

In this module you find those same weak spots first, from the defending side. It covers these features.

FeatureWhat it looks atHow it runs
Overall security diagnosisSSH, sudo, the firewall, kernel hardening and moresudo roamswitch status --server. The daemon also runs it every hour
Notice when a setting gets weakerWhether a setting that passed last time fails nowAutomatically, after each hourly audit
LLMNR and NBNS response checkWhether this host takes part in name lookups anyone on the network can answerOne item of the health check
Check of your own domainsSPF, DMARC, MTA-STS and expiry of your own domainsOnly when you name a domain
Proof-based vulnerability scanCORS mistakes and similar in dev servers on 127.0.0.1roamswitch scan-vulns --confirm, run by hand
RoamSwitch self-check (doctor)Whether RoamSwitch's own files are as installedsudo roamswitch doctor, run by hand
Practice on a disposable machine

The labs weaken settings, point DNS somewhere else and modify RoamSwitch's files. Use a disposable VM, not a production server. Every lab ends with the steps to undo it. Times in the output are UTC.

#Read the health check

The health check changes nothing. It checks this server's settings item by item and reports a score and a verdict per item.

Run the check and show only the items that did not pass

Run it once as root and once as an ordinary user.

sudo roamswitch status --server | head -6
sudo roamswitch status --server | grep -E "\[(WARN|UNCONFIRMED)\]"
roamswitch status --server | grep -E "\[(WARN|UNCONFIRMED)\]"
Excerpt (root)
  Score: 72/100 (B (Fair))
  Passed Checks: 24/33

  [WARN] SSH Root Login & Password Auth - Weak (Root login prohibited, password auth still allowed)
  [WARN] Sudoers Privilege Hygiene - Passwordless sudo rule with full privileges (NOPASSWD: ALL)
  [WARN] eBPF Runtime Security Agent - No active eBPF agent
  [WARN] Known Kernel CVE Exposure - Possible exposure to known kernel CVEs: CVE-2026-31532 (HIGH), CVE-2026-31584 (HIGH), CVE-2026-31586 (HIGH), … (514 more CVEs left out of this excerpt) (the package manager reports a pending update for this kernel; applying it and rebooting is recommended)
  [WARN] Exploited or Likely-Exploited Vulnerabilities (Installed Software) - 2 CVEs exploited or likely to be (brotli CVE-2026-111497, openssh CVE-2026-123158)
  [WARN] noexec on /tmp and /dev/shm - No noexec: /tmp (not its own mount; part of the / file system), /dev/shm
  [WARN] Legacy Insecure Protocols Absent - Cleartext client found (not a service that accepts connections): /usr/bin/telnet
  [WARN] Backup Recency - No backup job was found
  [WARN] Is the protection really working: Alert channels - ⚠️ not working
Excerpt (ordinary user: the lines that differ from root)
  Score: 76/100 (B (Fair))
  Passed Checks: 23/30
  [UNCONFIRMED] SSH Root Login & Password Auth - Unconfirmed (could not be read; root may be required)
  [UNCONFIRMED] Sudoers Privilege Hygiene - Unconfirmed (could not be read; root may be required)
  [UNCONFIRMED] Inbound Default Deny - Unconfirmed (insufficient permissions; reading this requires root)

There are four verdicts.

  • PASS: the item is set the expected way
  • WARN: a candidate to fix. The ↳ line of each item says how
  • UNCONFIRMED: it could not be checked. This does not mean it is safe
  • N/A: the item does not apply to this server (no Docker installed, for example)

The score and "Passed Checks" count only the items that were judged. N/A and UNCONFIRMED items are left out of the total. As root, 33 items were judged and 24 passed.

As an ordinary user the check cannot read the SSH settings, /etc/sudoers.d or the firewall rules, so more items come back UNCONFIRMED. The total drops to 30 and the score rises to 76. The higher score only means less could be read. Run it with sudo.

Each WARN above needs a different response.

  1. SSH: password logins are still allowed. Confirm that key login works, then set PasswordAuthentication no
  2. sudo: a rule lets someone sudo anything without a password. On this VM it is /etc/sudoers.d/90-cloud-init-users, written by cloud-init
  3. eBPF agent: the ↳ fix is to install Falco or Tetragon. They watch what happens inside containers
  4. Kernel CVEs: the running kernel's version may be hit by known CVEs. On this VM a newer kernel update was pending. Install it and reboot
  5. Exploited vulnerabilities: installed packages (brotli and openssh) still carry CVEs that are exploited or likely to be. See the details with sudo roamswitch scan-packages, then update
  6. noexec: a program dropped in /tmp or /dev/shm can run from there. RoamSwitch does not add noexec on its own, because it can break installers
  7. telnet: this is a client, not a service that accepts connections. If you do not use it, remove the package
  8. Backup: no backup job that runs on a schedule was found. If you back up some other way, you can ignore it
  9. Alert channels: there is nowhere to send alerts. The steps are in Installing Linux Server and first settings

For scripts, sudo roamswitch status --server --json returns the same result as JSON. The number of judged items is totalChecks, N/A items are notApplicableChecks, and UNCONFIRMED items are inconclusiveChecks.

What to do with items that did not pass
  1. A WARN for something this server does not need: fix it as its ↳ line says
  2. A WARN you keep on purpose (the host really is a router, for example): write the reason down in your operations records
  3. UNCONFIRMED: give the check what it needs, such as sudo or missing data. Package vulnerability data arrives through the updater (roamswitch-updater). How to read it is covered in An actively exploited vulnerability has no fix yet

Does this host take part in hijackable name lookups

When a name is not found in DNS, LLMNR and NBNS ask everyone on the local network. The first device to answer wins, so an attacker can answer with a fake address and receive the connection. This is known as LLMNR / NBT-NS poisoning.

LLMNR and NBNS response check checks whether any process is listening on the ports these lookups use (UDP 5355 for LLMNR, UDP 137 for NBNS). It reads only /proc/net/udp and /proc/net/udp6 and sends nothing. mDNS (UDP 5353) is listed but never fails the check, because printers and similar devices are found that way.

Turn LLMNR on and read the check

This changes the system. It adds one file to the systemd-resolved settings.

resolvectl llmnr
sudo mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nLLMNR=yes\n' | sudo tee /etc/systemd/resolved.conf.d/llmnr-lab.conf
sudo systemctl restart systemd-resolved
sudo ss -lunp "sport = :5355"
sudo roamswitch status --server | grep -A3 "LLMNR"
Excerpt
Global: no
Link 2 (enp0s1): no
[Resolve]
LLMNR=yes
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
UNCONN 0      0            0.0.0.0:5355      0.0.0.0:*    users:(("systemd-resolve",pid=3367,fd=11))
UNCONN 0      0               [::]:5355         [::]:*    users:(("systemd-resolve",pid=3367,fd=13))
  [WARN] LLMNR / NBNS / mDNS responders - ⚠️ Answering name lookups: LLMNR (systemd-resolve)
         (description line omitted)
         ↳ Turn off what you do not need. LLMNR: put LLMNR=no in /etc/systemd/resolved.conf and run sudo systemctl restart systemd-resolved (per NetworkManager connection: nmcli connection modify <name> connection.llmnr no).

On this VM, LLMNR was off to begin with (the first resolvectl llmnr output). The listener came from systemd-resolve, and the check names that process.

Undo it.

sudo rm /etc/systemd/resolved.conf.d/llmnr-lab.conf
sudo systemctl restart systemd-resolved
sudo roamswitch status --server | grep "LLMNR"
Output
  [PASS] LLMNR / NBNS / mDNS responders - No LLMNR, NBNS or mDNS responder

#Get told when a setting is weakened

Only the person who opens the health check reads it. If someone loosens a setting, nobody notices until the next time someone looks. Notice when a setting gets weaker sends one notification when a setting that passed at the last audit fails now.

The rules (checked in the source comments and the default settings):

  • The daemon audits 90 seconds after it starts, then every hour
  • The Server Edition watches 27 items, such as SSH, sudo, the firewall default and kernel hardening
  • The first audit only records. An item with no earlier record does not count as a change
  • The same item is not reported again within a day
  • The setting is config_regression_notice_enabled, true by default. After changing it, restart the daemon
Turn on IP forwarding and receive the notification

This changes the system. With net.ipv4.ip_forward set to 1, this server relays traffic for other machines. Attackers use it to reach networks further in.

To run an audit without waiting an hour, the lab restarts the daemon. The server kept its default language, and the history was read with LANG=en_US.UTF-8.

sudo roamswitch server config get config_regression_notice_enabled
sudo sysctl -w net.ipv4.ip_forward=1
roamswitch status --server | grep -A2 "IP Packet Forwarding"
sudo roamswitch server restart
# wait a little over 90 seconds
sudo roamswitch notifications | grep -A6 "A security setting was weakened"
Excerpt
true
net.ipv4.ip_forward = 1
  [WARN] IP Packet Forwarding Disabled - Enabled (Router mode)
         Ensures the host is not unintentionally acting as a network bridge/router.
         ↳ Set net.ipv4.ip_forward = 0 if this server is not a gateway.
  ✓ roamswitch-server service successfully restarted.

  A security setting was weakened
    ⚠️ Settings that passed the last audit now fail (1)
    Host: ubuntu (IP: 192.168.64.3)
    - IP Packet Forwarding Disabled: Enabled (Router mode)
    If you did not change them yourself, find out who or what did. `roamswitch status --server` shows the details and the fix.

The notification is kept in the notification history and also goes to the alert channels you set up (a webhook, for example). The body says only which setting changed and how. Read in a language other than the one it was recorded in, the setting line still shows the value, translated (in Japanese, - IPフォワーディング無効化: 有効 (ルーター動作)).

Undo it. A sysctl -w change is temporary, and an OS reboot also clears it.

sudo sysctl -w net.ipv4.ip_forward=0
Output
net.ipv4.ip_forward = 0
Do not
  • Set config_regression_notice_enabled to false to stop the notifications. Weakened settings would then go unnoticed
  • Dismiss a notification for a change you do not remember making. Check the logs for who or what made it

#Check your own domains

A domain with no SPF and DMARC can be forged as the sender of mail. A domain that lapses can be registered by someone else, together with every mailbox and login that trusted it. Neither shows up when you look inside the server.

Check of your own domains checks only the domains you list in domain_check_domains (up to five). The list is empty by default, and then nothing is looked up. With a domain listed, only three things leave the host (checked in the item's description and the source):

  1. dig asks the resolver in /etc/resolv.conf for the TXT, CAA, DS and MX records
  2. curl asks IANA's public list (which holds no domain name) and the registry's RDAP server for the expiry date
  3. Only when there are MX records and an _mta-sts TXT record, curl reads the policy file from the domain's own mta-sts host
Run a practice DNS server and check a domain

This changes the system. So that nothing is sent to outside servers, you run a practice DNS server (dnsmasq) inside the VM and point name lookups at it. example.com is a domain reserved for examples.

Start with SPF that lets anyone send (+all) and no DMARC.

sudo apt install -y dnsmasq-base
sudo ip addr add 127.0.0.2/8 dev lo
mkdir -p ~/lab
cat > ~/lab/zone.conf <<'Z'
port=53
listen-address=127.0.0.2
bind-interfaces
no-resolv
no-hosts
local=/example.com/
txt-record=example.com,"v=spf1 +all"
Z
sudo dnsmasq --conf-file=$HOME/lab/zone.conf --pid-file=/run/dnsmasq-lab.pid
IF=$(ip route show default | awk '{print $5; exit}')
sudo resolvectl dns "$IF" 127.0.0.2
dig +short TXT example.com
Output
"v=spf1 +all"

Name the domain and run the check.

sudo roamswitch server config set domain_check_domains example.com
sudo roamswitch status --server | grep -A2 "SPF"
Excerpt
  ✓ Server configuration updated and saved. (domain_check_domains = example.com)
  No restart needed. The next check (roamswitch server status, for example) uses it.

  [WARN] SPF, DMARC, MTA-STS and Expiry of Your Own Domains - example.com: SPF allows anyone to send (+all or ?all), no DMARC record / note: example.com: no CAA record, expiry date unavailable
         ↳ Add them to DNS. For a domain that sends mail, SPF is `v=spf1 include:(your sender) ~all`; for a domain that sends none, `v=spf1 -all`. Keep exactly one SPF TXT record. For DMARC, start with `v=DMARC1; p=none; rua=mailto:(address)` at `_dmarc`, confirm from the reports that your real senders pass, then raise it to `p=quarantine` and `p=reject`. (rest omitted)

The setting applies from the next check, with no restart. The expiry date cannot be read here, because the practice DNS server does not answer for the RDAP servers. When it cannot be read, it appears only as a note and does not fail the check.

Fix it as a domain that sends no mail: set SPF to -all and add DMARC.

sed -i 's/v=spf1 +all/v=spf1 -all/' ~/lab/zone.conf
echo 'txt-record=_dmarc.example.com,"v=DMARC1; p=reject"' >> ~/lab/zone.conf
sudo kill $(cat /run/dnsmasq-lab.pid); sleep 1
sudo dnsmasq --conf-file=$HOME/lab/zone.conf --pid-file=/run/dnsmasq-lab.pid
sudo resolvectl flush-caches
sudo roamswitch status --server | grep "SPF"
Output
  [PASS] SPF, DMARC, MTA-STS and Expiry of Your Own Domains - 1 domain checked (SPF and DMARC are fine; expiry could not be read for 1 domain) / note: example.com: no CAA record, expiry date unavailable

The audit right after the DNS change already showed the new result. As the check's description says, a command such as roamswitch status --server looks again each time it runs. Only the daemon and the MCP server, which keep running, remember the result for six hours.

Next, add an MX record and announce MTA-STS, but do not publish the policy file.

cat >> ~/lab/zone.conf <<'Z'
mx-host=example.com,mail.example.com,10
txt-record=_mta-sts.example.com,"v=STSv1; id=20261104"
address=/mta-sts.example.com/127.0.0.1
Z
sudo kill $(cat /run/dnsmasq-lab.pid); sleep 1
sudo dnsmasq --conf-file=$HOME/lab/zone.conf --pid-file=/run/dnsmasq-lab.pid
sudo resolvectl flush-caches
sudo roamswitch status --server | grep "SPF"
Output
  [WARN] SPF, DMARC, MTA-STS and Expiry of Your Own Domains - example.com: MTA-STS is announced but its policy file cannot be fetched / note: example.com: no CAA record, expiry date unavailable

Not using MTA-STS does not fail the check. Announcing it while the policy file cannot be fetched does. Sending mail servers trust the announcement and may then fail to deliver.

Undo it.

sudo roamswitch server config set domain_check_domains ""
sudo kill $(cat /run/dnsmasq-lab.pid)
sudo resolvectl revert "$IF"
sudo ip addr del 127.0.0.2/8 dev lo
Excerpt
  ✓ Server configuration updated and saved. (domain_check_domains = )

#Test a dev server for real

An open port and a weakness behind it are two different things. Proof-based vulnerability scan sends real check requests to services listening on 127.0.0.1. It sends read-only requests and nothing destructive (the --help text). The target is always 127.0.0.1; no other host can be named.

It is off by default. It sends only when ~/.config/roamswitch/config.json has "active_vuln_scan_enabled": true and you add --confirm. Without --confirm, it only lists the targets.

Test a dev server with a CORS mistake

For practice, run a small server that allows any Origin and also allows reading with credentials. With this setting, a malicious site can read this server's responses through a user's browser.

cat > /tmp/devsrv.py <<'PY'
import http.server
class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        o = self.headers.get('Origin')
        if o:
            self.send_header('Access-Control-Allow-Origin', o)
            self.send_header('Access-Control-Allow-Credentials', 'true')
        self.end_headers(); self.wfile.write(b'ok\n')
    def log_message(self, *a): pass
http.server.HTTPServer(('127.0.0.1', 3000), H).serve_forever()
PY
nohup python3 /tmp/devsrv.py >/dev/null 2>&1 &
roamswitch scan-vulns; echo "exit=$?"
Excerpt (without --confirm)
  The following will receive real, non-destructive reachability/verification requests:
    • 127.0.0.1:3000 (python3) — known-service check
    • 127.0.0.1:3000 (python3) — CORS / path traversal / redirect / reflected XSS check

  This was a dry run (nothing was sent). Re-run with --confirm to actually probe.
  This also requires "active_vuln_scan_enabled": true in ~/.config/roamswitch/config.json (off by default).

  Dry run: nothing was sent and nothing was checked (exit code 2).
exit=2

A dry run checks nothing, so it exits with 2. Adding --confirm while the setting is still off also gives 2. On Server Edition the message then says to write the setting into /root/.config/roamswitch/config.json if you run it with sudo.

Turn the setting on and run it for real.

mkdir -p ~/.config/roamswitch
echo '{"active_vuln_scan_enabled": true}' > ~/.config/roamswitch/config.json
roamswitch scan-vulns --confirm; echo "exit=$?"
Excerpt
  ✗ Port 3000 (python3)
    CORS Misconfiguration (Cross-Origin Allowed with Credentials)
    Sending an arbitrary Origin header caused it to be reflected in Access-Control-Allow-Origin (or answered with a wildcard *), together with Access-Control-Allow-Credentials: true. This combination lets a malicious website send authenticated requests to this server through a victim's browser and read the response.
    ↳ Restrict Access-Control-Allow-Origin to a specific, trusted allow-list of origins, and only enable Access-Control-Allow-Credentials when it's genuinely required.
  ℹ Port 22 (systemd (ssh.socket))
    nmap NSE finding: ssh2-enum-algos
    (omitted)
    ↳ nmap's own finding (not independently re-verified by RoamSwitch).
  ⚠ 1 item could not be checked (not evidence of safety):
    • 127.0.0.1:3000 (python3) — Reflected XSS (request values are written into the page unescaped)
  ℹ The 1 ℹ item is information (not a confirmed vulnerability, so it is not counted in the exit code).

exit=1

Read the marks like this.

  • ✗: the response to a request confirmed the weakness
  • ℹ: nmap's own result. RoamSwitch did not verify it. This run had one, the list of SSH ciphers on port 22 (ssh2-enum-algos). The owner of port 22 shows as systemd (ssh.socket), since ssh.socket starts sshd
  • ⚠ could not be checked: it could not be tested. This is not a sign of safety

The exit code is 1 when there is at least one ✗. ℹ items are not counted.

Limit the allowed origin to one and test again.

P=$(ss -ltnpH 'sport = :3000' | sed -n 's/.*pid=\([0-9]*\).*/\1/p'); kill $P
sed -i "s|if o:|if o == 'http://127.0.0.1:3000':|" /tmp/devsrv.py
nohup python3 /tmp/devsrv.py >/dev/null 2>&1 &
roamswitch scan-vulns --confirm | grep -c "✗"
roamswitch scan-vulns --confirm > /dev/null; echo "exit=$?"
Output
0
exit=0

The CORS finding is gone, and the exit code is 0. A script can tell the three cases apart: 0 for no ✗, 1 for at least one ✗, 2 when nothing could be checked. To carry on despite findings, handle 1 explicitly, as in roamswitch scan-vulns --confirm || [ $? -eq 1 ].

Undo it: stop the server and turn the setting off.

P=$(ss -ltnpH 'sport = :3000' | sed -n 's/.*pid=\([0-9]*\).*/\1/p'); kill $P
echo '{"active_vuln_scan_enabled": false}' > ~/.config/roamswitch/config.json

#Has RoamSwitch itself been modified

If the defenses themselves have been modified, the check results cannot be trusted either. RoamSwitch self-check (doctor) checks RoamSwitch's files and the tools the defenses use. It changes nothing.

It looks at these points (from roamswitch doctor --help and the actual output).

  • Whether the installed RoamSwitch files (including the daemon in /usr/lib/roamswitch and the systemd unit) and the tools the defenses use (nft and others) are owned by root and writable by no one else
  • Whether the package manager's check (dpkg --verify) finds the contents as installed
  • Whether certificate authorities were added by hand (/usr/local/share/ca-certificates and similar)
Modify an executable and catch it with doctor

This changes the system. You add one byte to the web gate executable, which is not in use, and make it writable by anyone. You also add a practice certificate authority. An attacker adds one to relay traffic and read its contents.

sudo cp -p /usr/bin/roamswitch-webgate /root/webgate.orig
printf '\0' | sudo tee -a /usr/bin/roamswitch-webgate >/dev/null
sudo chmod o+w /usr/bin/roamswitch-webgate
openssl req -x509 -newkey rsa:2048 -nodes -keyout /tmp/dummy-ca.key -out /tmp/dummy-ca.crt -days 1 -subj "/CN=Dummy Training CA"
sudo cp /tmp/dummy-ca.crt /usr/local/share/ca-certificates/dummy-training-ca.crt
sudo roamswitch doctor; echo "exit=$?"
Excerpt
[FAIL] roamswitch-webgate (/usr/bin/roamswitch-webgate) can be written by someone other than root (mode 757), so it could be replaced
[FAIL] 1 file of package roamswitch-server differs from what was installed: /usr/bin/roamswitch-webgate
[warn] 1 certificate authority file was added to this host by hand (dummy-training-ca.crt). Check that it does not belong to something that can read encrypted traffic

2 checks failed (5 warnings)
exit=1

One failure is enough to make the exit code 1, so a scheduled job can run doctor and act on the exit code. The mode and the contents appear on separate lines. Putting the mode back leaves the content difference in place.

Undo it.

sudo cp -p /root/webgate.orig /usr/bin/roamswitch-webgate
sudo chmod 755 /usr/bin/roamswitch-webgate
sudo rm /usr/local/share/ca-certificates/dummy-training-ca.crt /root/webgate.orig
sudo roamswitch doctor | tail -1
sudo dpkg --verify roamswitch-server; echo "verify=$?"
Output
Nothing failed (4 warnings)
verify=0

The permission check covers more than the executables in /usr/bin. It also looks at the daemon in /usr/lib/roamswitch and the systemd unit. If either is replaced, something else runs as root on the next start. This changes permissions (this changes the system).

U=$(systemctl show -p FragmentPath --value roamswitch-server)
sudo chmod o+w /usr/lib/roamswitch/roamswitch-server-daemon "$U"
sudo roamswitch doctor | grep "FAIL\|failed"
sudo chmod o-w /usr/lib/roamswitch/roamswitch-server-daemon "$U"
sudo roamswitch doctor | tail -1
Output
[FAIL] roamswitch-server-daemon (/usr/lib/roamswitch/roamswitch-server-daemon) can be written by someone other than root (mode 757), so it could be replaced
[FAIL] roamswitch-server.service (/usr/lib/systemd/system/roamswitch-server.service) can be written by someone other than root (mode 646), so it could be replaced
2 checks failed (4 warnings)
Nothing failed (4 warnings)

If a content difference shows up in real use, first find out who changed the file and when. A modified file can be a trace of an intrusion.

#What this does not show

What was checked, and what stays out of view

Every lab above was run on a real machine (a disposable Ubuntu 24.04 VM). Even so, the features in this module do not tell you the following.

  • Passing the health check does not prove there was no intrusion. It checks that settings have the expected form
  • The weakened-setting notification covers a fixed set of 27 items. Other changes (a port that opened, a guard that stopped) are reported by their own guards. The same item is not reported twice within a day, so two weakenings in one day give one notification
  • The name-lookup check looks at listeners. It does not see fake answers on the wire
  • The domain check trusts the DNS answers. A lying resolver gives a wrong result. The lab in this module used exactly that to practise
  • The active check covers 127.0.0.1 only. How the host looks from another device on the same LAN is checked by a dedicated node on the LAN (RoamSwitch Sensor)
  • doctor looks at RoamSwitch's files. Changes to other programs are the job of file integrity monitoring (FIM)
Check yourself

The health check shows "[UNCONFIRMED] Exploited or Likely-Exploited Vulnerabilities - Unconfirmed (no package vulnerability data is installed yet)". How should you read it?

Show the explanation

UNCONFIRMED means it could not be checked. It is not a confirmation of safety, and it is not counted in the score. The data arrives through the updater (roamswitch-updater); run the check again once it is there.

Check yourself

sudo roamswitch doctor says "1 check(s) failed" because a package file differs from what was installed. What do you do first?

Show the explanation

A content difference does not go away when you fix the mode. A modified file can be a trace of an intrusion. Check the logs for what happened, then reinstall the same package you installed.

#Where to go next

Features covered
What was verified

Verified on 2026-11-12. Target: Linux 1.17.39. On a disposable Ubuntu 24.04 VM with 1.17.39 from the public apt repository, every lab was run as root and as an ordinary user, in Japanese and English. The weakened-setting notice was raised once with the default server language and read in Japanese and in English (LANG).

Command output varies with the product version and the environment. If yours differs, check the version first.