fix(workspace): reject impossible calendar dates in memory snapshot import

This commit is contained in:
matevip 2026-05-16 14:51:52 +08:00
parent 69fb03968b
commit f56f4b059a
2 changed files with 46 additions and 1 deletions

View File

@ -17,6 +17,8 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
@ -210,7 +212,23 @@ public class WorkspaceMemoryArchiveService {
}
private static boolean isAllowedFilename(String name) {
return TOP_LEVEL_WHITELIST.contains(name) || DAILY_FILENAME.matcher(name).matches();
if (TOP_LEVEL_WHITELIST.contains(name)) {
return true;
}
if (!DAILY_FILENAME.matcher(name).matches()) {
return false;
}
// The regex only constrains digit shape, so structurally-valid but
// non-existent dates (memory/2026-13-99.md, memory/2026-02-30.md)
// would slip through. Parse the date to reject calendar dates that
// cannot occur, keeping the daily ledger namespace clean.
String date = name.substring("memory/".length(), name.length() - ".md".length());
try {
LocalDate.parse(date);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
/**

View File

@ -173,6 +173,33 @@ class WorkspaceMemoryArchiveServiceTest {
org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString());
}
@Test
@DisplayName("Daily filename matching the digit shape but not a real date is rejected")
void impossibleCalendarDateRejected() throws Exception {
wireAgent(1L, 10L);
when(workspaceFileService.getFile(1L, "memory/2026-05-12.md"))
.thenReturn(null);
byte[] zip = makeZip(Map.of(
"memory/2026-05-12.md", "real date → create",
"memory/2026-13-99.md", "month 13 / day 99 → reject",
"memory/2026-02-30.md", "feb 30 does not exist → reject"));
WorkspaceMemoryArchiveService.ImportPreview preview =
service.previewImport(1L, 10L, zip);
assertThat(preview.willCreate()).containsExactlyInAnyOrder("memory/2026-05-12.md");
assertThat(preview.willSkip())
.extracting(WorkspaceMemoryArchiveService.SkipEntry::filename,
WorkspaceMemoryArchiveService.SkipEntry::reason)
.contains(
org.assertj.core.groups.Tuple.tuple("memory/2026-13-99.md", "not in whitelist"),
org.assertj.core.groups.Tuple.tuple("memory/2026-02-30.md", "not in whitelist"));
verify(workspaceFileService, never()).saveFile(org.mockito.ArgumentMatchers.anyLong(),
org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString());
}
@Test
@DisplayName("Preview surfaces old vs new hash + size for updated files")
void previewExposesDiffMetadata() throws Exception {