Description
For every interactive account (UID 1000 or above with a login shell):
- the home directory exists, belongs to the user and has mode
750or stricter; - files and directories whose name starts with a dot directly in the home (
.bashrc,.profile,.ssh...) belong to the user and are not writable by group or others; .netrcand.rhostsdo not exist, and.forwardis not used.
Debian 13 creates new homes with mode 700 (HOME_MODE in /etc/login.defs); Debian 12
creates them with 755.
Rationale
A world-readable home exposes documents, history and keys. A shell startup file writable by
someone else lets that person run commands as the user at their next login. .rhosts allows
password-less login with legacy tools, and .netrc stores plain-text passwords.
Impact
Other users can no longer browse home directories. Shared files belong in a group directory.
Audit
For each interactive account, check stat -c '%U %a' ~user (owner is the user, mode 750 or
stricter), ls -la ~user/.??* (no group or world write, owned by the user) and that
.netrc, .rhosts and .forward are absent.
Automated: every check must pass. Scanners report each check by its id.
homes
Ownership and mode of interactive homes and their dot files.
script: |
rc=0
awk -F: '$3 >= 1000 && $3 < 65534 && $7 !~ /nologin$|false$/ {print $1 ":" $6}' /etc/passwd > /tmp/.cb_users.$$
while IFS=: read -r u h; do
[ -d "$h" ] || { echo "$u: home $h missing"; rc=1; continue; }
[ "$(stat -c %U "$h")" = "$u" ] || { echo "$u: home not owned by user"; rc=1; }
m=$(stat -c %a "$h"); [ $(( 0$m & 027 )) -eq 0 ] || { echo "$u: home mode $m"; rc=1; }
for f in "$h"/.[!.]* "$h"/..?*; do
[ -e "$f" ] || continue
[ -L "$f" ] && continue
case ${f##*/} in .netrc | .rhosts | .forward) echo "$u: ${f##*/} present"; rc=1 ;; esac
[ "$(stat -c %U "$f")" = "$u" ] || { echo "$u: ${f##*/} not owned by user"; rc=1; }
m=$(stat -c %a "$f"); [ $(( 0$m & 022 )) -eq 0 ] || { echo "$u: ${f##*/} mode $m"; rc=1; }
done
done < /tmp/.cb_users.$$
rm -f /tmp/.cb_users.$$
[ "$rc" -eq 0 ] && echo "all interactive homes are private"
exit $rc
Remediation
For each reported account:
chown <user> ~userandchmod 750 ~user.chown <user> ~user/.<file>andchmod go-w ~user/.<file>.- Remove
.rhostsand.netrcafter checking with the user; review and remove.forward.
| Restart | none |
| Disruption | low |
Rollback
Permissions are not backed up: note the previous modes from the audit output before applying.
permissions
awk -F: '$3 >= 1000 && $3 < 65534 && $7 !~ /nologin$|false$/ {print $1 ":" $6}' /etc/passwd |
while IFS=: read -r u h; do
[ -d "$h" ] || continue
chown "$u" "$h"
chmod g-w,o-rwx "$h"
for f in "$h"/.[!.]* "$h"/..?*; do
[ -e "$f" ] && [ ! -L "$f" ] || continue
chown "$u" "$f"
chmod go-w "$f"
done
done
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- login.defs(5), Debian manual pages (HOME_MODE) Debian, vendor-doc, accessed 2026-10-18
- ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R50) ANSSI, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0 | R50 | partial |
| NIST SP 800-53 Rev. 55.1.1 | AC-3 | related |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |