Skip to content

refactor: improve host validation logic and expand forbidden hosts list#1825

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

refactor: improve host validation logic and expand forbidden hosts list#1825
Artuomka merged 2 commits into
mainfrom
backend_fixes

Conversation

@Artuomka

@Artuomka Artuomka commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced connection host validation to better identify and block connections to private, loopback, link-local, and other reserved IP address ranges.
    • Improved security by expanding the list of forbidden internal network addresses.
  • Tests

    • Added comprehensive test coverage for host validation across different deployment modes.
    • Added tests for connection data validation scenarios including edge cases.

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

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors host security validation by extracting address-checking logic into a reusable helper, unifying DNS lookup pathways, expanding the forbidden network ranges to cover additional private and loopback addresses, and adding comprehensive test coverage for both the host check and connection validation flows.

Changes

Host Security Validation Refactor

Layer / File(s) Summary
Host check refactoring and isForbiddenAddress extraction
backend/src/entities/connection/utils/is-host-allowed.ts
isHostAllowed selects hostname based on SSH configuration, unifies DNS lookup to check all returned addresses, and extracts forbidden-address evaluation into a new isForbiddenAddress helper that normalizes IPv4-mapped IPv6 addresses before CIDR validation.
Forbidden hosts range expansion
backend/src/helpers/constants/constants.ts
Constants.FORBIDDEN_HOSTS expanded to include loopback, link-local, metadata service, and private-network CIDR ranges including 0.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, ::1/128, fd00::/8, and fe80::/10.
Host check and forbidden address tests
backend/test/ava-tests/unit-tests/is-host-allowed.test.ts
Tests verify isForbiddenAddress rejects loopback, link-local, metadata, private-network, and normalized IPv6-mapped addresses; and isHostAllowed blocks internal hosts in SaaS mode, allows public hosts, permits SSH-tunneled internal hosts via public SSH endpoints, blocks when SSH is also internal, bypasses checks for agent connections, and is bypassed in self-hosted and test modes.
Connection validation test coverage
backend/test/ava-tests/unit-tests/validate-create-connection-data.test.ts
Tests cover successful validation for Postgres (all fields), Redis (without username/database), and agent connections (minimal fields); rejection cases for missing type, host, username, database, invalid port, SSH parameters; and hostname/FQDN format validation in non-test modes.

🐰 A forbidden host check hops through the code today,
IPv6 addresses normalize along the way,
Private networks blocked with expanded CIDR grace,
While tests ensure each connection finds its place! 🐇

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately reflects the main changes: refactoring host validation logic (consolidating DNS lookup paths, extracting forbidden address check) and expanding the forbidden hosts CIDR list.
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.
Security Check ✅ Passed Expanded FORBIDDEN_HOSTS covers all SSRF vectors; proper IPv6 normalization; fail-closed error handling; comprehensive OWASP-aligned tests confirm no bypasses or security regressions.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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

This PR tightens SaaS host validation by expanding the forbidden IP/CIDR list and refactoring host resolution checks (including multi-A/AAAA DNS results), while adding unit tests to cover the updated validation behavior.

Changes:

  • Expanded Constants.FORBIDDEN_HOSTS to include additional IPv4/IPv6 reserved, link-local, and loopback ranges.
  • Refactored isHostAllowed() to resolve all DNS addresses and added isForbiddenAddress() to centralize normalization + range checks.
  • Added AVA unit tests for isHostAllowed() / isForbiddenAddress() and validateCreateConnectionData() (incl. host format validation outside test mode).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
backend/test/ava-tests/unit-tests/validate-create-connection-data.test.ts Adds validation coverage for connection creation data, including host format checks when not in test mode.
backend/test/ava-tests/unit-tests/is-host-allowed.test.ts Adds unit tests for forbidden address detection and SaaS host-allowance behavior (including SSH scenarios).
backend/src/helpers/constants/constants.ts Expands forbidden host CIDR list to cover more internal/reserved ranges.
backend/src/entities/connection/utils/is-host-allowed.ts Refactors host checking to use a single code path, multi-address DNS resolution, and adds isForbiddenAddress().

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

Comment on lines +26 to +34
const hostnameToCheck = connectionData.ssh ? connectionData.sshHost : connectionData.host;
if (!hostnameToCheck) {
return true;
}

const testHosts = Constants.getTestConnectionsHostNamesArr();
if (testHosts.includes(connectionData.host ?? '')) {
return true;
}
@Artuomka Artuomka enabled auto-merge June 3, 2026 12:17

@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.

🧹 Nitpick comments (7)
backend/test/ava-tests/unit-tests/validate-create-connection-data.test.ts (4)

23-25: ⚡ Quick win

Use arrow function per coding guidelines.

The function declaration should be converted to an arrow function. As per coding guidelines, arrow functions are preferred over function declarations.

♻️ Proposed refactor
-function makeData(params: Record<string, unknown>): CreateConnectionDs {
+const makeData = (params: Record<string, unknown>): CreateConnectionDs => {
 	return { connection_parameters: params } as unknown as CreateConnectionDs;
-}
+};
🤖 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/validate-create-connection-data.test.ts`
around lines 23 - 25, Convert the function declaration makeData into an arrow
function per coding guidelines: replace the function makeData(params:
Record<string, unknown>): CreateConnectionDs { ... } implementation with a const
makeData = (params: Record<string, unknown>): CreateConnectionDs => { ... }
form, preserving the returned value ({ connection_parameters: params } as
unknown as CreateConnectionDs) and any exports or usages of makeData in the
tests so references remain valid.

37-40: ⚡ Quick win

Use arrow function and consider safer type handling.

The function declaration should be converted to an arrow function per coding guidelines. Additionally, the type assertion error as HttpException is unsafe—consider adding a type guard to verify the error is actually an HttpException instance before calling getResponse().

♻️ Proposed refactor
-function thrownMessage(error: unknown): string {
-	const response = (error as HttpException).getResponse();
+const thrownMessage = (error: unknown): string => {
+	if (!(error instanceof HttpException)) {
+		return String(error);
+	}
+	const response = error.getResponse();
 	return typeof response === 'string' ? response : ((response as { message: string }).message ?? '');
-}
+};
🤖 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/validate-create-connection-data.test.ts`
around lines 37 - 40, The helper thrownMessage should be converted to an arrow
function and made resilient to non-HttpException values: add a type guard that
verifies the error is an instance of HttpException (or has a callable
getResponse method) before calling getResponse(), and return an empty string
when the guard fails; keep the existing logic for extracting a string response
or response.message when the guard passes. Ensure you reference thrownMessage,
HttpException and getResponse in the change so the test behavior is preserved
and no unsafe type assertions remain.

7-21: ⚖️ Poor tradeoff

Extract duplicate env management utilities to a shared test helper.

The setEnv helper and the pattern of storing/restoring NODE_ENV and IS_SAAS are duplicated in both is-host-allowed.test.ts and this file. Consider extracting these to a shared test utility module (e.g., backend/test/ava-tests/helpers/env-helpers.ts) to follow DRY principles.

📦 Example shared helper structure

Create a new file backend/test/ava-tests/helpers/env-helpers.ts:

export const setEnv = (key: 'NODE_ENV' | 'IS_SAAS', value: string | undefined): void => {
	if (value === undefined) {
		delete process.env[key];
	} else {
		process.env[key] = value;
	}
};

export const createEnvRestorer = () => {
	const ORIGINAL_NODE_ENV = process.env.NODE_ENV;
	const ORIGINAL_IS_SAAS = process.env.IS_SAAS;
	
	return () => {
		setEnv('NODE_ENV', ORIGINAL_NODE_ENV);
		setEnv('IS_SAAS', ORIGINAL_IS_SAAS);
	};
};

Then use it in both test files:

import { setEnv, createEnvRestorer } from '../helpers/env-helpers.js';

const restoreEnv = createEnvRestorer();
test.afterEach.always(restoreEnv);
🤖 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/validate-create-connection-data.test.ts`
around lines 7 - 21, The env setup and restore logic for NODE_ENV and IS_SAAS is
duplicated across the affected test files, so extract it into a shared helper
module and reuse it. Move the existing setEnv behavior plus the original-value
capture/restoration into a helper such as env-helpers, then update
validate-create-connection-data.test and is-host-allowed.test to import the
shared utility and use a common restore callback with test.afterEach.always.

10-16: ⚡ Quick win

Use arrow function and add explicit return type per coding guidelines.

The function declaration should be converted to an arrow function with an explicit return type annotation. As per coding guidelines, arrow functions are preferred over function declarations, and all TypeScript functions should have explicit return type annotations.

♻️ Proposed refactor
-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;
 	}
-}
+};
🤖 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/validate-create-connection-data.test.ts`
around lines 10 - 16, The helper setEnv should be converted from a function
declaration to an arrow function and keep an explicit return type annotation.
Update the local test utility in validate-create-connection-data.test.ts so
setEnv follows the project’s preferred style, while preserving its current
behavior for deleting or assigning process.env values.
backend/test/ava-tests/unit-tests/is-host-allowed.test.ts (2)

15-18: ⚡ Quick win

Use arrow function and add explicit return type per coding guidelines.

Similar to setEnv, this helper should use an arrow function with an explicit return type annotation.

♻️ Proposed refactor
-function enableSaaSHostCheck(): void {
+const enableSaaSHostCheck = (): void => {
 	process.env.NODE_ENV = 'development'; // isTest() => false
 	process.env.IS_SAAS = '1'; // isSaaS() => true
-}
+};
🤖 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/is-host-allowed.test.ts` around lines 15 -
18, The helper function enableSaaSHostCheck should be converted to an arrow
function with an explicit return type; replace the current function declaration
for enableSaaSHostCheck with a const arrow form (e.g., const enableSaaSHostCheck
= (): void => { ... }) mirroring the style used by setEnv, keeping the same
environment assignments (process.env.NODE_ENV and process.env.IS_SAAS) and
return type void.

7-13: ⚡ Quick win

Use arrow function and add explicit return type per coding guidelines.

The function declaration should be converted to an arrow function, and the return type should be explicitly annotated. As per coding guidelines, arrow functions are preferred over function declarations, and all TypeScript functions should have explicit return type annotations.

♻️ Proposed refactor
-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;
 	}
-}
+};
🤖 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/is-host-allowed.test.ts` around lines 7 -
13, Convert the setEnv helper in is-host-allowed.test.ts from a function
declaration to a const arrow function, and keep the explicit void return type
annotation as required by the TypeScript style guide. Make sure the updated
setEnv signature preserves the same key and value behavior while using the
arrow-function form preferred alongside the existing test helpers in this file.
backend/src/entities/connection/utils/is-host-allowed.ts (1)

26-34: ⚡ Quick win

Security bypass when ssh=true but sshHost is missing — not reachable through current flows

validateCreateConnectionData throws Messages.SSH_HOST_MISSING whenever ssh is enabled but sshHost is missing/empty, and the controller/use-cases call this validation before isHostAllowed runs (create/update/test/restore). The return true in isHostAllowed is therefore not reachable for SSH connections from these entrypoints; defensive hardening here would be optional.

🤖 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/connection/utils/is-host-allowed.ts` around lines 26 -
34, The current isHostAllowed logic uses hostnameToCheck = connectionData.ssh ?
connectionData.sshHost : connectionData.host and returns true if hostnameToCheck
is falsy, which can allow a bypass when ssh is true but sshHost is missing;
update isHostAllowed to deny (return false) when connectionData.ssh is true and
hostnameToCheck is missing or empty, and change the test-hosts check to compare
testHosts against hostnameToCheck (use
Constants.getTestConnectionsHostNamesArr() and
testHosts.includes(hostnameToCheck ?? '')). Ensure this behavior is implemented
in the isHostAllowed function and reference Messages.SSH_HOST_MISSING handling
remains consistent with validation.
🤖 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.

Nitpick comments:
In `@backend/src/entities/connection/utils/is-host-allowed.ts`:
- Around line 26-34: The current isHostAllowed logic uses hostnameToCheck =
connectionData.ssh ? connectionData.sshHost : connectionData.host and returns
true if hostnameToCheck is falsy, which can allow a bypass when ssh is true but
sshHost is missing; update isHostAllowed to deny (return false) when
connectionData.ssh is true and hostnameToCheck is missing or empty, and change
the test-hosts check to compare testHosts against hostnameToCheck (use
Constants.getTestConnectionsHostNamesArr() and
testHosts.includes(hostnameToCheck ?? '')). Ensure this behavior is implemented
in the isHostAllowed function and reference Messages.SSH_HOST_MISSING handling
remains consistent with validation.

In `@backend/test/ava-tests/unit-tests/is-host-allowed.test.ts`:
- Around line 15-18: The helper function enableSaaSHostCheck should be converted
to an arrow function with an explicit return type; replace the current function
declaration for enableSaaSHostCheck with a const arrow form (e.g., const
enableSaaSHostCheck = (): void => { ... }) mirroring the style used by setEnv,
keeping the same environment assignments (process.env.NODE_ENV and
process.env.IS_SAAS) and return type void.
- Around line 7-13: Convert the setEnv helper in is-host-allowed.test.ts from a
function declaration to a const arrow function, and keep the explicit void
return type annotation as required by the TypeScript style guide. Make sure the
updated setEnv signature preserves the same key and value behavior while using
the arrow-function form preferred alongside the existing test helpers in this
file.

In `@backend/test/ava-tests/unit-tests/validate-create-connection-data.test.ts`:
- Around line 23-25: Convert the function declaration makeData into an arrow
function per coding guidelines: replace the function makeData(params:
Record<string, unknown>): CreateConnectionDs { ... } implementation with a const
makeData = (params: Record<string, unknown>): CreateConnectionDs => { ... }
form, preserving the returned value ({ connection_parameters: params } as
unknown as CreateConnectionDs) and any exports or usages of makeData in the
tests so references remain valid.
- Around line 37-40: The helper thrownMessage should be converted to an arrow
function and made resilient to non-HttpException values: add a type guard that
verifies the error is an instance of HttpException (or has a callable
getResponse method) before calling getResponse(), and return an empty string
when the guard fails; keep the existing logic for extracting a string response
or response.message when the guard passes. Ensure you reference thrownMessage,
HttpException and getResponse in the change so the test behavior is preserved
and no unsafe type assertions remain.
- Around line 7-21: The env setup and restore logic for NODE_ENV and IS_SAAS is
duplicated across the affected test files, so extract it into a shared helper
module and reuse it. Move the existing setEnv behavior plus the original-value
capture/restoration into a helper such as env-helpers, then update
validate-create-connection-data.test and is-host-allowed.test to import the
shared utility and use a common restore callback with test.afterEach.always.
- Around line 10-16: The helper setEnv should be converted from a function
declaration to an arrow function and keep an explicit return type annotation.
Update the local test utility in validate-create-connection-data.test.ts so
setEnv follows the project’s preferred style, while preserving its current
behavior for deleting or assigning process.env values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0faa94f9-6d49-4d2a-8594-166005eecc75

📥 Commits

Reviewing files that changed from the base of the PR and between 986f9d4 and d75ce76.

📒 Files selected for processing (4)
  • backend/src/entities/connection/utils/is-host-allowed.ts
  • backend/src/helpers/constants/constants.ts
  • backend/test/ava-tests/unit-tests/is-host-allowed.test.ts
  • backend/test/ava-tests/unit-tests/validate-create-connection-data.test.ts

@Artuomka Artuomka merged commit 0d60cc6 into main Jun 3, 2026
16 of 17 checks passed
@Artuomka Artuomka deleted the backend_fixes branch June 3, 2026 12:26
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