Auto-expire public/backup exports via a configurable tmpfiles age
HortusFox writes export/auto-backup zips to public/backup using a fully predictable hf_backup_<timestamp>.zip name, serves that directory as real static files (ahead of its own auth-gated routes), and never deletes them itself unless an external backup_path is separately configured. That leaves full data exports downloadable by anyone who knows or guesses the timestamp, indefinitely. This can't be fixed from packaging alone, but giving public/backup's tmpfiles rule an age (new backupMaxAge option, default "1d", null to disable) lets systemd-tmpfiles-clean.timer prune old exports and bound the exposure window. Every other writableDirs entry is unaffected (age stays "-"). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,23 @@ let
|
||||
"public/img" "public/attachments" "public/backup" "public/themes"
|
||||
];
|
||||
|
||||
# systemd-tmpfiles "age" overrides for specific writableDirs (default: "-",
|
||||
# never auto-cleaned). public/backup is HortusFox's own export/auto-backup
|
||||
# destination, named with a fully predictable `hf_backup_<timestamp>.zip`
|
||||
# pattern (see BackupModule/BackupController upstream) and -- since it
|
||||
# lives under public/, served as a real static file by nginx/caddy before
|
||||
# ever reaching the app's own auth-gated routes -- effectively downloadable
|
||||
# by anyone who knows or guesses that timestamp, indefinitely, since
|
||||
# upstream never deletes it (the auto-backup cron only does so when an
|
||||
# external backup_path is also configured in the admin panel; the manual
|
||||
# "Export" flow never does). We can't fix upstream's public-webroot
|
||||
# placement or predictable naming from here, but pruning anything older
|
||||
# than backupMaxAge bounds how long a given export stays fetchable, via
|
||||
# systemd's own periodic systemd-tmpfiles-clean.timer.
|
||||
writableDirAges = {
|
||||
"public/backup" = if cfg.backupMaxAge == null then "-" else cfg.backupMaxAge;
|
||||
};
|
||||
|
||||
# Intermediate parent dirs of the above (e.g. "app", "public") that need
|
||||
# their own explicit tmpfiles rule too: systemd-tmpfiles auto-vivifies a
|
||||
# missing parent when processing a nested `d` line, but does so with
|
||||
@@ -216,6 +233,22 @@ in {
|
||||
'';
|
||||
};
|
||||
|
||||
backupMaxAge = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = "1d";
|
||||
example = "7d";
|
||||
description = ''
|
||||
How long HortusFox's own export/auto-backup zips (written to
|
||||
public/backup, using systemd-tmpfiles "age" syntax, e.g. "1d",
|
||||
"12h") are kept before being auto-deleted. HortusFox names these
|
||||
files with a fully predictable hf_backup_<timestamp>.zip pattern and
|
||||
serves them from the public webroot without ever cleaning them up
|
||||
itself, so anyone who knows or guesses the timestamp can download a
|
||||
full data export indefinitely otherwise. Set to null to disable
|
||||
cleanup entirely (not recommended for internet-facing deployments).
|
||||
'';
|
||||
};
|
||||
|
||||
adminAccount.passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
@@ -328,7 +361,7 @@ in {
|
||||
systemd.tmpfiles.rules =
|
||||
[ "d ${stateDir} 0755 ${cfg.user} ${cfg.group} - -" ]
|
||||
++ map (d: "d ${stateDir}/${d} 0755 ${cfg.user} ${cfg.group} - -") writableDirParents
|
||||
++ map (d: "d ${stateDir}/${d} 0755 ${cfg.user} ${cfg.group} - -") writableDirs;
|
||||
++ map (d: "d ${stateDir}/${d} 0755 ${cfg.user} ${cfg.group} ${writableDirAges.${d} or "-"} -") writableDirs;
|
||||
|
||||
# Kept as one attrset: hortusfox-setup/phpfpm-hortusfox plus the
|
||||
# cron-generated services all live under systemd.services, and a
|
||||
|
||||
Reference in New Issue
Block a user