mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
fix(files): reject symlinks when reloading generated downloads
This commit is contained in:
parent
52a2843bdf
commit
47066c6091
@ -506,12 +506,21 @@ public class GeneratedFileCache {
|
||||
Path bin = storageDir.resolve(id).normalize();
|
||||
Path meta = storageDir.resolve(id + META_SUFFIX).normalize();
|
||||
// Containment guard — id is already validated, this is defence in depth.
|
||||
if (!bin.startsWith(storageDir) || !Files.isRegularFile(bin) || !Files.isRegularFile(meta)) {
|
||||
if (!bin.startsWith(storageDir) || !Files.isRegularFile(bin, LinkOption.NOFOLLOW_LINKS)
|
||||
|| !Files.isRegularFile(meta, LinkOption.NOFOLLOW_LINKS)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Metadata parsed = parseMeta(Files.readString(meta), id);
|
||||
byte[] bytes = Files.readAllBytes(bin);
|
||||
// Refuse leaf symlinks again at open, including replacement after the
|
||||
// regular-file check. Parent-directory ownership is a separate boundary.
|
||||
Metadata parsed;
|
||||
try (var input = Files.newInputStream(meta, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) {
|
||||
parsed = parseMeta(new String(input.readAllBytes(), StandardCharsets.UTF_8), id);
|
||||
}
|
||||
byte[] bytes;
|
||||
try (var input = Files.newInputStream(bin, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) {
|
||||
bytes = input.readAllBytes();
|
||||
}
|
||||
return new Entry(bytes, parsed.filename(), parsed.mimeType(), parsed.expireAt(),
|
||||
parsed.workspaceId(), parsed.ownerUserId(), parsed.conversationId());
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -6,6 +6,8 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ -18,6 +20,32 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*/
|
||||
class GeneratedFileCachePersistenceTest {
|
||||
|
||||
@Test
|
||||
void coldDownloadRejectsSymbolicLinkToExternalContent(@TempDir Path dir) throws IOException {
|
||||
Path storage = Files.createDirectory(dir.resolve("cache"));
|
||||
var cache = new GeneratedFileCache(storage);
|
||||
String id = cache.put("original".getBytes(StandardCharsets.UTF_8), "report.txt", "text/plain");
|
||||
Path external = Files.writeString(dir.resolve("private.txt"), "outside content");
|
||||
Files.delete(storage.resolve(id));
|
||||
Files.createSymbolicLink(storage.resolve(id), external);
|
||||
|
||||
assertTrue(new GeneratedFileCache(storage).get(id).isEmpty());
|
||||
assertEquals("outside content", Files.readString(external));
|
||||
}
|
||||
|
||||
@Test
|
||||
void coldDownloadRejectsSymbolicLinkToExternalMetadata(@TempDir Path dir) throws IOException {
|
||||
Path storage = Files.createDirectory(dir.resolve("cache"));
|
||||
var cache = new GeneratedFileCache(storage);
|
||||
String id = cache.put("original".getBytes(StandardCharsets.UTF_8), "report.txt", "text/plain");
|
||||
Path metadata = storage.resolve(id + ".meta");
|
||||
Path external = Files.move(metadata, dir.resolve("outside.meta"));
|
||||
Files.createSymbolicLink(metadata, external);
|
||||
|
||||
assertTrue(new GeneratedFileCache(storage).get(id).isEmpty());
|
||||
assertTrue(Files.isRegularFile(external));
|
||||
}
|
||||
|
||||
@Test
|
||||
void callerCannotChangeRegisteredVersionThroughInputBytes(@TempDir Path dir) {
|
||||
var cache = new GeneratedFileCache(dir);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user