HIGH ssrfadonisjsapi keys

Ssrf in Adonisjs with Api Keys

Ssrf in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability

Server-Side Request Forgery (SSRF) in AdonisJS when API keys are used for outbound calls can amplify risk by exposing internal services and bypassing intended network boundaries. In many AdonisJS applications, API keys authorize HTTP requests to third‑party services via custom HTTP clients or libraries like axios. If user input (e.g., a URL or host parameter) is used to construct those requests without strict validation, an attacker can leverage a valid API key to make the server reach into internal endpoints that the key is permitted to access.

Consider an AdonisJS service that calls a payment provider with an API key. If the endpoint to call is taken directly from a request parameter and the API key is attached as a bearer token or header, SSRF becomes feasible: the attacker supplies an internal address (e.g., http://169.254.169.254/latest/meta-data/ or http://redis:6379) and the server, carrying the API key, performs the request. Because the API key is trusted, the call may succeed, revealing metadata or reaching internal services not exposed publicly. This pattern is common in integrations that proxy requests or enrich data from external APIs while using API keys for authentication at the target service.

AdonisJS applications that consume OpenAPI specs or generate clients via @adonisjs/axios can inadvertently introduce SSRF if schema-derived parameters are mapped without validation to request targets. For example, an operation defined in an OpenAPI 3.0 spec may accept a server variable that is later concatenated with paths and API keys. If the spec is not rigorously constrained and runtime input is not sanitized, the concatenation can redirect requests internally. Additionally, if the application scans endpoints using tools like middleBrick, which tests unauthenticated attack surfaces, SSRF may surface as a finding when integrations use API keys with broad internal permissions, because the scanner can observe whether user-supplied inputs influence where requests with valid credentials are sent.

Real-world attack patterns include probing cloud metadata services (e.g., 169.254.169.254), internal Kubernetes services, or database endpoints that rely on network-level authentication rather than application authentication. In such scenarios, the presence of API keys does not mitigate SSRF; it may actually increase impact by providing a credential that reaches deeper into the infrastructure. Therefore, SSRF in AdonisJS with API keys centers on insecure composition of user input into request targets, even when those requests carry authorized credentials.

Api Keys-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on strict validation of request targets, avoiding direct use of user input for URLs, and ensuring API keys are scoped and handled safely. Below are concrete AdonisJS code examples demonstrating secure patterns.

1. Validate and whitelist target hosts

Never forward user input as a full URL. Instead, accept an identifier and map it to a pre‑approved base URL.

// resources/validators/payment.ts
import { schema } from '@ioc:Adonis/Core/Validator'

export const paymentSchema = schema.create({
  serviceId: schema.string({}, { format: 'paymentService' })
})

// in controller
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import PaymentService from 'App/Services/PaymentService'

export default class PaymentsController {
  public async pay({ request, response }: HttpContextContract) {
    const body = request.validate({ schema: paymentSchema })
    const baseUrl = this.getBaseUrlForService(body.serviceId) // whitelisted mapping
    await PaymentService.charge(baseUrl, request.only(['amount', 'token']))
    return response.ok({ ok: true })
  }

  private getBaseUrlForService(id: string): string {
    const map: Record = {
      'stripe': 'https://api.stripe.com/v1',
      'paypal': 'https://api.sandbox.paypal.com/v1'
    }
    return map[id] ?? 'https://api.stripe.com/v1'
  }
}

2. Use a typed HTTP client with fixed base URLs

Configure @adonisjs/axios with a fixed base URL and only allow path segments validated server-side.

// start/hooks.ts
import { IocResolver } from '@adonisjs/fold'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { cgroup } from '@adonisjs/core/Helpers'

export const boot = [
  // ...
]

export const aceHooks = {
  async aceHook({ client }: { client: any }) {
    client.interceptors.request.use((config: any) => {
      // ensure request URL is relative and base is fixed
      if (!config.url?.startsWith('/v1/')) {
        throw new Error('Invalid path')
      }
      return config
    })
  }
]

// config/http-client.ts
export const httpClient = {
  defaults: {
    baseURL: 'https://api.stripe.com/v1',
    headers: { Authorization: `Bearer ${process.env.STRIPE_API_KEY}` }
  }
}

// app/Services/PaymentService.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import axios from 'axios'

class PaymentService {
  private client = axios.create({
    baseURL: process.env.PAYMENT_BASE_URL,
    headers: { Authorization: `Bearer ${process.env.PAYMENT_API_KEY}` }
  })

  async charge(amount: number, token: string) {
    // only path provided; base and key are server-controlled
    return this.client.post('/charges', { amount, source: token })
  }
}

export default new PaymentService()

3. Enforce network-level restrictions and sanitize inputs

Use environment variables for API keys, avoid concatenating user input into hostnames, and validate URLs using a library that rejects private IPs and localhost.

// utils/validateUrl.ts
import { URL } from 'url'

const privateCidr = (ip: string): boolean => {
  // simplistic check for private/reserved ranges
  return /^127\./.test(ip) ||
         /^10\./.test(ip) ||
         /^192\.168\./.test(ip) ||
         /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip) ||
         /^169\.254\./.test(ip)
}

export function sanitizeAndValidate(input: string): string {
  try {
    const url = new URL(input)
    const host = url.hostname
    if (privateCidr(host) || host === 'localhost' || host === '127.0.0.1') {
      throw new Error('Blocked internal URL')
    }
    return url.toString()
  } catch {
    throw new Error('Invalid URL')
  }
}

// usage in controller
const externalUrl = sanitizeAndValidate(request.input('url'))
await axios.get(externalUrl, { headers: { Authorization: `Bearer ${process.env.EXTERNAL_API_KEY}` } })

These patterns ensure API keys remain tied to controlled endpoints and are not exposed to dynamic host resolution that can lead to SSRF.

Related CWEs: ssrf

CWE IDNameSeverity
CWE-918Server-Side Request Forgery (SSRF) CRITICAL
CWE-441Unintended Proxy or Intermediary (Confused Deputy) HIGH

Frequently Asked Questions

Can middleBrick detect SSRF in AdonisJS APIs that use API keys?
Yes, middleBrick scans unauthenticated attack surfaces and can identify SSRF indicators when user input influences request targets, even when API keys are used for authorization. Findings include severity and remediation guidance.
Does using API keys fix SSRF in AdonisJS?
No. API keys provide authorization at the target service but do not prevent the server from making unintended internal requests. Input validation and strict URL controls are required to mitigate SSRF.