Skip to content

fix(models): the gpu-orchestrator's compose up goes through the install security check; 4b, embed and the CUDA model bundles drop ipc: host - #543

Open
kh0pper wants to merge 6 commits into
mainfrom
fix/item30-a
Open

kh0pper wants to merge 6 commits into
mainfrom
fix/item30-a

Conversation

@kh0pper

@kh0pper kh0pper commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Follow-up (a) to #508, recorded in queue item 30. Stacked on #509, so merge #509 first. Once #509 lands, this branch is rebased onto main, so only its own commits remain.

The problem. servers/gateway/gpu-orchestrator.js started model bundles with docker compose up from the repo's compose file. That path skipped the install security check (compose-guard.js), and it passed the gateway's whole environment, secrets included, to Compose.

What changes

  • The orchestrator runs only model bundles. These are bundles whose repo manifest says inference: true and names the same id. A provider row synced from the fleet can name any id, so the row alone isn't trusted.
  • One invocation for the check and the start. Every up is checked first by the shared model check. The check sees the exact model that up will apply: the same -f file, the same working folder, and the gateway's allowlisted compose env (no secrets, no COMPOSE_*). stop uses the same invocation.
  • The check runs before eviction. It happens before any mutex sibling is stopped, so a refused start never costs the box its resident model. A refusal throws ComposeGuardError and nothing starts.
  • A manifest declares what a model bundle needs. The new host_grants field lists gpu, shm_size, env_files and path_env. It is read from the repo manifest only, never from a community bundle or an installed copy.
    • GPU devices, gpus, GPU reservations, runtime: nvidia and NVIDIA_* env need host_grants.gpu.
    • A /dev/shm above 1 GiB needs host_grants.shm_size.
    • An env_file outside the bundle is allowed only if it is declared and sits in ~/.crow/env. inference: true no longer opens any host file by itself.
    • The folders named by path_env keys (for example LLM_CACHE) come from the bundle's .env in the checkout and are passed as the check's operator roots. Since fix(bundles): outside Crow's home, extensions mount only folders the owner allowed #524, a mount outside Crow's home must sit under one of them, and the floor still applies: system folders, home dot-folders and ~/bin, a git checkout, and anything containing Crow's home stay refused.
  • The env_file checks fail closed.
    • Any NVIDIA_ mention in an admitted env_file needs the gpu grant. This covers any case, separator, quoting or line ending.
    • The file is checked before it is opened, so a device or FIFO is never opened. Only a regular file is read, up to 1 MiB.
    • A missing env_file is refused unless it is marked required: false.
    • An env_file under a read-write bind of the same project is refused, because a container could swap it before Compose reads it.
  • More bundles drop ipc: host. The 4b, embed, vllm-cuda-embed, vllm-cuda-rerank and vllm-cuda-vision bundles drop it and keep their private /dev/shm at shm_size. Each is one container, so the host IPC namespace only matters for sharing with other containers, and there are none. fix(models): the 35B bundle drops ipc: host (follow-up to #508) #509 drops the 35B's.
  • composeEnv moves to compose-guard.js. The routes keep a thin wrapper.
  • Versions and grants:

⚠ Merge inside a scheduled model window.

  • This PR changes the 4b and embed compose files. Once it is pulled, the next orchestrator up of either container recreates it. That happens at their next start, not at merge time: a resident model is never re-upped.
  • So, after merging, check in the window that both come up healthy:
    • 4b answers a completion;
    • embed answers an embedding with 1024 dims.
  • The gateway needs a restart to run the new orchestrator code.
  • A read-only check on a live host (docker compose config only) ran this branch's check on the three crow model bundles with that host's real .env values. All three pass and come out with a private IPC namespace.

Tests

  • tests/compose-guard.test.js: grants, env_file handling, and every repo model bundle passing the check as the orchestrator runs it.
  • New tests/gpu-orchestrator-compose-guard.test.js: covers these cases.
    • A refused bundle is never started.
    • A refused target doesn't evict the resident sibling.
    • The orchestrator runs only inference: true bundles.
    • The compose env carries no gateway secrets.
  • Full suite locally: 10432 tests, 10397 pass, 0 fail, 35 skipped. Registry rebuilt; check-port-allocation passes.

Not in this PR

https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp

…emory with other processes)

The install security check (#508) refuses ipc: host without privileged
consent, and the read-only prod dry run of its Start check flagged this one
bundle. llama-server is a single process (--no-mmap, weights in GPU memory
through Vulkan) and uses no POSIX shared memory with other processes, so
the host IPC namespace is not needed; the private /dev/shm keeps shm_size
16g as headroom. Manifest gets a version (1.0.1).

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…ll security check

- bundleUp/bundleStop run only model bundles (repo manifest inference: true), with the
  gateway's allowlisted compose env (never its secrets or COMPOSE_*), and every up is
  checked by the shared model check first, on exactly the invocation up runs (-f, cwd,
  env). The check runs before any mutex sibling is evicted, so a refused start never
  costs the box its resident model. A refusal is a ComposeGuardError; nothing starts.
- What model bundles need is declared, not loopholed: manifest host_grants (gpu,
  shm_size, env_files, path_env), read from the repo manifest only, never for community
  bundles. GPU devices, gpus, GPU reservations, runtime nvidia and NVIDIA_* env need
  host_grants.gpu; /dev/shm above 1 GiB needs host_grants.shm_size; env_file outside the
  bundle only for a declared, existing file in ~/.crow/env (inference: true no longer
  opens any host file).
- 4b, embed and vllm-cuda-{embed,rerank,vision} drop ipc: host (their private /dev/shm
  is shm_size). The 35B's is dropped by its own PR; this one must merge after it.
- composeEnv moves to compose-guard.js (routes keep their wrapper); loadComposeModel
  takes the caller's -f file and cwd.
- Grants and versions: 4b, embed, vllm-cuda-* 1.0.1; 35B 1.0.2 (grants only); sdxl,
  vllm 1.0.2; ollama, localai 1.0.1. Docs (en/es) list host_grants.

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
… clearer refusal wording

Code review I1: the model is built with --no-env-resolution, so NVIDIA_VISIBLE_DEVICES /
NVIDIA_DRIVER_CAPABILITIES in an env_file never showed up in it. Each env_file the check
admits is now read for them. A refused stop says "stop"; comments no longer name hosts and
say that operatorRoots relaxes nothing until the host-path allowlist uses it.

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
… and only a small regular file

Re-review N1: the NVIDIA_* scan opened every env_file path before the outside-path
refusal, so env_file: /dev/zero (or a FIFO) could block and crash the gateway. The scan
now runs inside the admission loop, after the path is accepted, and reads only a regular
file of at most 1 MiB (anything else is refused). N2: it matches the KEY: value form
Compose accepts, and format: raw keeps quotes literal.

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…ng file, no container-writable file

Final security pass on the install check's env_file handling:
- Any NVIDIA_ mention in an admitted env_file (any case, separator, quoting
  or line ending) needs the gpu grant. The line regex it replaces could
  disagree with Compose's dotenv parser (a lone CR, a NEL prefix).
- The file is stat'ed before it is opened (a device or FIFO is never
  opened), opened non-blocking, checked to be the same regular file, and
  read up to 1 MiB from the descriptor.
- A missing env_file is refused unless it is marked required: false.
- An env_file under a read-write bind or a bind-device volume of the same
  project is refused: a container could swap it before Compose reads it.

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…ass the check; operatorRoots now count (#524)

Stacked on #509, so the test's ipc: host exception for the 35B is dead and is removed: all
model bundles in the repo must pass the install security check as the orchestrator runs it.
The preflight comment said the check ignores ctx.operatorRoots; since #524 the host-path
allowlist uses them (a mount outside Crow's home must sit under one, and the floor applies).

Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant