Keep the profile private and write it all at once

Settings, bookmarks (server passwords) and identities (private keys)
were written in place with the default permissions, readable by every
user on the machine, and a crash mid-write left a truncated file that
the next start silently replaced with defaults.

Every profile file is now written to a private temporary file, synced
and moved over the old one in one step. The profile folder itself is
made accessible to its owner only, which also covers files written
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 10:55:09 +00:00
parent d7261c906d
commit 3cdade9ad7
10 changed files with 142 additions and 24 deletions

View File

@@ -0,0 +1,49 @@
package com.ts3client.config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ProfileFilesTest {
@TempDir
Path dir;
@Test
void replacesTheFileReadableByTheUserOnly() throws Exception {
File file = dir.resolve("settings.properties").toFile();
Files.writeString(file.toPath(), "old");
ProfileFiles.write(file, out -> out.write("new".getBytes()));
assertEquals("new", Files.readString(file.toPath()));
assertEquals("rw-------", PosixFilePermissions.toString(Files.getPosixFilePermissions(file.toPath())));
try (var left = Files.list(dir)) {
assertEquals(1, left.count(), "no temporary file left behind");
}
}
@Test
void aFailedWriteLeavesTheOldFileAlone() throws Exception {
File file = dir.resolve("bookmarks.properties").toFile();
Files.writeString(file.toPath(), "old");
assertThrows(IOException.class, () -> ProfileFiles.write(file, out -> {
out.write("half".getBytes());
throw new IOException("crashed");
}));
assertEquals("old", Files.readString(file.toPath()));
try (var left = Files.list(dir)) {
assertEquals(1, left.count(), "no temporary file left behind");
}
}
}