Llm Data Leakage in Adonisjs with Api Keys
Llm Data Leakage in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
AdonisJS applications that expose HTTP API keys to large language model (LLM) endpoints can inadvertently leak sensitive credentials through prompts, tool usage, or generated code. When API keys are embedded in route handlers, environment files, or service classes and an LLM endpoint is reachable from the application, the risk of data leakage arises from how prompts are constructed and how LLM responses are handled.
Consider a route that forwards user input to an LLM for code suggestions while also attaching an API key for another service:
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { OpenAI } from 'openai'
export default class AiController {
public async chatWithCompletion({ request, response }: HttpContextContract) {
const userMessage = request.input('message')
const apiKey = process.env.OPENAI_API_KEY // sensitive key
const openai = new OpenAI({ apiKey })
const chatCompletion = await openai.chat.completions.create({
messages: [{ role: 'user', content: userMessage }],
model: 'gpt-3.5-turbo',
})
return response.json(chatCompletion.choices[0]?.message?.content)
}
}
If the developer includes the API key in the prompt (e.g., to authorize downstream calls) or logs full request/response pairs, the key can be exposed through LLM outputs, cached traces, or error messages. For example, passing the key explicitly in the prompt:
const chatCompletion = await openai.chat.completions.create({
messages: [
{ role: 'system', content: `Use API key ${process.env.OPENAI_API_KEY} for authenticated requests.` },
{ role: 'user', content: userMessage },
],
model: 'gpt-3.5-turbo',
})
This pattern risks leaking the key in LLM responses if the model echoes system instructions or if output scanning is not applied. Similarly, logging prompts for debugging can persist keys in application logs that may be accessed by unauthorized parties.
AdonisJS applications using environment-based configuration must ensure that sensitive values are never interpolated into prompts or exposed through tool schemas. The framework’s config system should treat API keys as runtime secrets, referenced via process.env, and never serialized into user-facing contexts. Without explicit guardrails, an LLM endpoint that accepts user-influenced prompts can become an inadvertent exfiltration channel for API keys, especially when combined with insufficient output validation or permissive CORS settings.
Effective mitigation combines input validation, strict prompt construction, and runtime output inspection to prevent sensitive data from appearing in LLM interactions.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on isolating API keys from prompt content, avoiding their inclusion in logs, and validating LLM outputs. Below are concrete patterns for AdonisJS that reduce the risk of LLM-driven data leakage.
1) Keep API keys out of prompts and system messages:
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { OpenAI } from 'openai'
export default class AiController {
public async chatCompletion({ request, response }: HttpContextContract) {
const userMessage = request.input('message')
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const chatCompletion = await openai.chat.completions.create({
messages: [{ role: 'user', content: userMessage }],
model: 'gpt-3.5-turbo',
})
return response.json(chatCompletion.choices[0]?.message?.content)
}
}
2) Use AdonisJS config to centralize secrets and avoid inline references:
// config/openai.ts
export default {
apiKey: process.env.OPENAI_API_KEY,
}
// In controller
import Config from '@ioc:Adonis/Core/Config'
const apiKey = Config.get('openai.apiKey')
const openai = new OpenAI({ apiKey })
3) Validate and sanitize LLM outputs before use:
export default class AiController {
public async safeCompletion({ request, response }: HttpContextContract) {
const userMessage = request.input('message')
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const chatCompletion = await openai.chat.completions.create({
messages: [{ role: 'user', content: userMessage }],
model: 'gpt-3.5-turbo',
})
const raw = chatCompletion.choices[0]?.message?.content || ''
if (this.containsSensitiveData(raw)) {
return response.badRequest({ error: 'Invalid response from LLM' })
}
return response.json({ content: raw })
}
private containsSensitiveData(text: string): boolean {
const apiKeyPattern = /\b[a-zA-Z0-9_\-]{30,}\b/ // naive example; use stricter checks
return apiKeyPattern.test(text)
}
}
4) Audit and restrict logging to avoid persisting keys:
// Avoid logging full messages or API keys
export default class ApiLogger {
public static logRequest(path: string, userMessage: string) {
// Log only non-sensitive metadata
console.info({ path, timestamp: new Date().toISOString(), userMessageHash: require('crypto').createHash('sha256').update(userMessage).digest('hex') })
}
}
5) Enforce environment-level protections and CI checks:
- Use .env files with proper .gitignore to prevent commits of API keys.
- Add a pre-commit hook or CI check to detect accidental key patterns in code.
These practices help ensure API keys remain server-side secrets and are not exposed through LLM interactions or application artifacts.
Related CWEs: llmSecurity
| CWE ID | Name | Severity |
|---|---|---|
| CWE-754 | Improper Check for Unusual or Exceptional Conditions | MEDIUM |