From 212da40b046e5ca254855d13ea65967148f8cb73 Mon Sep 17 00:00:00 2001 From: Jenner Torrence Date: Thu, 5 Nov 2026 21:07:43 -0500 Subject: [PATCH 1/2] docs(qdrant): add deployment inventory, Compose binding and runtime triage Document that source and editable checkouts load .env.local, .env and .env.native as Qdrant client credential sources, and add a scoped consumer-inventory rule (credential source names only; unknown is not none). Bind the live readback to the running container through the Compose config-hash label, and check the backup against its SHA-256 manifest before a restore. State that synthetic CRUD is not semantic search acceptance. Add a dated 2026-11-05 re-scan to the dependency triage: no new advisories, the certifi 2026.10.19 guidance, and why starlette and certifi advisories do not apply to pre-upgrade lexical STDIO installs. Refs #106 Co-Authored-By: Claude Opus 5.5 --- docs/operations/qdrant-authentication.md | 32 +++++++++++++++++-- docs/security/dependency-triage-2026-10-31.md | 24 ++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/docs/operations/qdrant-authentication.md b/docs/operations/qdrant-authentication.md index c0eac56c..2e20d5a6 100644 --- a/docs/operations/qdrant-authentication.md +++ b/docs/operations/qdrant-authentication.md @@ -53,6 +53,18 @@ Not migrated: now fails closed when a server URL is used without the key. The repository ships no Qdrant server manifest for Kubernetes. +### Deployment consumer inventory + +The list above covers clients in this repository. Each deployment also needs its own +inventory. Record every client that can reach the deployment, whether it is running, +scheduled, dormant or unreachable to the operator. For each client, record: owner, state +(active/inactive/unknown), the client artifact and interpreter, mode (server, file or +memory), the credential **source name** (never the value), the transport, whether it +needs read or write access, its test receipt and the remaining action. A client you +cannot inspect, such as another user's configuration or a remote host behind a tunnel, +stays `unknown` with a named owner. Finding no running client does not show that none +exists. + ## Configuration - `QDRANT_API_KEY`: the key. Use at least 32 random bytes, for example @@ -82,12 +94,17 @@ Plan for downtime. `QDRANT__SERVICE__API_KEY`. Print only the variable names, never the values. 2. Back up the storage. Take a Qdrant snapshot of each collection with the current credentials, or stop the container and copy the storage directory. Verify the - backup's size and file count before you continue. + backup's size and file count, and record a SHA-256 manifest of its files, before you + continue. 3. Provision the key in the secret manager. Do not create or rotate it without owner approval. 4. Update every client environment that sets `QDRANT_URL` with the same `QDRANT_API_KEY`: MCP server `.mcp.env` and `.mcp.json` entries, CI or scheduled - jobs if any, and operator shells. + jobs if any, and operator shells. Source and editable checkouts also load + `.env.local`, `.env` and `.env.native` from the checkout root + (`mcp_server/config/environment.py` `load_local_env_files`) unless + `PYTHON_DOTENV_DISABLED=1` is set. Values already in the process environment win. + Treat a `QDRANT_API_KEY` or `QDRANT_URL` in those files as a client credential source. 5. Restart Qdrant through the repository Compose file with the key in the environment: `op run --env-file= -- docker compose -f docker-compose.qdrant.yml [-f ] up -d qdrant`. In the merged model, `environment` takes precedence over `env_file`, so a host @@ -100,6 +117,11 @@ Plan for downtime. counts are present. - `mcp-index setup semantic` preflight reports Qdrant `ready`. - A semantic search returns results. + - The running container matches the reviewed Compose inputs: its + `com.docker.compose.config-hash` label equals the hash printed by + `docker compose config --hash qdrant`. That command + prints only a hash. Do not run `docker compose config` without `--hash`, because + it renders the key. 7. Restart the MCP clients and confirm that readiness is `ready`. ## Rollback @@ -115,7 +137,8 @@ the unsafe state that #106 removes. - **Qdrant does not start (guard exit 64, or a Compose render error):** the key did not reach the container. Fix the injection and run the restart again. The data on disk has not changed. -- **Data problem after the restart:** stop the container, restore the backup from +- **Data problem after the restart:** stop the container, move the failed storage + aside (keep it), check the backup against its SHA-256 manifest, restore the backup from step 2 into the storage path, and start it again with the API key still required. - **Code regression:** revert this change's commits. The authenticated server configuration can stay in place, because the previous client code sends @@ -136,5 +159,8 @@ the unsafe state that #106 removes. It skips when Docker or the pinned image is unavailable: `uv run --extra dev pytest tests/integration/test_qdrant_auth_container.py -m integration --no-cov`. +Synthetic create, write, query and delete checks prove authentication only. They do not +show that embedding-backed MCP semantic search works. Track that check separately. + A source change does not prove that a running deployment is repaired. Only the readback in step 6 against the live service shows that. diff --git a/docs/security/dependency-triage-2026-10-31.md b/docs/security/dependency-triage-2026-10-31.md index 83985526..50455baf 100644 --- a/docs/security/dependency-triage-2026-10-31.md +++ b/docs/security/dependency-triage-2026-10-31.md @@ -72,3 +72,27 @@ advisories. starlette still matches 4 advisories, all listed as not applicable b - The installed and deployed versions on operator hosts were not inventoried. A host that installed before this change keeps starlette 1.0.0 and the old certifi until it runs `uv sync --locked` again or the image is rebuilt. + +## Re-scan and runtime applicability (2026-11-05) + +- Re-scan: `uv.lock` at `1a61533`, same export and scanner (`pip-audit` 2.10.1), advisory + data as of 2026-11-05. The result is 393 rows in 35 packages and 278 distinct advisories. + Every package's distinct advisory count equals the count in the tables above. The only + difference is that the two advisories fixed by the starlette and certifi upgrades are + gone. No new advisory applies, and every disposition above stands. +- certifi: GHSA-6q48-6c7w-9gw2 now recommends 2026.10.19 or later. 2026.10.18 removed every + emSign root, and 2026.10.19 restored the roots that are not affected. The locked + 2026.10.18 is outside the vulnerable range. The difference affects only availability for + servers chained to the restored roots, so there is no security action. Pick it up with the + next routine lock refresh. +- Installed lexical STDIO deployments that predate the upgrade keep starlette 1.0.0 and + certifi 2026.2.25. In that mode neither advisory applies: + - `python -m mcp_server.cli stdio` imports Starlette (through the MCP SDK) but builds + no ASGI application. It never imports `mcp_server.gateway` or + `mcp_server.security.security_middleware`, so `AuthenticationMiddleware` is never + instantiated and no HTTP listener reads `request.url`. + - With semantic search disabled and no summarization provider key in the process + environment, no HTTPS client runs, so the certifi bundle has no reachable consumer. + - Reopen this assessment, or reinstall from the current lock, before such a host + serves the HTTP gateway (`mcp-index serve` or `uvicorn mcp_server.gateway:app`), + enables semantic search or reranking, or passes a summarization provider key. From 2b94466ce85fa2e63c96f594b5a3f52b784d01c6 Mon Sep 17 00:00:00 2001 From: Jenner Torrence Date: Fri, 6 Nov 2026 10:56:27 -0500 Subject: [PATCH 2/2] docs(qdrant): address review of deployment inventory and triage - Make the semantic search check optional and separate from the auth readback. - Bind the config-hash check to step 5's exact op-run/compose invocation. - Note that PYTHON_DOTENV_DISABLED needs python-dotenv 1.2+ (locked 1.2.2). - Name the repository's AuthenticationMiddleware, add reranking to the no-HTTPS-client conditions, state the count-based re-scan basis, and point the residual-risk bullet at the 2026-11-05 host addendum. Refs #106 Co-Authored-By: Claude Opus 5.5 --- docs/operations/qdrant-authentication.md | 17 +++++++++----- docs/security/dependency-triage-2026-10-31.md | 22 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/operations/qdrant-authentication.md b/docs/operations/qdrant-authentication.md index 2e20d5a6..9359ffba 100644 --- a/docs/operations/qdrant-authentication.md +++ b/docs/operations/qdrant-authentication.md @@ -103,7 +103,9 @@ Plan for downtime. jobs if any, and operator shells. Source and editable checkouts also load `.env.local`, `.env` and `.env.native` from the checkout root (`mcp_server/config/environment.py` `load_local_env_files`) unless - `PYTHON_DOTENV_DISABLED=1` is set. Values already in the process environment win. + `PYTHON_DOTENV_DISABLED=1` is set. python-dotenv itself honours that variable from + version 1.2 (the lock pins 1.2.2); an install that does not use `uv.lock` can resolve + an older python-dotenv that ignores it. Values already in the process environment win. Treat a `QDRANT_API_KEY` or `QDRANT_URL` in those files as a client credential source. 5. Restart Qdrant through the repository Compose file with the key in the environment: `op run --env-file= -- docker compose -f docker-compose.qdrant.yml [-f ] up -d qdrant`. @@ -116,12 +118,15 @@ Plan for downtime. - `GET /collections` with the key returns 200, and the expected collections and point counts are present. - `mcp-index setup semantic` preflight reports Qdrant `ready`. - - A semantic search returns results. + - If semantic search is enabled for this deployment, run a semantic search as a + separate check (see below). It is not part of the authentication readback. - The running container matches the reviewed Compose inputs: its - `com.docker.compose.config-hash` label equals the hash printed by - `docker compose config --hash qdrant`. That command - prints only a hash. Do not run `docker compose config` without `--hash`, because - it renders the key. + `com.docker.compose.config-hash` label equals the hash from step 5's exact command + with `config --hash qdrant` in place of `up -d qdrant`, including the same `op run` + wrapper, `--env-file` and `-f` files. It prints `qdrant `; compare the hash + field. Without the wrapper the command fails, and with a different key value it + reports a different hash, so only a mismatch from the same invocation is drift. + Do not run `docker compose config` without `--hash`, because it renders the key. 7. Restart the MCP clients and confirm that readiness is `ready`. ## Rollback diff --git a/docs/security/dependency-triage-2026-10-31.md b/docs/security/dependency-triage-2026-10-31.md index 50455baf..7fdda62a 100644 --- a/docs/security/dependency-triage-2026-10-31.md +++ b/docs/security/dependency-triage-2026-10-31.md @@ -69,9 +69,10 @@ advisories. starlette still matches 4 advisories, all listed as not applicable b - Applicability was judged from the source at this commit. New uses of SDK HTTP transports, forms, `PyJWK`, chunker repository processing or async Voyage clients must reopen the matching rows. -- The installed and deployed versions on operator hosts were not inventoried. A host - that installed before this change keeps starlette 1.0.0 and the old certifi until it - runs `uv sync --locked` again or the image is rebuilt. +- Installed and deployed versions on operator hosts were not inventoried here; one + installed lexical STDIO host is assessed in the 2026-11-05 addendum below. Any other + host that installed before this change keeps starlette 1.0.0 and the old certifi until + it runs `uv sync --locked` again or the image is rebuilt. ## Re-scan and runtime applicability (2026-11-05) @@ -79,7 +80,9 @@ advisories. starlette still matches 4 advisories, all listed as not applicable b data as of 2026-11-05. The result is 393 rows in 35 packages and 278 distinct advisories. Every package's distinct advisory count equals the count in the tables above. The only difference is that the two advisories fixed by the starlette and certifi upgrades are - gone. No new advisory applies, and every disposition above stands. + gone. This comparison is by per-package count, not by advisory ID; a GitHub advisory + database check found no advisory published since 2026-10-31 against a locked version. + Every disposition above stands. - certifi: GHSA-6q48-6c7w-9gw2 now recommends 2026.10.19 or later. 2026.10.18 removed every emSign root, and 2026.10.19 restored the roots that are not affected. The locked 2026.10.18 is outside the vulnerable range. The difference affects only availability for @@ -89,10 +92,13 @@ advisories. starlette still matches 4 advisories, all listed as not applicable b certifi 2026.2.25. In that mode neither advisory applies: - `python -m mcp_server.cli stdio` imports Starlette (through the MCP SDK) but builds no ASGI application. It never imports `mcp_server.gateway` or - `mcp_server.security.security_middleware`, so `AuthenticationMiddleware` is never - instantiated and no HTTP listener reads `request.url`. - - With semantic search disabled and no summarization provider key in the process - environment, no HTTPS client runs, so the certifi bundle has no reachable consumer. + `mcp_server.security.security_middleware`, so the repository's + `AuthenticationMiddleware` is never instantiated (Starlette's class of the same name + is imported with the SDK, but no ASGI application is built) and no HTTP listener + reads `request.url`. + - With semantic search and reranking disabled and no summarization provider key in + the process environment, no HTTPS client runs, so the certifi bundle has no + reachable consumer. - Reopen this assessment, or reinstall from the current lock, before such a host serves the HTTP gateway (`mcp-index serve` or `uvicorn mcp_server.gateway:app`), enables semantic search or reranking, or passes a summarization provider key.