Merge pull request #127 from naturallaw777/copilot/update-api-password-check

Use /etc/shadow as authoritative source for factory default password detection
This commit is contained in:
Sovran_Systems
2026-04-07 13:55:53 -05:00
committed by GitHub
2 changed files with 54 additions and 11 deletions

View File

@@ -2963,15 +2963,42 @@ async def api_security_status():
return {"status": status, "warning": warning}
def _is_free_password_default() -> bool:
"""Check /etc/shadow directly to see if 'free' still has a factory default password.
Hashes each known factory default against the current shadow hash so that
password changes made via GNOME, passwd, or any method other than the Hub
are detected correctly.
"""
import crypt # available in Python stdlib (deprecated in 3.13 but present on NixOS)
FACTORY_DEFAULTS = ["free", "gosovransystems"]
try:
with open("/etc/shadow", "r") as f:
for line in f:
parts = line.strip().split(":")
if parts[0] == "free" and len(parts) > 1:
current_hash = parts[1]
if not current_hash or current_hash in ("!", "*", "!!"):
return True # locked/no password — treat as default
for default_pw in FACTORY_DEFAULTS:
if crypt.crypt(default_pw, current_hash) == current_hash:
return True
return False
except (FileNotFoundError, PermissionError):
pass
return True # if /etc/shadow is unreadable, assume default for safety
@app.get("/api/security/password-is-default")
async def api_password_is_default():
"""Check if the free account password is still the factory default."""
try:
with open("/var/lib/secrets/free-password", "r") as f:
current = f.read().strip()
return {"is_default": current == "free"}
except FileNotFoundError:
return {"is_default": True}
"""Check if the free account password is still the factory default.
Uses /etc/shadow as the authoritative source so that password changes made
via GNOME Settings, the passwd command, or any other method are detected
correctly — not just changes made through the Hub or change-free-password.
"""
return {"is_default": _is_free_password_default()}
# ── System password change ────────────────────────────────────────

View File

@@ -98,7 +98,7 @@ in
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
path = [ pkgs.coreutils pkgs.python3 ];
script = ''
# If sealed AND onboarded fully clean, nothing to do
[ -f /var/lib/sovran-factory-sealed ] && [ -f /var/lib/sovran-customer-onboarded ] && exit 0
@@ -119,9 +119,25 @@ EOF
# If the user completed Hub onboarding, they've addressed security
[ -f /var/lib/sovran/onboarding-complete ] && exit 0
# If the free password has been changed from the factory default, no warning needed
if [ -f /var/lib/secrets/free-password ]; then
[ "$(cat /var/lib/secrets/free-password)" != "free" ] && exit 0
# If the free password has been changed from ALL known factory defaults, no warning needed
if [ -f /etc/shadow ]; then
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
STILL_DEFAULT=false
for DEFAULT_PW in "free" "gosovransystems"; do
EXPECTED=$(DEFAULT_PW="$DEFAULT_PW" FREE_HASH="$FREE_HASH" python3 -c \
"import crypt, os; print(crypt.crypt(os.environ['DEFAULT_PW'], os.environ['FREE_HASH']))")
if [ "$EXPECTED" = "$FREE_HASH" ]; then
STILL_DEFAULT=true
break
fi
done
if [ "$STILL_DEFAULT" = "false" ]; then
# Password was changed clear any legacy warning and exit
rm -f /var/lib/sovran/security-status /var/lib/sovran/security-warning
exit 0
fi
fi
fi
# No flags at all + secrets exist = legacy (pre-seal era) machine