From 107b16cc02cb66ce2fc91b1232fa1932b592af3a Mon Sep 17 00:00:00 2001 From: Tasoo Park Date: Mon, 27 Jul 2026 18:11:21 +0900 Subject: [PATCH] serve: wire Host allowlist through CLI --- c/coli | 10 ++++++++-- c/tests/test_cli_output.py | 5 +++++ docs/ENVIRONMENT.md | 1 + docs/SETTINGS.md | 2 ++ docs/api.md | 13 +++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/c/coli b/c/coli index 9f6cf667..0f331bde 100755 --- a/c/coli +++ b/c/coli @@ -847,7 +847,7 @@ def cmd_serve(a): try: serve(a.model, a.host, a.port, a.model_id, a.api_key, a.cap,a.ngen,GLM,env_for(a),a.cors_origin, - a.max_queue,a.queue_timeout,a.kv_slots) + a.max_queue,a.queue_timeout,a.kv_slots,allowed_hosts=a.allowed_host) finally: try: os.unlink(serve_pidfile(a.port)) except OSError: pass @@ -1011,6 +1011,9 @@ def main(): ps.add_argument("--max-queue",type=int,default=int(os.environ.get("COLI_MAX_QUEUE","8"))) ps.add_argument("--queue-timeout",type=float,default=float(os.environ.get("COLI_QUEUE_TIMEOUT","300"))) ps.add_argument("--kv-slots",type=int,default=int(os.environ.get("COLI_KV_SLOTS","1"))) + ps.add_argument("--allowed-host",action="append", + default=[h.strip() for h in os.environ.get("COLI_ALLOWED_HOSTS","").split(",") if h.strip()], + help="additional Host header accepted by the DNS-rebinding guard; repeat as needed") pst=sub.add_parser("stop", parents=[common], help="shut down a running coli serve and its engine") pst.add_argument("--port",type=int,default=8000); pst.add_argument("--dry-run",action="store_true") pw=sub.add_parser("web", parents=[common], help="serve + open the dashboard in a browser") @@ -1020,7 +1023,10 @@ def main(): ("--cors-origin",dict(action="append",default=None)), ("--max-queue",dict(type=int,default=int(os.environ.get("COLI_MAX_QUEUE","8")))), ("--queue-timeout",dict(type=float,default=float(os.environ.get("COLI_QUEUE_TIMEOUT","300")))), - ("--kv-slots",dict(type=int,default=int(os.environ.get("COLI_KV_SLOTS","1"))))): + ("--kv-slots",dict(type=int,default=int(os.environ.get("COLI_KV_SLOTS","1")))), + ("--allowed-host",dict(action="append", + default=[h.strip() for h in os.environ.get("COLI_ALLOWED_HOSTS","").split(",") if h.strip()], + help="additional Host header accepted by the DNS-rebinding guard; repeat as needed"))): pw.add_argument(arg,**kw) pw.add_argument("--no-browser",action="store_true",help="don't auto-open the browser") pb=sub.add_parser("bench", parents=[common]); pb.add_argument("tasks", nargs="*") diff --git a/c/tests/test_cli_output.py b/c/tests/test_cli_output.py index 77fc86a0..efefadf5 100644 --- a/c/tests/test_cli_output.py +++ b/c/tests/test_cli_output.py @@ -28,6 +28,11 @@ def test_help_is_english(self): self.assertNotIn("modello", result.stdout.lower()) self.assertNotIn("motore", result.stdout.lower()) + def test_serve_help_includes_allowed_host(self): + result = self.run_cli("serve", "--help") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("--allowed-host", result.stdout) + def test_info_status_is_english(self): with tempfile.TemporaryDirectory() as model: result = self.run_cli("info", "--model", model) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 7854e195..b2328591 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -173,6 +173,7 @@ These are read by the Python programs (not the `glm` engine), so they don't appe | `COLI_MODEL` | unset | Default model directory (fallback for `--model`). | | `COLI_MODEL_ID` | `glm-5.2-colibri` | Model id reported by the API. | | `COLI_API_KEY` | unset | Required bearer token for the server. | +| `COLI_ALLOWED_HOSTS` | unset | Comma-separated hostnames or IP addresses accepted by the DNS-rebinding guard in addition to loopback and the bind address. Equivalent to repeating `--allowed-host`. | | `COLI_MAX_QUEUE` | `8` | Max queued requests. | | `COLI_QUEUE_TIMEOUT` | `300` | Seconds a request may wait in the queue. | | `COLI_KV_SLOTS` | `1` | Independent KV conversation slots (→ engine `KV_SLOTS`). | diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 131fafc6..89b836b2 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -57,6 +57,7 @@ Flags may also be given **after** the subcommand. Most flags map onto an engine | `--model-id` | `$COLI_MODEL_ID` or `glm-5.2-colibri` | Model id reported by the API. | | `--api-key` | `$COLI_API_KEY` | Require this bearer token. | | `--cors-origin` | none (repeatable) | Allowed CORS origin(s). | +| `--allowed-host` | `$COLI_ALLOWED_HOSTS` or none (repeatable) | Additional Host header accepted by the DNS-rebinding guard. | | `--max-queue` | `$COLI_MAX_QUEUE` or `8` | Max queued requests. | | `--queue-timeout` | `$COLI_QUEUE_TIMEOUT` or `300` | Seconds a request may wait. | | `--kv-slots` | `$COLI_KV_SLOTS` or `1` | Independent KV conversation slots (→ `KV_SLOTS`). | @@ -94,6 +95,7 @@ Run directly (or via `coli serve`). OpenAI-compatible `/v1/chat/completions`. | `--model-id` | `$COLI_MODEL_ID` or `glm-5.2-colibri` | Model id in API responses. | | `--api-key` | `$COLI_API_KEY` | Required bearer token. | | `--cors-origin` | none (repeatable) | Allowed CORS origin(s). | +| `--allowed-host` | `$COLI_ALLOWED_HOSTS` or none (repeatable) | Additional Host header accepted by the DNS-rebinding guard. | | `--cap` | `8` | Expert-cache cap. | | `--max-tokens` | `1024` | Default max completion tokens. | | `--max-queue` | `$COLI_MAX_QUEUE` or `8` | Max queued requests. | diff --git a/docs/api.md b/docs/api.md index 3f5f86b8..4daf784b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -46,6 +46,19 @@ and token penalties return an explicit error rather than being silently ignored. The default bind address is localhost; set `COLI_API_KEY` before exposing the server beyond the machine. +When a reverse proxy or MagicDNS hostname preserves a public `Host` header, +trust that exact hostname with repeatable `--allowed-host` options. The +comma-separated `COLI_ALLOWED_HOSTS` environment variable is equivalent: + +```bash +COLI_ALLOWED_HOSTS=llm.example.com ./coli serve --model /nvme/glm52_i4 +# or: ./coli serve --model /nvme/glm52_i4 --allowed-host llm.example.com +``` + +Only configure hostnames or IP addresses you control; there is no wildcard. +This setting extends the DNS-rebinding allowlist and is independent of CORS and +API-key authentication. + Browser access from the Vite development server and Tauri local origins is enabled by default. Repeat `--cors-origin https://your-ui.example` to allow another exact origin, or use `--cors-origin '*'` only on a trusted local