mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
fix(files): isolate generated preview content with sandbox policy
This commit is contained in:
parent
0498530f3a
commit
7e45a26755
@ -60,16 +60,14 @@ public class GeneratedFileController {
|
||||
boolean isImage = mime != null && mime.startsWith("image/");
|
||||
boolean isHtml = mime != null && mime.toLowerCase().startsWith("text/html");
|
||||
String disposition = (isImage || isHtml) ? "inline" : "attachment";
|
||||
if (isHtml) {
|
||||
// The bytes are model/tool-generated HTML served from the app's
|
||||
// own origin. A strict CSP neutralises XSS: scripts, plugins and
|
||||
// framing are forbidden, only inline styles + images/fonts load.
|
||||
// This makes an on-demand "open the article" preview safe.
|
||||
headers.add("Content-Security-Policy",
|
||||
"default-src 'none'; img-src * data:; style-src 'unsafe-inline'; "
|
||||
+ "font-src * data:; media-src *; base-uri 'none'; form-action 'none'");
|
||||
headers.add("X-Content-Type-Options", "nosniff");
|
||||
}
|
||||
// Every generated document is untrusted, including SVG served
|
||||
// inline as an image. Isolate document origins and active content;
|
||||
// retain static styles/media and explicit downloads for previews.
|
||||
headers.add("Content-Security-Policy",
|
||||
"sandbox allow-downloads; default-src 'none'; img-src * data:; "
|
||||
+ "style-src 'unsafe-inline'; font-src * data:; media-src *; "
|
||||
+ "base-uri 'none'; form-action 'none'");
|
||||
headers.add("X-Content-Type-Options", "nosniff");
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||
disposition + "; filename=\"" + sanitizeAscii(entry.filename())
|
||||
+ "\"; filename*=UTF-8''" + encodedName);
|
||||
|
||||
@ -2,6 +2,8 @@ package vip.mate.tool.document;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
@ -12,7 +14,7 @@ import vip.mate.workspace.core.service.WorkspaceService;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ -53,6 +55,33 @@ class GeneratedFileControllerTest {
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"image/svg+xml,inline", "text/html,inline", "image/png,inline", "text/plain,attachment"})
|
||||
void generatedContentHasSandboxPolicyWithoutChangingBytesOrDisposition(String mime, String disposition,
|
||||
@TempDir Path dir) {
|
||||
GeneratedFileCache cache = new GeneratedFileCache(dir);
|
||||
byte[] payload = "<svg xmlns=\"http://www.w3.org/2000/svg\"><script>window.generatedScriptRan=true</script></svg>"
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
String id = cache.put(payload, "report", mime, new GeneratedFileCache.Owner(20L, 30L, "conv"));
|
||||
AuthService authService = mock(AuthService.class);
|
||||
WorkspaceService workspaceService = mock(WorkspaceService.class);
|
||||
when(authService.findByUsername("alice")).thenReturn(user(30L, "user"));
|
||||
when(workspaceService.hasPermissionCached(20L, 30L, "viewer")).thenReturn(true);
|
||||
var response = new GeneratedFileController(cache, authService, workspaceService)
|
||||
.download(id, 20L, new TestingAuthenticationToken("alice", "pw"));
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertArrayEquals(payload, (byte[]) response.getBody());
|
||||
assertTrue(response.getHeaders().getFirst("Content-Disposition").startsWith(disposition + ";"));
|
||||
assertEquals("nosniff", response.getHeaders().getFirst("X-Content-Type-Options"));
|
||||
String policy = response.getHeaders().getFirst("Content-Security-Policy");
|
||||
assertNotNull(policy);
|
||||
assertTrue(policy.contains("sandbox allow-downloads;"));
|
||||
assertTrue(policy.contains("default-src 'none';"));
|
||||
assertTrue(policy.contains("form-action 'none'"));
|
||||
assertFalse(policy.contains("allow-scripts"));
|
||||
assertFalse(policy.contains("allow-same-origin"));
|
||||
}
|
||||
|
||||
private static UserEntity user(Long id, String role) {
|
||||
UserEntity user = new UserEntity();
|
||||
user.setId(id);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user