Ssrf Server Side in Feathersjs with Firestore
Ssrf Server Side in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability
Server-side SSRF in a FeathersJS service that uses Firestore arises when an attacker can coerce the server into making HTTP requests from the runtime where Firestore is initialized. FeathersJS is a framework that encourages service-oriented, declarative APIs; if a service method accepts user-controlled URLs or hostnames and passes them to an HTTP client (for webhooks, fetching remote resources, or integrations), SSRF can occur. Firestore itself does not introduce SSRF, but the server-side environment (Node.js process, service account permissions, and network egress) becomes reachable through FeathersJS service logic.
Consider a FeathersJS service that accepts a webhookUrl from a request to notify an external endpoint. If the service uses an unvalidated URL in an HTTP request, an attacker can supply an internal metadata service address such as http://169.254.169.254 (AWS instance metadata) or the Firestore metadata endpoint. Because the runtime has Firestore credentials (via Application Default Credentials or a service account key), the SSRF can lead to metadata exfiltration or attempts to reach other Google services that are bound to the same service account.
In a FeathersJS app, this often manifests in a custom hook or service method that performs an outbound HTTP call without strict allowlisting of hosts. For example, a developer might use node-fetch or axios with the user-supplied URL, inadvertently allowing redirection to internal endpoints. The Firestore Admin SDK initialized in the same process has broad permissions; if the compromised process has a Firestore role such as datastore.user, an SSRF combined with Server-Side Request Forgery against Google metadata can escalate to a broader lateral movement scenario.
Real-world attack patterns include probing for the metadata service to obtain short-lived tokens, attempting to reach the Firestore REST API from within the trusted network, or using SSRF to fingerprint internal services. These are not Firestore-specific vulnerabilities but are enabled by the server-side runtime where Firestore is used. The risk is that an SSRF in FeathersJS can pivot to cloud metadata or other internal APIs that the Firestore-bound service account can interact with.
To contextualize within the 12 checks run by middleBrick, SSRF is tested as a distinct category. If your FeathersJS API accepts URLs as input, ensure they are validated and restricted to a strict allowlist, and avoid forwarding user input directly to the runtime network stack. This reduces the chance that an SSRF can reach internal endpoints, including those used by Firestore administration or metadata services.
Firestore-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on input validation, network controls, and service account scoping within the FeathersJS service layer. Do not forward untrusted input to HTTP clients, and if you must perform external calls, enforce strict allowlists and disable redirects.
Example of a vulnerable FeathersJS service before remediation:
const { Service } = require('feathersjs');
const fetch = require('node-fetch');
class WebhookService extends Service {
async create(data, params) {
const { webhookUrl, payload } = data;
// Unsafe: user-controlled URL used directly
const response = await fetch(webhookUrl, { method: 'POST', body: JSON.stringify(payload) });
return { status: response.status };
}
}
module.exports = function () {
const app = require('feathersjs')();
app.use('/webhooks', new WebhookService());
return app;
};
Fixed version with host allowlisting and safe options:
const { Service } = require('feathersjs');
const fetch = require('node-fetch');
const ALLOWED_HOSTS = new Set(['https://hooks.example.com', 'https://notify.partner.org']);
function isAllowedUrl(url) {
try {
const u = new URL(url);
return ALLOWED_HOSTS.has(u.origin);
} catch (err) {
return false;
}
}
class WebhookService extends Service {
async create(data, params) {
const { webhookUrl, payload } = data;
if (!isAllowedUrl(webhookUrl)) {
throw new Error('Webhook URL not allowed');
}
const response = await fetch(webhookUrl, {
method: 'POST',
body: JSON.stringify(payload),
redirect: 'manual',
follow: 0
});
return { status: response.status };
}
}
module.exports = function () {
const app = require('feathersjs')();
app.use('/webhooks', new WebhookService());
return app;
};
Firestore-specific guidance: keep Firestore credentials scoped narrowly. If your FeathersJS service only needs to read documents from a specific collection, use a service account with the datastore.user role limited to that path via IAM conditions. Avoid using the default Admin SDK initialization that grants broad access unless strictly necessary. Example of scoped Firestore usage:
const { Firestore } = require('@google-cloud/firestore');
const db = new Firestore({
projectId: 'your-project',
// Use a service account key with limited permissions
});
async function getPublicSettings() {
const snapshot = await db.collection('settings').where('public', '==', true).get();
const settings = {};
snapshot.forEach(doc => { settings[doc.id] = doc.data(); });
return settings;
}
Combine these practices in your FeathersJS hooks to ensure that user input never reaches the network unchecked and that Firestore operations follow the principle of least privilege. middleBrick can help validate these controls by scanning your API surface and mapping findings to frameworks such as OWASP API Top 10 and SOC2.