Skip to content

feat: implement SSRF guard with request configuration and add tests#1826

Merged
Artuomka merged 4 commits into
mainfrom
backend_fixes
Jun 3, 2026
Merged

feat: implement SSRF guard with request configuration and add tests#1826
Artuomka merged 4 commits into
mainfrom
backend_fixes

Conversation

@Artuomka

@Artuomka Artuomka commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Added SSRF (Server-Side Request Forgery) protection for outbound HTTP requests in table actions and Slack integrations
    • Improved forbidden host address validation with centralized validation logic

Copilot AI review requested due to automatic review settings June 3, 2026 12:47
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Artuomka, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 17f4e017-0ba5-465c-bab5-c7533763e9c6

📥 Commits

Reviewing files that changed from the base of the PR and between 8ab6302 and 9482c58.

📒 Files selected for processing (2)
  • backend/src/helpers/validators/ssrf-safe-http.ts
  • backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts
📝 Walkthrough

Walkthrough

This PR implements SSRF protection by extracting forbidden address validation into a shared helper, creating guarded DNS resolution with Axios agent wiring for SaaS mode, and applying SSRF-safe request configuration to table action and Slack integrations. Test coverage verifies guard rejection of forbidden IPs and config behavior across environments.

Changes

SSRF Protection Implementation

Layer / File(s) Summary
Centralized Forbidden Address Validation
backend/src/helpers/validators/is-forbidden-address.ts, backend/src/entities/connection/utils/is-host-allowed.ts
New isForbiddenAddress helper normalizes IPv4-mapped IPv6 addresses and checks against forbidden hosts. is-host-allowed.ts now imports and re-exports this helper instead of maintaining its own implementation.
SSRF-Safe HTTP Guards and Agents
backend/src/helpers/validators/ssrf-safe-http.ts
Introduces ssrfGuardLookup for filtering DNS-resolved addresses against forbidden hosts, and exports getSsrfSafeRequestConfig() that returns Axios config with guarded HTTP/HTTPS agents, disabled redirects, and 10-second timeout for SaaS non-test mode.
SSRF Protection in Services
backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts, backend/src/helpers/slack/action-slack-post-message.ts
Table action HTTP requests and Slack message posts now integrate getSsrfSafeRequestConfig() by spreading its returned configuration into Axios options.
SSRF Guard Test Suite
backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts
Unit tests verify ssrfGuardLookup rejects forbidden/internal IPs and resolves public addresses, and confirm getSsrfSafeRequestConfig() returns guarded agents only in SaaS mode with environment isolation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rocket-admin/rocketadmin#1825: Both PRs refactor forbidden-host validation by centralizing isForbiddenAddress used by connection utilities, with overlapping changes to is-host-allowed.ts and the validation function.

Poem

🐰 A rabbit hops through networks safe,
With guards that check each DNS wafer,
No SSRF tricks shall pass the gate,
Where forbidden IPs meet their fate,
And Slack sends posts without a shake!

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Security Check ⚠️ Warning Missing proxy: false in getSsrfSafeRequestConfig() allows HTTP_PROXY environment variables to bypass SSRF guard by having proxies resolve final destinations independently. Add proxy: false to the returned config in getSsrfSafeRequestConfig() to prevent Axios from using environment proxy variables that could bypass the SSRF check.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective of the changeset: implementing SSRF (Server-Side Request Forgery) protection mechanisms with request configuration and corresponding test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend_fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements an SSRF mitigation for outbound HTTP(S) calls in SaaS mode by introducing a DNS lookup guard and a reusable Axios request configuration, then wiring it into existing outbound request paths.

Changes:

  • Added ssrfGuardLookup + getSsrfSafeRequestConfig() to enforce forbidden-address blocking via custom HTTP(S) agents (SaaS-only, non-test).
  • Centralized forbidden-address checking into a shared isForbiddenAddress() helper and re-exported it for compatibility.
  • Updated outbound Axios calls (Slack webhook + table action HTTP callback) to use the SSRF-safe request config; added AVA unit tests for the guard/config.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts Adds unit tests for SSRF lookup blocking/allowing behavior and SaaS/test gating for request config.
backend/src/helpers/validators/ssrf-safe-http.ts Introduces SSRF-safe Axios request config and guarded DNS lookup used by custom agents.
backend/src/helpers/validators/is-forbidden-address.ts Extracts and centralizes forbidden-address detection logic.
backend/src/helpers/slack/action-slack-post-message.ts Applies SSRF-safe request config to Slack webhook POST.
backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts Applies SSRF-safe request config to outbound table action HTTP POST.
backend/src/entities/connection/utils/is-host-allowed.ts Switches to shared isForbiddenAddress implementation and re-exports it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import test from 'ava';
import { getSsrfSafeRequestConfig, ssrfGuardLookup } from '../../../src/helpers/validators/ssrf-safe-http.js';

// The SSRF guard is only meant to engage in SaaS mode (self-hosted owns its network). isSaaS()/
Comment on lines +1 to +2
import test from 'ava';
import { getSsrfSafeRequestConfig, ssrfGuardLookup } from '../../../src/helpers/validators/ssrf-safe-http.js';

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts (1)

217-242: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve Axios/SSRF-guard transport error details when no response exists

In SaaS, getSsrfSafeRequestConfig() installs a custom agent lookup (ssrfGuardLookup) that can fail during DNS/SSRF checks before axios.post receives any HTTP response. Since result is only assigned after the await axios.post(...) succeeds, the catch block often falls back to result?.data..., losing the real Axios error message and returning a generic “An error occurred”. Build the message from error.response?.data (when present) and otherwise from error.message.

🔧 Proposed fix
 		} catch (error) {
 			if (axios.isAxiosError(error)) {
+				const responseData = error.response?.data;
 				const errorMessage =
-					result?.data?.error ||
-					result?.data?.message ||
-					result?.data?.errorMessage ||
-					result?.data?.response ||
+					responseData?.error ||
+					responseData?.message ||
+					responseData?.errorMessage ||
+					responseData?.response ||
+					error.message ||
 					'An error occurred';
 				const responseStatus = error.response?.status || result?.status || 500;
 				throw new HttpException(
 					{
 						message: errorMessage,
 					},
 					responseStatus,
 				);
 			}
 			throw error;
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts`
around lines 217 - 242, The catch block in table-action-activation.service.ts
currently reads error details from the local result variable which is only set
on success; update the axios error handling in the catch of the axios.post call
so that when axios.isAxiosError(error) you first extract data from
error.response?.data (checking .error, .message, .errorMessage, .response) and
if none exist fall back to error.message, and determine status from
error.response?.status or default 500; then throw the HttpException using that
constructed message and status (preserving SSRF/agent errors from
getSsrfSafeRequestConfig()/ssrfGuardLookup by using error.message when no
response exists).
🧹 Nitpick comments (1)
backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts (1)

10-33: ⚡ Quick win

Use arrow helpers and an interface for the lookup result type.

On Line 10 and Line 23, switch helper declarations to const arrow functions and extract the inline object shape into an interface to match repository TS rules.

Proposed refactor
+interface LookupResult {
+	address: string;
+	family: number;
+}
+
-function setEnv(key: 'NODE_ENV' | 'IS_SAAS', value: string | undefined): void {
+const setEnv = (key: 'NODE_ENV' | 'IS_SAAS', value: string | undefined): void => {
 	if (value === undefined) {
 		delete process.env[key];
 	} else {
 		process.env[key] = value;
 	}
-}
+};

-function lookup(host: string): Promise<{ address: string; family: number }> {
+const lookup = (host: string): Promise<LookupResult> => {
 	return new Promise((resolve, reject) => {
 		ssrfGuardLookup(host, {}, (err, address, family) => {
 			if (err) {
 				reject(err);
 			} else {
 				resolve({ address: address as string, family: family ?? 0 });
 			}
 		});
 	});
-}
+};

As per coding guidelines, “Prefer arrow functions over function declarations” and “Use interfaces for object shapes and type for unions and primitives”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts` around lines 10 -
33, Convert the function declarations setEnv and lookup into const arrow
functions and introduce an interface (e.g., LookupResult) for the lookup return
shape instead of an inline object type; specifically, change "function
setEnv(...)" to "const setEnv = (...): void => { ... }" and "function
lookup(host: string): Promise<...>" to "const lookup = (host: string):
Promise<LookupResult> => { ... }", define "interface LookupResult { address:
string; family: number }" and update the lookup resolve to return that interface
type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/helpers/validators/ssrf-safe-http.ts`:
- Around line 39-43: The SSRF-safe axios config returned from the function
currently sets httpAgent/httpsAgent and timeout but doesn't disable axios'
environment proxy handling; add proxy: false to the returned config object so
axios won't use HTTP_PROXY/HTTPS_PROXY and bypass the
ssrfSafeHttpAgent/ssrfSafeHttpsAgent checks (update the return where httpAgent:
ssrfSafeHttpAgent, httpsAgent: ssrfSafeHttpsAgent, maxRedirects: 0, timeout:
SSRF_REQUEST_TIMEOUT_MS are set to include proxy: false).

---

Outside diff comments:
In
`@backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts`:
- Around line 217-242: The catch block in table-action-activation.service.ts
currently reads error details from the local result variable which is only set
on success; update the axios error handling in the catch of the axios.post call
so that when axios.isAxiosError(error) you first extract data from
error.response?.data (checking .error, .message, .errorMessage, .response) and
if none exist fall back to error.message, and determine status from
error.response?.status or default 500; then throw the HttpException using that
constructed message and status (preserving SSRF/agent errors from
getSsrfSafeRequestConfig()/ssrfGuardLookup by using error.message when no
response exists).

---

Nitpick comments:
In `@backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts`:
- Around line 10-33: Convert the function declarations setEnv and lookup into
const arrow functions and introduce an interface (e.g., LookupResult) for the
lookup return shape instead of an inline object type; specifically, change
"function setEnv(...)" to "const setEnv = (...): void => { ... }" and "function
lookup(host: string): Promise<...>" to "const lookup = (host: string):
Promise<LookupResult> => { ... }", define "interface LookupResult { address:
string; family: number }" and update the lookup resolve to return that interface
type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6655085f-fa75-48d2-9687-83ba6784c5c9

📥 Commits

Reviewing files that changed from the base of the PR and between 0d60cc6 and 8ab6302.

📒 Files selected for processing (6)
  • backend/src/entities/connection/utils/is-host-allowed.ts
  • backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts
  • backend/src/helpers/slack/action-slack-post-message.ts
  • backend/src/helpers/validators/is-forbidden-address.ts
  • backend/src/helpers/validators/ssrf-safe-http.ts
  • backend/test/ava-tests/unit-tests/ssrf-safe-http.test.ts

Comment on lines +39 to +43
return {
httpAgent: ssrfSafeHttpAgent,
httpsAgent: ssrfSafeHttpsAgent,
maxRedirects: 0,
timeout: SSRF_REQUEST_TIMEOUT_MS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Axios in Node.js honor HTTP_PROXY / HTTPS_PROXY environment variables unless AxiosRequestConfig.proxy is explicitly set to false?

💡 Result:

Yes. In Axios for Node.js, HTTP(S)_PROXY environment variables are honored by Axios’s own proxy logic unless you explicitly disable proxying by setting AxiosRequestConfig.proxy to false. Axios documents that setting proxy to false “disable[s] proxies, ignoring environment variables.”[1][2] In practice, Axios’s HTTP adapter applies proxy settings derived from the environment; and the Axios issue discussions note that Node’s built-in proxy handling may also be involved in some setups, but they still describe proxy: false as the switch that disables Axios’s proxy behavior.[3][4] So, if you want Axios to ignore HTTP_PROXY/HTTPS_PROXY/NO_PROXY, you must set proxy: false in the request config (or corresponding Axios instance/defaults).

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the SSRF-safe request config helper
rg -n "getSsrfSafeRequestConfig|ssrfSafeHttpAgent|ssrfSafeHttpsAgent|ssrfGuardLookup" backend/src/helpers/validators/ssrf-safe-http.ts
echo "----"
sed -n '1,200p' backend/src/helpers/validators/ssrf-safe-http.ts

echo "==== Usage sites ===="
# Find usages of getSsrfSafeRequestConfig
rg -n "getSsrfSafeRequestConfig\(" backend/src
echo "----"
# Show surrounding context where used
rg -n "getSsrfSafeRequestConfig\(" backend/src -n -C 3

echo "==== Axios proxy config elsewhere ===="
# Search for proxy configuration or env proxy usage
rg -n "axios.*proxy|AxiosRequestConfig.*proxy|proxy:\s*false|HTTP_PROXY|HTTPS_PROXY|NO_PROXY" backend/src -S

Repository: rocket-admin/rocketadmin

Length of output: 4342


🌐 Web query:

Axios for Node.js: what does setting AxiosRequestConfig.proxy=false do regarding HTTP(S)_PROXY env vars and NO_PROXY?

💡 Result:

Setting AxiosRequestConfig.proxy to false disables Axios's internal proxy processing logic [1][2]. When this value is explicitly set, Axios ignores the HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables that it would otherwise automatically detect and apply to the request [1][2][3]. By setting proxy: false, you effectively opt out of Axios's automatic proxy resolution mechanism [1][2]. This is the standard way to ensure that your request is made directly (or via any custom agent you explicitly configure) without interference from environment-based proxy configurations that Axios would normally inspect [1][4]. It is important to note the following context: 1. Scope of Effect: This setting specifically targets Axios's own proxy handling [2]. It does not prevent other mechanisms, such as Node.js native proxy support (e.g., if NODE_USE_ENV_PROXY is enabled in modern Node.js versions) or other libraries from potentially applying proxies, though it prevents Axios from injecting its own proxy configuration or overriding request details based on environment variables [5][6]. 2. Custom Agents: If you need to manage your own proxy logic (e.g., using an https-proxy-agent), you should set proxy: false and provide your custom httpAgent or httpsAgent configuration [1][4]. 3. Troubleshooting: In some configurations, if you are not using environment variables but are seeing issues, ensuring proxy: false is set can help confirm that Axios is not unintentionally attempting to resolve a proxy [7]. Conversely, if you observe that a proxy is still being used despite setting proxy: false, it may be due to Node.js's own internal proxy handling (if enabled) or a custom agent implementation that includes its own proxy logic [5][8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the SSRF-safe request config helper
rg -n "getSsrfSafeRequestConfig|ssrfSafeHttpAgent|ssrfSafeHttpsAgent|ssrfGuardLookup" backend/src/helpers/validators/ssrf-safe-http.ts
echo "----"
sed -n '1,200p' backend/src/helpers/validators/ssrf-safe-http.ts

echo "==== Usage sites ===="
# Find usages of getSsrfSafeRequestConfig
rg -n "getSsrfSafeRequestConfig\(" backend/src
echo "----"
# Show surrounding context where used
rg -n "getSsrfSafeRequestConfig\(" backend/src -n -C 3

echo "==== Axios proxy config elsewhere ===="
# Search for proxy configuration or env proxy usage
rg -n "axios.*proxy|AxiosRequestConfig.*proxy|proxy:\s*false|HTTP_PROXY|HTTPS_PROXY|NO_PROXY" backend/src -S

Repository: rocket-admin/rocketadmin

Length of output: 4342


🌐 Web query:

Axios for Node.js: what does setting AxiosRequestConfig.proxy=false do regarding HTTP(S)_PROXY env vars and NO_PROXY?

💡 Result:

In Axios for Node.js, setting AxiosRequestConfig.proxy = false instructs Axios to disable its internal proxy resolution logic for that specific request [1][2]. Specifically, this configuration has the following effects: 1. Ignores Environment Variables: It causes Axios to ignore standard proxy environment variables, including HTTP_PROXY, HTTPS_PROXY, and NO_PROXY [1][2]. 2. Disables Internal Proxy Logic: By setting this, you explicitly instruct Axios to skip its internal code responsible for parsing these environment variables and configuring the request to route through a proxy [3][4]. 3. Direct Connection: Because the internal proxy logic is bypassed, the request will typically attempt to connect directly to the destination URL [2]. Important Considerations: * Interaction with Node.js Native Proxy Support: In modern Node.js versions (e.g., when NODE_USE_ENV_PROXY is enabled), Node.js may handle proxying natively [5][3]. When proxy: false is set in Axios, you are disabling only the Axios-specific proxy logic; if your Node.js environment is configured to handle proxies automatically, that behavior may still persist independently of Axios [3][4]. * Potential for Conflicts: Users have reported that in certain configurations—particularly when custom httpAgent or httpsAgent instances are provided—setting proxy: false can occasionally lead to unexpected connection errors (e.g., ENOTFOUND or other socket-related issues) if the underlying agent expectations are not perfectly aligned with a direct connection [6]. * Not a Global Setting: This is a request-level configuration. If you wish to disable proxying for all requests, you must apply it to your Axios instance defaults or to every individual request configuration object [7].

Citations:


Disable Axios environment proxy handling in SSRF-safe config

Axios can still use HTTP_PROXY/HTTPS_PROXY; with only a guarded httpAgent/httpsAgent, the guard validates the proxy host DNS but the proxy resolves/connects to the final destination, bypassing the SSRF check. Set proxy: false in the returned config.

🔒 Proposed fix
 	return {
 		httpAgent: ssrfSafeHttpAgent,
 		httpsAgent: ssrfSafeHttpsAgent,
+		proxy: false,
 		maxRedirects: 0,
 		timeout: SSRF_REQUEST_TIMEOUT_MS,
 	};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return {
httpAgent: ssrfSafeHttpAgent,
httpsAgent: ssrfSafeHttpsAgent,
maxRedirects: 0,
timeout: SSRF_REQUEST_TIMEOUT_MS,
return {
httpAgent: ssrfSafeHttpAgent,
httpsAgent: ssrfSafeHttpsAgent,
proxy: false,
maxRedirects: 0,
timeout: SSRF_REQUEST_TIMEOUT_MS,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/helpers/validators/ssrf-safe-http.ts` around lines 39 - 43, The
SSRF-safe axios config returned from the function currently sets
httpAgent/httpsAgent and timeout but doesn't disable axios' environment proxy
handling; add proxy: false to the returned config object so axios won't use
HTTP_PROXY/HTTPS_PROXY and bypass the ssrfSafeHttpAgent/ssrfSafeHttpsAgent
checks (update the return where httpAgent: ssrfSafeHttpAgent, httpsAgent:
ssrfSafeHttpsAgent, maxRedirects: 0, timeout: SSRF_REQUEST_TIMEOUT_MS are set to
include proxy: false).

@Artuomka Artuomka enabled auto-merge June 3, 2026 13:10
@Artuomka Artuomka merged commit 6a2ce01 into main Jun 3, 2026
16 of 17 checks passed
@Artuomka Artuomka deleted the backend_fixes branch June 3, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants