fix: close the path-escape hole in delivery (gallery and filename)
The wiki flagged this twice as open: `recipe.picdrop_gallery` had the same
weakness the print module already fixed. `posixpath.join('/kunden/lea',
'../../../etc/cron.d')` is `/etc/cron.d`, and the upload creates the directory
first. Gallery and file names come from the database -- recipes, folders,
settings -- and therefore ultimately from user input.
Checked in `safeSegment()` immediately before the write, in both `uploadBuffer`
(Picdrop and every FTP target) and `uploadToNas`. That is the one place nobody
delivering can bypass, rather than a check somewhere further up that a new call
site would miss.
Deliberately a denylist of the dangerous constructs -- separators, control
characters, all-dot names -- not a narrow allowlist. A customer folder may well
be called "Müller & Söhne (2026)"; that is harmless, and rejecting it would
have been a self-inflicted outage. Covered both ways by tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNhLunz586g6DeYMa7Qsd1
This commit is contained in:
3 files changed
+60
-6
No files matched your search
@@ -128,3 +128,29 @@ test('Die Schleife gibt auch bei dauerhaft belegten Namen etwas zurück', async
|
||||
const n = await uniqueFilename(ID1, 'lea', 'a4', 'png', AT, async () => true);
|
||||
assert.ok(n.endsWith('.png') && n.length > 10, `unbrauchbarer Rückfall: ${n}`);
|
||||
});
|
||||
|
||||
// --- Pfad-Ausbruch beim Ausliefern -------------------------------------------
|
||||
// `posixpath.join('/kunden/lea','../../../etc/cron.d')` ergibt `/etc/cron.d`, und
|
||||
// der Upload legt das Verzeichnis vorher an. Deshalb wird unmittelbar vor dem
|
||||
// Schreiben geprüft — hier die Prüfung selbst.
|
||||
import { safeSegment } from '../src/lib/picdrop.ts';
|
||||
|
||||
test('Galerie- und Dateinamen können den Basisordner nicht verlassen', () => {
|
||||
const boese = ['..', '../..', '../../../etc/cron.d', 'a/b', 'a\\b', '.', '...',
|
||||
'POSTER LEA/../..', 'y\ny', 'z\u0000z'];
|
||||
for (const b of boese) {
|
||||
assert.throws(() => safeSegment(b, 'Galerie'), /Ungültige/, `durchgerutscht: ${JSON.stringify(b)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Echte Kundenordner bleiben erlaubt — die Prüfung ist keine Schikane', () => {
|
||||
const gut = ['POSTER LEA', 'TheFrame-Backgrounds', 'Müller & Söhne (2026)',
|
||||
"Hochzeit O'Brien", 'Kita 2026_Gruppe 3', 'a.b.c',
|
||||
'2026-08-20_143207_lea-badge_the-frame_a3f9.jpg'];
|
||||
for (const g of gut) assert.equal(safeSegment(g, 'Galerie'), g, `fälschlich abgelehnt: ${g}`);
|
||||
});
|
||||
|
||||
test('Leerer Name ist erlaubt und bedeutet „kein Unterordner"', () => {
|
||||
assert.equal(safeSegment('', 'Galerie'), '');
|
||||
assert.equal(safeSegment(' ', 'Galerie'), '');
|
||||
});
|
||||
Reference in new issue
Block a user