Ssrf Server Side in Adonisjs (Typescript)
Ssrf Server Side in Adonisjs with Typescript — how this specific combination creates or exposes the vulnerability
Server-side request forgery (SSRF) in AdonisJS with TypeScript occurs when an application accepts a URL or host input from an untrusted source and uses it to make HTTP requests without strict validation or network controls. Because AdonisJS is a Node.js framework, the runtime executes JavaScript/TypeScript on the server, and any HTTP client (e.g., got, axios, native http) will follow redirects and resolve internal hostnames if the attacker provides a malicious target.
Typescript adds type safety at compile time but does not prevent runtime behavior; if the code uses type assertions like as string or casts user input to any, it can bypass intended checks and pass a crafted URI to an internal service. Common patterns include fetching a webhook URL, pinging a status endpoint, or importing a remote schema. An attacker can supply an input such as http://169.254.169.254/latest/meta-data/iam/security-credentials/ to reach the metadata service, or use file:// or `gopher://` URIs to interact with local resources. SSRF often pairs with Server-Side Template Injection (SSTI) or deserialization bugs to chain exploits.
In AdonisJS, routes and controllers written in TypeScript may call services that perform outbound requests. Without a deny-list of internal IPs (127.0.0.1, 169.254.169.254, ::1), private RFC1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), or cloud metadata endpoints, a seemingly benign feature can become an SSRF vector. The framework’s validation layer (e.g., schema-based checks) can reject malformed URLs but may not enforce host resolution rules or protocol restrictions, leaving a gap an attacker can exploit via DNS rebinding or unexpected redirect behavior.
Typescript-Specific Remediation in Adonisjs — concrete code fixes
To mitigate SSRF in AdonisJS with TypeScript, enforce allow-lists, disable redirects, set timeouts, and avoid passing user-controlled input directly to HTTP clients. Below are concrete, type-safe examples that you can adopt in your controllers and services.
1. Use an allow-listed HTTP client with strict typing
Prefer a client that lets you disable redirects and set a tight timeout. The following example uses got with TypeScript interfaces to ensure the response shape is validated before use.
import got from 'got';
interface Metadata {
instanceId: string;
publicHostname: string;
}
export async function fetchInstanceMetadata(url: string): Promise {
// Allow-list protocol and host
const allowedHosts = new Set(['metadata.service.example.com']);
const parsed = new URL(url);
if (!allowedHosts.has(parsed.hostname)) {
throw new Error('Request target not allowed');
}
return got(url, {
followRedirect: false,
timeout: { request: 5000 },
headers: { 'User-Agent': 'middleBrick-SSRF-protection' },
responseType: 'json',
}).json();
}
2. Validate input with a strict schema and reject private IPs
Use a validation library (e.g., Joi or AdonisJS schema) to enforce a URL format and reject private/reserved IPs before making a request.
import { schema } from '@ioc:Adonis/Core/Validator';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
const urlSchema = schema.string({}, [
(value, { pointer, report }) => {
try {
const u = new URL(value);
const ipRegex = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{13,}$/;
const privateCidrRegex = /^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/;
if (ipRegex.test(u.hostname) || privateCidrRegex.test(u.hostname)) {
report(pointer, 'field', 'forbidden_target', 'Private or loopback IPs are not allowed');
}
if (!['http:', 'https:'].includes(u.protocol)) {
report(pointer, 'field', 'invalid_protocol', 'Only HTTP and HTTPS are allowed');
}
} catch (e) {
report(pointer, 'pointer', 'invalid_url', 'Invalid URL');
}
},
]);
export async function store({ request, response }: HttpContextContract) {
const payload = request.validate({ schema: urlSchema });
// Safe to proceed
const data = await fetchInstanceMetadata(payload);
return response.ok(data);
}
3. Centralize outbound requests with a service class
Encapsulate HTTP calls in a service that applies network controls, making it easier to audit and enforce policies across controllers.
import got from 'got';
export class OutboundService {
private allowedHosts = new Set(['api.partner.com', 'cdn.example.org']);
public async getJson(inputUrl: string): Promise {
const url = new URL(inputUrl);
if (!this.allowedHosts.has(url.hostname)) {
throw new Error('Host not permitted by SSRF policy');
}
return got(url.toString(), {
followRedirect: false,
timeout: { request: 8000 },
headers: { Accept: 'application/json' },
}).json();
}
}
4. Runtime detection and logging
Log suspicious inputs (e.g., metadata addresses, cloud IMDSv2 attempts) without executing them. This supports monitoring and incident response while keeping the runtime safe.
import logger from 'utils/logger';
export function safeFetch(url: string): void {
const metaPatterns = [
/^https?:\/\/169\.254\.169\.254/, // AWS IMDS
/^https?:\/\/169.254.169.254\.compute/, // GCP
/^https?:\/\/metadata\.google\.internal/, // GCP
];
if (metaPatterns.some(r => r.test(url))) {
logger.warn('SSRF probe blocked', { url });
return;
}
// proceed with safe client
}