diff --git a/skills/toy/references/content-checklist.md b/skills/toy/references/content-checklist.md index b989bd5..6a45547 100644 --- a/skills/toy/references/content-checklist.md +++ b/skills/toy/references/content-checklist.md @@ -35,6 +35,15 @@ - 框架项目**只传构建产物**(`dist` / `build`),不要传源码(`src/`、`package.json` 那一坨)。先 `npm run build`,确认产物里有 `index.html`。 - 上传支持 `.zip` / `.html` / `.htm` / 文件夹(文件夹会自动打包)。 - 别把 `.git`、`node_modules`、`__MACOSX`、`.DS_Store` 这类打进包里。 +- **ZIP 包内文件类型白名单**:出于安全考虑,只有白名单内后缀的文件会被发布,其余文件(如可执行文件、后端脚本)会被自动过滤掉。当前支持的后缀: + - 网页与脚本:`.html` `.htm` `.css` `.js` `.json` `.wasm` + - 数据:`.data` `.md` `.csv` `.tsv` + - 图片:`.png` `.jpg` `.jpeg` `.gif` `.svg` `.webp` `.ico` + - 字体:`.woff2` `.woff` `.ttf` `.eot` + - 音频:`.mp3` `.wav` `.ogg` `.m4a` + - 视频:`.mp4` `.webm` + - 其他:`.nani` `.unityweb` + 如果页面依赖被过滤的文件,请改用上述支持的格式或走 CDN 外链。 - 旧项目里的 `toy.yaml` 只作本地兼容线索读取,不是页面资源,别打进上传包(`toy_doctor.py` 已自动排除)。 (官方 FAQ Q6/Q7/Q11。) diff --git a/skills/toy/scripts/toy_doctor.py b/skills/toy/scripts/toy_doctor.py index 559e6a7..90522f6 100755 --- a/skills/toy/scripts/toy_doctor.py +++ b/skills/toy/scripts/toy_doctor.py @@ -27,6 +27,17 @@ CSS_URL_RE = re.compile(r"""url\(\s*(?P["']?)(?P[^'")]+)(?P=quote)\s*\)""", re.I) TITLE_RE = re.compile(r"]*>(.*?)", re.I | re.S) +# ZIP 包内文件类型白名单(服务端自动过滤不合规后缀,此处静态预检提前告警) +_ALLOWED_EXTENSIONS: frozenset[str] = frozenset({ + ".html", ".htm", ".css", ".js", ".json", ".wasm", + ".data", ".md", ".csv", ".tsv", + ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", + ".woff2", ".woff", ".ttf", ".eot", + ".mp3", ".wav", ".ogg", ".m4a", + ".mp4", ".webm", + ".nani", ".unityweb", +}) + @dataclass class Finding: @@ -190,6 +201,18 @@ def validate_index(pkg: StaticPackage, require_root: bool, reporter: Reporter) - reporter.error(".", "missing index.html at root or first-level folder") +def validate_file_extensions(pkg: StaticPackage, reporter: Reporter) -> None: + """检查 ZIP/目录中是否有不在白名单内的文件后缀。""" + for rel in sorted(pkg.files): + ext = PurePosixPath(rel).suffix.lower() + if ext and ext not in _ALLOWED_EXTENSIONS: + reporter.warn( + rel, + f"unsupported file extension {ext}; will be filtered by server — " + "use a supported format or CDN external link instead", + ) + + def validate_framework_source(pkg: StaticPackage, reporter: Reporter) -> None: if "package.json" in pkg.files and any(f.startswith(("src/", "app/", "pages/")) for f in pkg.files): reporter.warn( @@ -346,6 +369,7 @@ def run_checks(args: argparse.Namespace) -> Reporter: if not reporter.has_errors: validate_index(pkg, args.require_root_index, reporter) validate_framework_source(pkg, reporter) + validate_file_extensions(pkg, reporter) for rel in pkg.html_files(): try: check_html(pkg, rel, pkg.read_text(rel), reporter)