Open Redirect in Feathersjs
How Open Redirect Manifests in Feathersjs
Open redirect vulnerabilities in Feathersjs applications typically occur when user-controlled input is used to construct redirect URLs without proper validation. This is particularly common in authentication flows, error handling, and navigation logic.
A classic pattern in Feathersjs involves using query parameters to determine where to redirect users after login. Consider this common middleware pattern:
app.post('/auth/callback', async (req, res) => {
const { redirectUrl } = req.query;
// Vulnerable: no validation of redirectUrl
res.redirect(redirectUrl);
});
In this Feathersjs route handler, an attacker can supply any URL in the redirectUrl parameter, potentially sending users to phishing sites or malicious destinations.
Another Feathersjs-specific manifestation occurs in error handling middleware. When an API call fails and you want to redirect users to a friendly error page, developers often pass the original request URL:
app.use('/api/*', async (context, params) => {
try {
return await context.app.service(context.path).find(params);
} catch (error) {
// Vulnerable: error.message might contain user input
return res.redirect(`/error?message=${error.message}`);
}
});
Feathersjs's flexible service architecture can also introduce open redirect risks when services dynamically construct URLs based on user input. For example:
class UserService {
async getProfileUrl(userId, callbackUrl) {
// Vulnerable: callbackUrl not validated
return `${callbackUrl}/user/${userId}/profile`;
}
}
The Feathersjs ecosystem's reliance on hooks for request processing can obscure open redirect vulnerabilities. A hook that modifies the response URL based on user input might look like:
const validateRedirect = async (context) => {
const { redirectUrl } = context.params.query;
// Missing validation logic!
context.result.redirect = redirectUrl;
};
Authentication strategies in Feathersjs, particularly those using OAuth or third-party providers, are also common sources of open redirect vulnerabilities. The verify callback often uses unvalidated URLs:
app.post('/auth/verify', async (req, res) => {
const { provider, returnTo } = req.body;
// Vulnerable: returnTo can be any URL
res.redirect(returnTo);
});
Feathersjs applications using the feathers-authentication-jwt strategy might implement logout redirects that are vulnerable:
app.post('/logout', async (req, res) => {
// Vulnerable: next parameter not validated
const next = req.query.next || '/';
res.redirect(next);
});
Feathersjs-Specific Detection
Detecting open redirect vulnerabilities in Feathersjs applications requires both manual code review and automated scanning. The flexible nature of Feathersjs routing and service hooks means vulnerabilities can be hidden in unexpected places.
Manual detection should focus on these Feathersjs-specific patterns:
// Search for these patterns in your codebase:
// 1. Direct res.redirect() with user input
res.redirect(req.query.returnUrl);
// 2. Hooks modifying redirect URLs
context.result.redirect = params.query.next;
// 3. Service methods building URLs from parameters
const url = `${baseUrl}${path}`;
// 4. Authentication callbacks
res.redirect(req.body.returnTo);
middleBrick's API security scanner is particularly effective at finding open redirect vulnerabilities in Feathersjs applications because it actively tests unauthenticated endpoints for redirect behavior. Unlike static analysis tools, middleBrick:
- Submits crafted redirect parameters to Feathersjs endpoints
- Follows the redirects to verify they lead to external domains
- Checks for common Feathersjs patterns like OAuth callbacks and authentication flows
- Provides specific findings with the exact request that triggered the vulnerability
- Maps findings to OWASP API Top 10 categories for compliance reporting
For Feathersjs applications, middleBrick's LLM security features are also relevant because many Feathersjs apps now integrate AI/ML endpoints that might have their own redirect vulnerabilities. The scanner tests for:
// middleBrick tests these LLM-specific patterns:
// System prompt leakage through redirects
// Prompt injection that causes unintended redirects
// Excessive agency where LLM tools make external calls
middleBrick's OpenAPI/Swagger analysis is especially valuable for Feathersjs applications because it can automatically detect when your Feathersjs services' documented endpoints accept redirect parameters without validation. The scanner:
- Parses your Feathersjs-generated OpenAPI spec
- Identifies query parameters that could contain URLs
- Cross-references parameter usage with actual endpoint behavior
- Provides a security score (A-F) for your API surface
CLI usage for Feathersjs developers:
# Scan your Feathersjs API in 10 seconds
middlebrick scan https://yourapi.com
# Integrate into your Feathersjs CI/CD pipeline
github-action.yml:
- name: Run middleBrick security scan
run: middlebrick scan https://staging-api.yourfeathersapp.com
if: github.ref == 'refs/heads/main'
Feathersjs-Specific Remediation
Remediating open redirect vulnerabilities in Feathersjs applications requires a combination of input validation, safe redirect patterns, and leveraging Feathersjs's built-in features. Here are Feathersjs-specific approaches:
First, implement a whitelist-based redirect validator as a reusable Feathersjs hook:
const createRedirectValidator = (allowedDomains = []) => {
return async (context) => {
const { redirectUrl } = context.params.query;
if (!redirectUrl) return context;
try {
const url = new URL(redirectUrl);
const isAllowed = allowedDomains.some(domain =>
url.hostname === domain ||
url.hostname.endsWith(`.${domain}`)
);
if (!isAllowed) {
throw new Error('Redirect URL not allowed');
}
} catch (error) {
// Fallback to safe default
context.params.query.redirectUrl = '/';
}
return context;
};
};
// Usage in Feathersjs app
app.service('auth').hooks({
before: {
create: createRedirectValidator([
'yourapp.com',
'yourotherdomain.com'
])
}
});
For Feathersjs authentication flows, use the built-in validation provided by feathers-authentication-jwt and similar strategies:
const { authenticate } = require('@feathersjs/authentication').hooks;
app.post('/auth/callback',
authenticate('jwt'),
async (req, res) => {
// JWT authentication already validates the request
const { redirectUrl } = req.query;
// Still validate redirectUrl even with auth
if (redirectUrl && isValidRedirect(redirectUrl)) {
res.redirect(redirectUrl);
} else {
res.redirect('/dashboard');
}
}
);
Implement a centralized redirect service in Feathersjs:
class RedirectService {
constructor() {
this.allowedDomains = [
'yourapp.com',
'yourotherdomain.com',
'localhost:3030'
];
}
validate(url) {
try {
const parsed = new URL(url);
return this.allowedDomains.some(domain =>
parsed.hostname === domain ||
parsed.hostname.endsWith(`.${domain}`)
);
} catch {
return false;
}
}
getSafeRedirect(url, fallback = '/') {
return this.validate(url) ? url : fallback;
}
}
const redirectService = new RedirectService();
// Use throughout your Feathersjs app
app.post('/logout', async (req, res) => {
const next = redirectService.getSafeRedirect(req.query.next, '/');
res.redirect(next);
});
For Feathersjs hooks that handle redirects, use the built-in context object safely:
const validateRedirectHook = () => {
return async context => {
const { redirectUrl } = context.params.query;
if (redirectUrl) {
const safeRedirect = redirectService.getSafeRedirect(redirectUrl);
context.result = { redirect: safeRedirect };
}
return context;
};
};
// Apply to specific services
app.service('users').hooks({
after: {
get: validateRedirectHook()
}
});
Finally, leverage Feathersjs's error handling to prevent information leakage:
app.use(async (context, params) => {
try {
return await context.app.service(context.path).find(params);
} catch (error) {
// Don't include user input in error messages
const safeMessage = 'An error occurred. Please try again.';
return {
error: true,
message: safeMessage,
redirect: '/error'
};
}
});