diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index c684f64..1861761 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -59,3 +59,7 @@ This intentionally changes the previous ability to pass arbitrary remote URLs. T The local Spring Boot `/files/upload` and `/files/editor/upload` endpoints now discard directory components from browser-supplied filenames, reject empty/control-character/oversized names with HTTP 400, and stream upload bytes into the configured local `backend/files/` directory instead of loading whole files into memory. Returned upload URLs encode the stored filename. Downloads match either the exact generated filename or the generated UUID followed by an underscore, avoiding arbitrary substring matches. **Scope:** This is path and memory hygiene for a local prototype, **not** user authentication or upload content validation. Limit public access, review content-type restrictions, enforce quotas and scan untrusted files before production use. Existing previously uploaded files are not renamed. + +## Local video progress WebSocket + +The Flask-SocketIO progress channel accepts browser origins only from `http://localhost:8100` and `http://127.0.0.1:8100`; wildcard CORS has been removed. This matches the loopback-only Vite development server. If you use a different frontend origin, do **not** remove the restriction by setting `*`; implement a deployment-specific authenticated origin and access policy instead. An allowed browser Origin is not proof of a trusted user. diff --git a/inference/main.py b/inference/main.py index 7cd2511..3d62977 100644 --- a/inference/main.py +++ b/inference/main.py @@ -8,6 +8,7 @@ from predict import predictImg from input_validation import resolve_weight_file, validate_local_media_url from image_requests import process_image +from socket_policy import LOCAL_VITE_ORIGINS from flask_socketio import SocketIO, emit @@ -16,7 +17,7 @@ class VideoProcessingApp: def __init__(self, host='127.0.0.1', port=5000): """初始化 Flask 应用并设置路由""" self.app = Flask(__name__) - self.socketio = SocketIO(self.app, cors_allowed_origins="*") # 初始化 SocketIO + self.socketio = SocketIO(self.app, cors_allowed_origins=list(LOCAL_VITE_ORIGINS)) self.host = host self.port = port self.setup_routes() diff --git a/inference/socket_policy.py b/inference/socket_policy.py new file mode 100644 index 0000000..cc59e9f --- /dev/null +++ b/inference/socket_policy.py @@ -0,0 +1,10 @@ +"""Allowed browser origins for CropLens' localhost-only Socket.IO demo. + +This is origin-based defense-in-depth, not authentication. Keep the +inference server on loopback; never treat this as a public access policy. +""" + +LOCAL_VITE_ORIGINS = ( + "http://localhost:8100", + "http://127.0.0.1:8100", +) diff --git a/inference/tests/test_socket_policy.py b/inference/tests/test_socket_policy.py new file mode 100644 index 0000000..f113a4a --- /dev/null +++ b/inference/tests/test_socket_policy.py @@ -0,0 +1,22 @@ +"""Regression guardrails for the local demo's websocket Origin allowlist.""" +import unittest + +from socket_policy import LOCAL_VITE_ORIGINS + + +class SocketOriginPolicyTests(unittest.TestCase): + def test_only_local_vite_origins_are_allowed(self): + self.assertEqual( + set(LOCAL_VITE_ORIGINS), + {"http://localhost:8100", "http://127.0.0.1:8100"}, + ) + + def test_no_wildcard_or_remote_origin(self): + for origin in LOCAL_VITE_ORIGINS: + self.assertNotIn("*", origin) + self.assertNotIn("0.0.0.0", origin) + self.assertNotIn("https://", origin) + + +if __name__ == "__main__": + unittest.main()