fix: reject include patterns that are not in the volume root
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled

This commit is contained in:
Nicolas Meienberger 2026-03-12 20:08:16 +01:00
parent 469f0d6c4e
commit 66f95dcd8e
2 changed files with 29 additions and 0 deletions

View file

@ -100,6 +100,21 @@ describe("executeBackup - include / exclude patterns", () => {
expect(processPattern("!*.log", "/volume")).toBe("!*.log");
});
test("rejects include patterns that escape the volume root", () => {
const volumePath = "/var/lib/zerobyte/volumes/vol123/_data";
const signal = new AbortController().signal;
expect(() =>
createBackupOptions(
createSchedule({
includePatterns: ["../../../../etc/shadow", "/../etc/passwd", "!/../../secrets.txt"],
}),
volumePath,
signal,
),
).toThrow("Include pattern escapes volume root");
});
test("anchors relative glob include patterns to the volume path", () => {
const volumePath = "/var/lib/zerobyte/volumes/vol123/_data";
const schedule = createSchedule({

View file

@ -23,12 +23,26 @@ export const processPattern = (pattern: string, volumePath: string, relative = f
const isNegated = pattern.startsWith("!");
const p = isNegated ? pattern.slice(1) : pattern;
const ensurePatternIsWithinVolume = (candidate: string) => {
const resolvedVolumePath = path.resolve(volumePath);
const resolvedCandidatePath = path.resolve(volumePath, candidate);
const relativePath = path.relative(resolvedVolumePath, resolvedCandidatePath);
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
throw new Error(`Include pattern escapes volume root: ${pattern}`);
}
};
if (!p.startsWith("/")) {
if (!relative) return pattern;
ensurePatternIsWithinVolume(p);
const processed = path.join(volumePath, p);
return isNegated ? `!${processed}` : processed;
}
if (relative) {
ensurePatternIsWithinVolume(p.slice(1));
}
const processed = path.join(volumePath, p.slice(1));
return isNegated ? `!${processed}` : processed;
};