Browse Source

fix: tighten dashboard cors and backup gitignore defaults

lingfengQAQ 3 weeks ago
parent
commit
367755364e

+ 13 - 1
webnovel-writer/dashboard/app.py

@@ -28,6 +28,14 @@ _project_root: Path | None = None
 _watcher = FileWatcher()
 
 STATIC_DIR = Path(__file__).parent / "frontend" / "dist"
+LOCAL_CORS_ORIGINS = [
+    "http://localhost",
+    "http://localhost:5173",
+    "http://localhost:8000",
+    "http://127.0.0.1",
+    "http://127.0.0.1:5173",
+    "http://127.0.0.1:8000",
+]
 
 
 def _get_project_root() -> Path:
@@ -248,7 +256,7 @@ def create_app(project_root: str | Path | None = None) -> FastAPI:
 
     app.add_middleware(
         CORSMiddleware,
-        allow_origins=["*"],
+        allow_origins=LOCAL_CORS_ORIGINS,
         allow_methods=["GET"],
         allow_headers=["*"],
     )
@@ -811,6 +819,10 @@ def create_app(project_root: str | Path | None = None) -> FastAPI:
         if not resolved.is_file():
             raise HTTPException(404, "文件不存在")
 
+        max_bytes = 2 * 1024 * 1024
+        if resolved.stat().st_size > max_bytes:
+            raise HTTPException(413, "文件过大,无法预览")
+
         # 文本文件直接读;其他情况返回占位信息
         try:
             content = resolved.read_text(encoding="utf-8")

+ 5 - 0
webnovel-writer/scripts/backup_manager.py

@@ -118,6 +118,11 @@ __pycache__/
 # Don't ignore .webnovel (we need to track state.json)
 # But ignore cache files
 .webnovel/context_cache.json
+
+# Env files
+.env
+.env.*
+!.env.example
 """)
 
             # 初始提交

+ 23 - 0
webnovel-writer/scripts/tests/test_backup_manager.py

@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+import subprocess
+
+import backup_manager
+from backup_manager import GitBackupManager
+
+
+def test_backup_manager_gitignore_excludes_env(tmp_path, monkeypatch):
+    def fake_run(args, cwd=None, check=False, capture_output=False, text=False, encoding=None, timeout=None):
+        if args == ["git", "init"]:
+            (tmp_path / ".git").mkdir(exist_ok=True)
+        return subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
+
+    monkeypatch.setattr(backup_manager, "is_git_available", lambda: True)
+    monkeypatch.setattr(backup_manager.subprocess, "run", fake_run)
+
+    GitBackupManager(str(tmp_path))
+
+    gitignore = (tmp_path / ".gitignore").read_text(encoding="utf-8")
+    assert ".env" in gitignore
+    assert ".env.*" in gitignore
+    assert "!.env.example" in gitignore

+ 66 - 0
webnovel-writer/scripts/tests/test_dashboard_security.py

@@ -0,0 +1,66 @@
+from __future__ import annotations
+
+import importlib
+import sys
+from pathlib import Path
+
+from fastapi.testclient import TestClient
+
+
+def _create_dashboard_client(monkeypatch, project_root: Path) -> TestClient:
+    plugin_root = Path(__file__).resolve().parents[2]
+    if str(plugin_root) not in sys.path:
+        monkeypatch.syspath_prepend(str(plugin_root))
+
+    for name in list(sys.modules):
+        if name == "dashboard.app":
+            sys.modules.pop(name, None)
+
+    module = importlib.import_module("dashboard.app")
+    return TestClient(module.create_app(project_root))
+
+
+def test_dashboard_cors_allows_localhost_origin(monkeypatch, tmp_path):
+    (tmp_path / ".webnovel").mkdir(parents=True)
+    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
+    client = _create_dashboard_client(monkeypatch, tmp_path)
+
+    response = client.options(
+        "/api/project/info",
+        headers={
+            "Origin": "http://localhost:5173",
+            "Access-Control-Request-Method": "GET",
+        },
+    )
+
+    assert response.headers["access-control-allow-origin"] == "http://localhost:5173"
+
+
+def test_dashboard_cors_rejects_untrusted_origin(monkeypatch, tmp_path):
+    (tmp_path / ".webnovel").mkdir(parents=True)
+    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
+    client = _create_dashboard_client(monkeypatch, tmp_path)
+
+    response = client.options(
+        "/api/project/info",
+        headers={
+            "Origin": "https://example.com",
+            "Access-Control-Request-Method": "GET",
+        },
+    )
+
+    assert "access-control-allow-origin" not in response.headers
+
+
+def test_dashboard_file_read_rejects_large_files(monkeypatch, tmp_path):
+    (tmp_path / ".webnovel").mkdir(parents=True)
+    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
+    prose_dir = tmp_path / "正文"
+    prose_dir.mkdir()
+    large_file = prose_dir / "huge.md"
+    large_file.write_bytes(b"x" * (2 * 1024 * 1024 + 1))
+    client = _create_dashboard_client(monkeypatch, tmp_path)
+
+    response = client.get("/api/files/read", params={"path": "正文/huge.md"})
+
+    assert response.status_code == 413