mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): block per-file symlink escape and stop sourcePath clobbering
This commit is contained in:
parent
f3a335f4f6
commit
4ffe7026d2
@ -133,6 +133,21 @@ public class WikiDirectoryScanService {
|
||||
|
||||
for (Path file : files) {
|
||||
try {
|
||||
// Per-file symlink guard: a symlinked file inside an allowed
|
||||
// directory could point outside it (e.g. secret.md ->
|
||||
// /etc/passwd). Resolve the real path and require it to stay
|
||||
// within the validated scan root; skip escapes.
|
||||
Path realFile;
|
||||
try {
|
||||
realFile = file.toRealPath();
|
||||
} catch (IOException e) {
|
||||
realFile = file.toAbsolutePath().normalize();
|
||||
}
|
||||
if (!realFile.startsWith(dir)) {
|
||||
errors.add("Skipped symlink escaping the scan root: " + file.getFileName());
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
String absolutePath = file.toAbsolutePath().normalize().toString();
|
||||
String fileName = file.getFileName().toString();
|
||||
String ext = getExtension(fileName);
|
||||
|
||||
@ -106,7 +106,10 @@ public class WikiRawMaterialService {
|
||||
// addText dedups internally by hash, so this reuses sameContent when
|
||||
// unchanged and inserts + triggers processing when the content differs.
|
||||
WikiRawMaterialEntity raw = addText(kbId, fileName, content);
|
||||
if (raw != null) {
|
||||
// Only stamp the path on a genuinely new raw. When the content matched
|
||||
// an existing raw (possibly a different file with identical content),
|
||||
// overwriting its sourcePath would corrupt that raw's provenance.
|
||||
if (raw != null && sameContent == null) {
|
||||
updateSourcePath(raw.getId(), absolutePath);
|
||||
}
|
||||
return sameContent == null;
|
||||
|
||||
@ -33,6 +33,8 @@ class WikiSourceWatcherServiceE2ETest {
|
||||
private WikiSourceWatcherService watcherService;
|
||||
@Autowired
|
||||
private WikiKnowledgeBaseService kbService;
|
||||
@Autowired
|
||||
private WikiDirectoryScanService scanService;
|
||||
|
||||
private static final java.util.concurrent.atomic.AtomicLong SEQ =
|
||||
new java.util.concurrent.atomic.AtomicLong(System.nanoTime());
|
||||
@ -64,6 +66,27 @@ class WikiSourceWatcherServiceE2ETest {
|
||||
assertEquals(1, afterEdit, "a modified file must be re-ingested");
|
||||
}
|
||||
|
||||
@Test
|
||||
void symlinkFileEscapingScanRoot_isNotIngested(@TempDir Path sourceDir, @TempDir Path outside)
|
||||
throws java.io.IOException {
|
||||
Files.writeString(sourceDir.resolve("real.md"), "# Real\n\nlocal content");
|
||||
Path secret = Files.writeString(outside.resolve("secret.md"), "TOP SECRET OUTSIDE");
|
||||
Path link = sourceDir.resolve("leak.md");
|
||||
try {
|
||||
Files.createSymbolicLink(link, secret);
|
||||
} catch (UnsupportedOperationException | java.io.IOException e) {
|
||||
return; // filesystem without symlink support — skip
|
||||
}
|
||||
|
||||
long kb = SEQ.incrementAndGet();
|
||||
WikiDirectoryScanService.ScanResult result = scanService.scanDirectory(kb, sourceDir.toString());
|
||||
|
||||
// Only the real file is ingested; the symlink escaping the root is skipped.
|
||||
assertEquals(1, result.added(), "symlinked file pointing outside the root must not be ingested");
|
||||
assertTrue(result.skipped() >= 1 || !result.errors().isEmpty(),
|
||||
"the escaping symlink should be reported as skipped");
|
||||
}
|
||||
|
||||
@Test
|
||||
void kbsWithoutSourceDirectory_areSkipped() {
|
||||
// A KB with no source directory must not cause errors in the cycle.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user