Escape adminAccount.username/email before interpolating into the seed INSERT

Both fields were spliced directly into a single-quoted SQL string. An
ordinary value containing an apostrophe (e.g. adminAccount.username =
"O'Brien") broke the INSERT's SQL syntax outright, failing
hortusfox-setup on first boot under set -euo pipefail; a crafted value
could inject arbitrary SQL. Escape backslashes and quotes to match
MySQL/MariaDB's default string literal rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 13:19:24 -04:00
co-authored by Claude Sonnet 5
parent d88f6f312d
commit e2ac57d9c6
+10 -1
View File
@@ -41,6 +41,15 @@ let
in if builtins.length parts > 1 then lib.concatStringsSep "/" (lib.init parts) else null)
writableDirs));
# Escape a value for interpolation into a single-quoted SQL string literal
# (used for the admin-seed INSERT below). Doubling backslashes first, then
# quotes, matches MySQL/MariaDB's default (backslash-escapes-active)
# string literal rules -- without this, an ordinary value like "O'Brien"
# breaks the INSERT's SQL syntax outright (failing hortusfox-setup on
# first boot, since the script runs under `set -euo pipefail`), and a
# crafted value could inject arbitrary SQL.
sqlEscape = s: lib.replaceStrings [ "\\" "'" ] [ "\\\\" "''" ] s;
# Render one KEY=VALUE .env line. Booleans/ints/floats are left bare so
# Asatru's datatype auto-detection picks them up as such rather than string.
mkEnvLine = name: value:
@@ -384,7 +393,7 @@ in {
# this point.
${lib.optionalString (cfg.adminAccount.passwordFile != null) ''
admin_hash="$(php -r 'echo password_hash($argv[1], PASSWORD_BCRYPT);' "$(cat "${cfg.adminAccount.passwordFile}")")"
mariadb "${cfg.database.name}" -e "INSERT INTO UserModel (name, email, password, admin) VALUES ('${cfg.adminAccount.username}', '${cfg.adminAccount.email}', '$admin_hash', 1);"
mariadb "${cfg.database.name}" -e "INSERT INTO UserModel (name, email, password, admin) VALUES ('${sqlEscape cfg.adminAccount.username}', '${sqlEscape cfg.adminAccount.email}', '$admin_hash', 1);"
''}
touch "${stateDir}/.installed"