From 4c56df2ed36557a1e82f00bade4de3beaf7a3c7b Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 31 May 2026 07:57:44 +0800 Subject: [PATCH] fix(wiki): add fail-closed option for empty source-path allow-list --- .../src/main/java/vip/mate/wiki/WikiProperties.java | 10 ++++++++++ .../mate/wiki/service/WikiSourcePathValidator.java | 5 +++++ .../wiki/service/WikiSourcePathValidatorTest.java | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java index c01ec03a..4267f2b5 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java @@ -80,6 +80,16 @@ public class WikiProperties { */ private java.util.List allowedSourceRoots = new java.util.ArrayList<>(); + /** + * Fail-closed switch for source-path validation. When {@code true} and + * {@link #allowedSourceRoots} is empty, every source directory is rejected + * (no path is allowed until a root is configured) — recommended for + * multi-tenant servers so a missing allow-list cannot silently re-open + * full-filesystem reads. Default {@code false} keeps the opt-in behaviour + * for desktop / single-tenant where no roots are configured. + */ + private boolean requireAllowedRoots = false; + /** * When {@code true}, a scheduled job (single-owner via ShedLock) scans each * KB's configured source directory and auto-ingests new files. Off by diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourcePathValidator.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourcePathValidator.java index 2a6aefea..2400a88b 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourcePathValidator.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourcePathValidator.java @@ -47,6 +47,11 @@ public class WikiSourcePathValidator { Path resolved = canonicalize(Paths.get(rawPath)); List roots = properties.getAllowedSourceRoots(); if (roots == null || roots.isEmpty()) { + if (properties.isRequireAllowedRoots()) { + throw new IllegalArgumentException( + "No allowed source roots are configured; refusing the path (fail-closed). " + + "Set mate.wiki.allowed-source-roots to permit directories."); + } return resolved; } for (String root : roots) { diff --git a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourcePathValidatorTest.java b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourcePathValidatorTest.java index a9dde248..df7eaee6 100644 --- a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourcePathValidatorTest.java +++ b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourcePathValidatorTest.java @@ -27,6 +27,12 @@ class WikiSourcePathValidatorTest { return new WikiSourcePathValidator(props); } + private WikiSourcePathValidator failClosedValidator() { + WikiProperties props = new WikiProperties(); + props.setRequireAllowedRoots(true); + return new WikiSourcePathValidator(props); + } + @Test void blankPath_rejected() { assertThrows(IllegalArgumentException.class, () -> validator(List.of()).validateDirectory(" ")); @@ -38,6 +44,13 @@ class WikiSourcePathValidatorTest { assertEquals(tmp.toRealPath(), resolved); } + @Test + void emptyRoots_failClosed_rejectsEverything(@TempDir Path tmp) { + // With require-allowed-roots enabled, an empty allow-list denies all. + assertThrows(IllegalArgumentException.class, + () -> failClosedValidator().validateDirectory(tmp.toString())); + } + @Test void insideAllowedRoot_isAccepted(@TempDir Path root) throws IOException { Path sub = Files.createDirectory(root.resolve("kb-source"));