Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2748,7 +2748,20 @@ async function setupNim(gpu) {
console.log(" Installing Ollama via Homebrew...");
run("brew install ollama", { ignoreError: true });
console.log(" Starting Ollama...");
run("OLLAMA_HOST=0.0.0.0:11434 ollama serve > /dev/null 2>&1 &", { ignoreError: true });
// On macOS, Docker Desktop routes host-gateway through the VM so
// 127.0.0.1 is reachable from containers — bind to localhost to
// avoid exposing Ollama to the LAN (CWE-668, NVBUG 6014821).
// On Linux, containers access the host via the Docker bridge IP
// so 0.0.0.0 is required for reachability.
// On WSL2, the default binding works without override.
let ollamaEnv = "";
if (!isWsl()) {
ollamaEnv =
process.platform === "darwin"
? "OLLAMA_HOST=127.0.0.1:11434 "
: "OLLAMA_HOST=0.0.0.0:11434 ";
Comment on lines +2757 to +2762

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Non-WSL Linux still publishes Ollama off-host.

Line 2650 keeps the default Linux launch path on OLLAMA_HOST=0.0.0.0:11434. A normal nemoclaw onboard on Linux therefore still exposes the Ollama API beyond the host, so this hardening only lands for macOS right now. If Linux is meant to be covered too, this branch needs to stop forcing 0.0.0.0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 2645 - 2650, The code sets ollamaEnv to
"OLLAMA_HOST=0.0.0.0:11434" for non-WSL Linux which exposes the API off-host;
change the non-WSL branch that currently checks process.platform to use a
loopback bind (e.g., "OLLAMA_HOST=127.0.0.1:11434") instead of 0.0.0.0 so Linux
gets the same hardening as macOS; update the ollamaEnv assignment (the variable
ollamaEnv and the surrounding isWsl() / process.platform logic) to default to
127.0.0.1 unless an explicit configuration/flag requires 0.0.0.0.

}
run(`${ollamaEnv}ollama serve > /dev/null 2>&1 &`, { ignoreError: true });
Comment on lines +2751 to +2764

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reuse the bind-selection logic in both Ollama launch paths.

Line 2334 makes install-ollama macOS-only, so the non-darwin branch here is dead. Meanwhile Line 2697 in the regular selected.key === "ollama" flow still launches a stopped Ollama with OLLAMA_HOST=0.0.0.0:11434 on macOS, which reintroduces the LAN exposure this PR is trying to remove.

💡 Suggested fix
+function getOllamaServeEnvPrefix() {
+  if (isWsl()) return "";
+  return process.platform === "darwin"
+    ? "OLLAMA_HOST=127.0.0.1:11434 "
+    : "OLLAMA_HOST=0.0.0.0:11434 ";
+}
...
-          const ollamaEnv = isWsl() ? "" : "OLLAMA_HOST=0.0.0.0:11434 ";
+          const ollamaEnv = getOllamaServeEnvPrefix();
           run(`${ollamaEnv}ollama serve > /dev/null 2>&1 &`, { ignoreError: true });
...
-        let ollamaEnv = "";
-        if (!isWsl()) {
-          ollamaEnv =
-            process.platform === "darwin"
-              ? "OLLAMA_HOST=127.0.0.1:11434 "
-              : "OLLAMA_HOST=0.0.0.0:11434 ";
-        }
+        const ollamaEnv = getOllamaServeEnvPrefix();
         run(`${ollamaEnv}ollama serve > /dev/null 2>&1 &`, { ignoreError: true });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 2751 - 2764, The Ollama bind-selection (the
computation of ollamaEnv using isWsl() and process.platform === "darwin") must
be reused for both launch paths so macOS doesn't get OLLAMA_HOST=0.0.0.0:11434;
update the code that runs Ollama (the run(...) call in the non-install branch
where selected.key === "ollama") to compute and use the same ollamaEnv logic (or
call a small helper function) as the install-ollama path instead of hardcoding
0.0.0.0, referencing the existing isWsl, ollamaEnv variable logic and the
run(...) invocation to locate where to change.

sleep(2);
console.log(" ✓ Using Ollama on localhost:11434");
provider = "ollama-local";
Expand Down
6 changes: 4 additions & 2 deletions spark-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ ollama run nemotron-3-super:120b
# type /bye to exit
```

### 4. Configure Ollama to Listen on All Interfaces
### 4. Configure Ollama to Listen on All Interfaces (Linux only)

By default Ollama binds to `127.0.0.1`, which is not reachable from inside the sandbox container. Configure it to listen on all interfaces:
On Linux, Ollama's default `127.0.0.1` binding is not reachable from inside the sandbox container because Docker containers access the host via the bridge IP, not loopback. Configure Ollama to listen on all interfaces:

> **Note:** `OLLAMA_HOST=0.0.0.0` exposes Ollama on your network. If you're not on a trusted LAN, restrict access with host firewall rules (`ufw`, `iptables`, etc.).
>
> **macOS users:** Docker Desktop routes `host-gateway` through the VM, so the default `127.0.0.1` binding works — skip this step. The NemoClaw onboarding wizard handles this automatically.

```bash
sudo mkdir -p /etc/systemd/system/ollama.service.d
Expand Down
19 changes: 12 additions & 7 deletions src/lib/local-inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ describe("local inference helpers", () => {
});

it("returns the expected base URL for ollama-local", () => {
expect(getLocalProviderBaseUrl("ollama-local")).toBe(
"http://host.openshell.internal:11434/v1",
);
expect(getLocalProviderBaseUrl("ollama-local")).toBe("http://host.openshell.internal:11434/v1");
});

it("returns null for unknown local provider URLs", () => {
Expand Down Expand Up @@ -91,7 +89,13 @@ describe("local inference helpers", () => {
});
expect(result.ok).toBe(false);
expect(result.message).toMatch(/host\.openshell\.internal:11434/);
expect(result.message).toMatch(/0\.0\.0\.0:11434/);
// Platform-aware message: macOS advises restarting Docker Desktop;
// Linux advises binding to 0.0.0.0 (CWE-668 / NVBUG 6014821).
if (process.platform === "darwin") {
expect(result.message).toMatch(/Restart Docker Desktop/);
} else {
expect(result.message).toMatch(/0\.0\.0\.0:11434/);
}
});

it("returns a clear error when vllm-local is unavailable", () => {
Expand Down Expand Up @@ -202,9 +206,10 @@ describe("local inference helpers", () => {
expect(
getBootstrapOllamaModelOptions({ totalMemoryMB: LARGE_OLLAMA_MIN_MEMORY_MB - 1 }),
).toEqual(["qwen2.5:7b"]);
expect(
getBootstrapOllamaModelOptions({ totalMemoryMB: LARGE_OLLAMA_MIN_MEMORY_MB }),
).toEqual(["qwen2.5:7b", DEFAULT_OLLAMA_MODEL]);
expect(getBootstrapOllamaModelOptions({ totalMemoryMB: LARGE_OLLAMA_MIN_MEMORY_MB })).toEqual([
"qwen2.5:7b",
DEFAULT_OLLAMA_MODEL,
]);
expect(getDefaultOllamaModel(() => "", { totalMemoryMB: 16384 })).toBe("qwen2.5:7b");
});

Expand Down
9 changes: 4 additions & 5 deletions src/lib/local-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ export function validateLocalProvider(
return {
ok: false,
message:
"Local Ollama is responding on localhost, but containers cannot reach http://host.openshell.internal:11434. Ensure Ollama listens on 0.0.0.0:11434 instead of 127.0.0.1 so sandboxes can reach it.",
process.platform === "darwin"
? "Local Ollama is responding on localhost, but containers cannot reach http://host.openshell.internal:11434. Restart Docker Desktop and ensure host networking is enabled."
: "Local Ollama is responding on localhost, but containers cannot reach http://host.openshell.internal:11434. Ensure Ollama listens on 0.0.0.0:11434 (not 127.0.0.1) so sandboxes can reach it via the Docker bridge.",
Comment on lines +122 to +124

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle WSL separately in the Ollama reachability hint.

This darwin/else split still sends WSL users to the Linux advice, but bin/lib/onboard.js intentionally leaves OLLAMA_HOST unset on WSL because 0.0.0.0 breaks host-gateway reachability there. If this validation fails on WSL, the message now points users at the wrong remediation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/local-inference.ts` around lines 122 - 124, The current platform
branch uses only darwin vs else and incorrectly directs WSL users to Linux
advice; update the conditional that checks process.platform (the ternary
producing the two hint strings) to detect WSL (e.g., check
process.env.WSL_DISTRO_NAME or process.platform === "linux" &&
/microsoft/i.test(require("os").release())) and add a third branch for WSL with
a specific hint: explain that OLLAMA_HOST should be left unset on WSL because
0.0.0.0 breaks host-gateway reachability and point them to restart Docker
Desktop or use appropriate WSL host networking workarounds (mirroring the intent
in bin/lib/onboard.js). Ensure the darwin and non-WSL linux messages remain
unchanged.

};
default:
return {
Expand Down Expand Up @@ -207,10 +209,7 @@ export function getOllamaProbeCommand(
return `curl -sS --max-time ${timeoutSeconds} http://localhost:11434/api/generate -H 'Content-Type: application/json' -d ${shellQuote(payload)} 2>/dev/null`;
}

export function validateOllamaModel(
model: string,
runCapture: RunCaptureFn,
): ValidationResult {
export function validateOllamaModel(model: string, runCapture: RunCaptureFn): ValidationResult {
const output = runCapture(getOllamaProbeCommand(model), { ignoreError: true });
if (!output) {
return {
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/test-gpu-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Mirrors what a user with a GPU would actually do:
# 1. Install Ollama binary
# 2. Run the NemoClaw installer with NEMOCLAW_PROVIDER=ollama
# 3. Onboard starts Ollama (OLLAMA_HOST=0.0.0.0:11434), pulls model, creates sandbox
# 3. Onboard starts Ollama (OLLAMA_HOST=0.0.0.0:11434 on Linux for container reachability), pulls model, creates sandbox
# 4. Verify inference works through the sandbox
# 5. Destroy + uninstall
#
Expand Down Expand Up @@ -159,10 +159,10 @@ if [ "${NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE:-}" != "1" ]; then
exit 1
fi

# Verify port 11434 is free (onboard needs to start Ollama on 0.0.0.0:11434)
# Verify port 11434 is free (onboard needs to start Ollama on 0.0.0.0:11434 on Linux for container reachability)
if curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
info "WARNING: Something is already listening on port 11434."
info "Onboard may not be able to start Ollama on 0.0.0.0:11434."
info "Onboard may not be able to bind Ollama to 0.0.0.0:11434."
info "On ephemeral runners this should not happen."
# Don't fail — onboard will detect the running Ollama and use it.
# The container reachability check in onboard will catch 127.0.0.1 issues.
Expand All @@ -188,7 +188,7 @@ else
fi

# If the Ollama installer started a system service, stop it so onboard
# can start Ollama with OLLAMA_HOST=0.0.0.0:11434 (required for containers).
# can start Ollama with OLLAMA_HOST=0.0.0.0:11434 (required for container reachability on Linux).
# This needs the ollama process to be owned by our user, or systemctl access.
if curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
info "Ollama service is running — attempting to stop for clean onboard..."
Expand Down
Loading