diff --git a/README.md b/README.md index 394fb577..8971aec7 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,23 @@ Recommended production baseline: If the GHCR package remains private, run `docker login ghcr.io` before `docker compose up -d`. +### Upload Allowlist Override + +Skill package upload validation uses the default extension allowlist from +[`SkillPackagePolicy.java`](./server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java). +`SkillPublishProperties` uses that same list by default for +`skillhub.publish.allowed-file-extensions`. + +If you need to replace the default allowlist at runtime, set: + +```bash +SKILLHUB_PUBLISH_ALLOWED_FILE_EXTENSIONS=.md,.json,.xsd,.xsl,.dtd,.docx,.xlsx,.pptx +``` + +Spring Boot binds this environment variable to +`skillhub.publish.allowed-file-extensions`. When set, it replaces the default +allowlist instead of appending to it. + ### Monitoring A Prometheus + Grafana monitoring stack lives under [`monitoring/`](./monitoring). diff --git a/README_zh.md b/README_zh.md index 534142cc..0bdaaf3f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -204,6 +204,23 @@ AUTH_SESSION_TIMEOUT=30m 完整配置参考请查看 [`application.yml`](./server/skillhub-app/src/main/resources/application.yml)。 +### 上传白名单覆盖 + +技能包上传校验默认使用 +[`SkillPackagePolicy.java`](./server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java) +中的扩展名白名单。`SkillPublishProperties` 默认也会把这份列表作为 +`skillhub.publish.allowed-file-extensions` 的值。 + +如果需要在运行时整体替换默认白名单,可以设置: + +```bash +SKILLHUB_PUBLISH_ALLOWED_FILE_EXTENSIONS=.md,.json,.xsd,.xsl,.dtd,.docx,.xlsx,.pptx +``` + +Spring Boot 会把这个环境变量绑定到 +`skillhub.publish.allowed-file-extensions`。一旦设置,该配置会替换默认白名单, +而不是在默认列表后追加。 + ## 架构 SkillHub 采用清晰的分层架构: diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/SkillPublishProperties.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/SkillPublishProperties.java index 98cb6de7..f316ea37 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/SkillPublishProperties.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/SkillPublishProperties.java @@ -1,5 +1,6 @@ package com.iflytek.skillhub.config; +import com.iflytek.skillhub.domain.skill.validation.SkillPackagePolicy; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -13,13 +14,7 @@ public class SkillPublishProperties { private int maxFileCount = 100; private long maxSingleFileSize = 10 * 1024 * 1024; // 10MB private long maxPackageSize = 100 * 1024 * 1024; - private Set allowedFileExtensions = new LinkedHashSet<>(Set.of( - ".md", ".txt", ".json", ".yaml", ".yml", ".html", ".css", ".csv", ".pdf", - ".toml", ".xml", ".ini", ".cfg", ".env", - ".js", ".ts", ".py", ".sh", ".rb", ".go", ".rs", ".java", ".kt", - ".lua", ".sql", ".r", ".bat", ".ps1", ".zsh", ".bash", - ".png", ".jpg", ".jpeg", ".svg", ".gif", ".webp", ".ico" - )); + private Set allowedFileExtensions = new LinkedHashSet<>(SkillPackagePolicy.ALLOWED_EXTENSIONS); public int getMaxFileCount() { return maxFileCount; diff --git a/server/skillhub-app/src/main/resources/application.yml b/server/skillhub-app/src/main/resources/application.yml index 3ab88d28..8a3872af 100644 --- a/server/skillhub-app/src/main/resources/application.yml +++ b/server/skillhub-app/src/main/resources/application.yml @@ -112,7 +112,8 @@ skillhub: max-file-count: 100 max-single-file-size: 10485760 # 10MB max-package-size: 104857600 # 100MB - allowed-file-extensions: .md,.txt,.json,.yaml,.yml,.html,.css,.csv,.pdf,.toml,.xml,.ini,.cfg,.env,.js,.ts,.py,.pyc,.sh,.rb,.go,.rs,.java,.kt,.lua,.sql,.r,.bat,.ps1,.zsh,.bash,.png,.jpg,.jpeg,.svg,.gif,.webp,.ico + # allowed-file-extensions: uses SkillPackagePolicy.ALLOWED_EXTENSIONS by default + # Override via SKILLHUB_PUBLISH_ALLOWED_FILE_EXTENSIONS env var if needed profile: moderation: machine-review: ${SKILLHUB_PROFILE_MACHINE_REVIEW_ENABLED:true} # Enable machine review (e.g. sensitive word detection) diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/SkillPublishPropertiesTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/SkillPublishPropertiesTest.java new file mode 100644 index 00000000..6bda9a44 --- /dev/null +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/SkillPublishPropertiesTest.java @@ -0,0 +1,39 @@ +package com.iflytek.skillhub.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.SystemEnvironmentPropertySource; + +import java.util.Map; + +class SkillPublishPropertiesTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(TestConfig.class); + + @Test + void bindsAllowedFileExtensionsFromEnvironmentStyleProperty() { + contextRunner + .withInitializer((context) -> context.getEnvironment().getPropertySources().addFirst( + new SystemEnvironmentPropertySource( + "test-env", + Map.of("SKILLHUB_PUBLISH_ALLOWED_FILE_EXTENSIONS", ".docx,.xsd,.pptx") + ) + )) + .run((context) -> { + SkillPublishProperties properties = context.getBean(SkillPublishProperties.class); + + assertThat(properties.getAllowedFileExtensions()) + .containsExactly(".docx", ".xsd", ".pptx"); + }); + } + + @Configuration + @EnableConfigurationProperties(SkillPublishProperties.class) + static class TestConfig { + } +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java index 87a952b9..49a5f052 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/SkillPackagePolicy.java @@ -22,13 +22,15 @@ public final class SkillPackagePolicy { public static final Set ALLOWED_EXTENSIONS = Set.of( // Documentation ".md", ".txt", ".json", ".yaml", ".yml", ".html", ".css", ".csv", ".pdf", - // Configuration - ".toml", ".xml", ".ini", ".cfg", ".env", + // Configuration and schemas + ".toml", ".xml", ".xsd", ".xsl", ".dtd", ".ini", ".cfg", ".env", // Scripts and source code ".js", ".ts", ".py", ".sh", ".rb", ".go", ".rs", ".java", ".kt", ".lua", ".sql", ".r", ".bat", ".ps1", ".zsh", ".bash", // Images - ".png", ".jpg", ".jpeg", ".svg", ".gif", ".webp", ".ico" + ".png", ".jpg", ".jpeg", ".svg", ".gif", ".webp", ".ico", + // Office documents + ".docx", ".xlsx", ".pptx" ); private SkillPackagePolicy() { @@ -126,7 +128,8 @@ public final class SkillPackagePolicy { || path.endsWith(".json") || path.endsWith(".yaml") || path.endsWith(".yml") || path.endsWith(".js") || path.endsWith(".ts") || path.endsWith(".py") || path.endsWith(".sh") || path.endsWith(".html") || path.endsWith(".css") || path.endsWith(".csv") - || path.endsWith(".toml") || path.endsWith(".xml") || path.endsWith(".ini") + || path.endsWith(".toml") || path.endsWith(".xml") || path.endsWith(".xsd") + || path.endsWith(".xsl") || path.endsWith(".dtd") || path.endsWith(".ini") || path.endsWith(".cfg") || path.endsWith(".env") || path.endsWith(".rb") || path.endsWith(".go") || path.endsWith(".rs") || path.endsWith(".java") || path.endsWith(".kt") || path.endsWith(".lua") diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/validation/SkillPackageValidatorTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/validation/SkillPackageValidatorTest.java index 44c57254..39de4640 100644 --- a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/validation/SkillPackageValidatorTest.java +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/validation/SkillPackageValidatorTest.java @@ -286,6 +286,29 @@ class SkillPackageValidatorTest { assertTrue(result.passed()); } + @Test + void acceptsDocxFile() { + // DOCX is a ZIP-based format; PK magic bytes (0x50, 0x4b, 0x03, 0x04) + byte[] docxContent = new byte[] {0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x06, 0x00}; + List entries = List.of( + skillMdEntry(), + new PackageEntry("document.docx", docxContent, docxContent.length, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") + ); + ValidationResult result = validator.validate(entries); + assertTrue(result.passed()); + } + + @Test + void acceptsXsdSchemaFile() { + byte[] xsdContent = "".getBytes(); + List entries = List.of( + skillMdEntry(), + new PackageEntry("schema.xsd", xsdContent, xsdContent.length, "application/xml") + ); + ValidationResult result = validator.validate(entries); + assertTrue(result.passed()); + } + private PackageEntry skillMdEntry() { String skillMdContent = """ --- diff --git a/web/src/features/skill/file-type-utils.ts b/web/src/features/skill/file-type-utils.ts index ebc4ed64..dd818470 100644 --- a/web/src/features/skill/file-type-utils.ts +++ b/web/src/features/skill/file-type-utils.ts @@ -14,8 +14,8 @@ const PREVIEWABLE_EXTENSIONS = new Set([ 'ts', 'tsx', 'js', 'jsx', 'json', 'yaml', 'yml', 'py', 'java', 'go', 'rs', 'c', 'cpp', 'h', 'hpp', 'sh', 'bash', 'zsh', 'fish', - // Config - 'txt', 'xml', 'toml', 'ini', 'env', + // Config and schemas + 'txt', 'xml', 'xsd', 'xsl', 'dtd', 'toml', 'ini', 'env', // Web 'html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte', @@ -178,6 +178,9 @@ export function getLanguageForHighlight(fileName: string): string | null { yml: 'yaml', toml: 'toml', xml: 'xml', + xsd: 'xml', + xsl: 'xml', + dtd: 'xml', ini: 'ini', // Web files