snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
146
packages/server-fetch/src/checkForSsrf.ts
Normal file
146
packages/server-fetch/src/checkForSsrf.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { isTruthy } from '@rocket.chat/tools';
|
||||
|
||||
import {
|
||||
allowlistedIpResolved,
|
||||
isIpInAnyRange,
|
||||
isIpValid,
|
||||
isValidDomain,
|
||||
normalizeAllowlistEntry,
|
||||
normalizeHostForAllowlistMatch,
|
||||
nslookup,
|
||||
parseIpv4WithPort,
|
||||
} from './helpers';
|
||||
|
||||
export const parseSsrfAllowlist = (value: string): string[] => {
|
||||
if (!value.trim()) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.split(/[\n,]/)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(isTruthy);
|
||||
};
|
||||
|
||||
function getEffectiveAllowlist(allowListRaw: string[]): string[] {
|
||||
return allowListRaw.map(normalizeAllowlistEntry).filter((entry) => entry.length > 0);
|
||||
}
|
||||
|
||||
/** Normalize allowList (string or string[]) to a single effective list. Parses raw string in this single place. */
|
||||
function toEffectiveAllowlist(allowList?: string | string[]): string[] {
|
||||
if (allowList === undefined || allowList === null) {
|
||||
return [];
|
||||
}
|
||||
if (typeof allowList === 'string') {
|
||||
return getEffectiveAllowlist(parseSsrfAllowlist(allowList));
|
||||
}
|
||||
return getEffectiveAllowlist(allowList);
|
||||
}
|
||||
|
||||
function isInAllowlist(hostOrIp: string, port: string | undefined, allowlist: string[]): boolean {
|
||||
if (allowlist.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const normalized = normalizeHostForAllowlistMatch(hostOrIp);
|
||||
const withPort = port ? `${normalized}:${port}` : normalized;
|
||||
return allowlist.some((entry) => entry === normalized || entry === withPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the URL is allowed by SSRF rules.
|
||||
* @param input - URL or host to check
|
||||
* @param allowList - Optional raw string (newline/comma-separated) or array of allowed hosts/IPs/CIDRs. Parsed inside.
|
||||
*/
|
||||
export const checkForSsrf = async (input: string, allowList?: string | string[]): Promise<boolean> => {
|
||||
const result = await checkForSsrfWithIp(input, allowList);
|
||||
return result.allowed;
|
||||
};
|
||||
|
||||
/**
|
||||
* SSRF check with resolved IP for pinning. allowList is optional; string is parsed in this single place.
|
||||
*/
|
||||
export const checkForSsrfWithIp = async (
|
||||
input: string,
|
||||
allowList?: string | string[],
|
||||
): Promise<{ allowed: false } | { allowed: true; resolvedIp: string }> => {
|
||||
const effectiveAllowlist = toEffectiveAllowlist(allowList);
|
||||
let ipOrDomain: string;
|
||||
let port: string | undefined;
|
||||
let wasUrlParsed = false;
|
||||
|
||||
try {
|
||||
const url = new URL(input);
|
||||
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
||||
return { allowed: false };
|
||||
}
|
||||
ipOrDomain = url.hostname;
|
||||
port = url.port || undefined;
|
||||
wasUrlParsed = true;
|
||||
} catch {
|
||||
const parsed = parseIpv4WithPort(input);
|
||||
if (parsed) {
|
||||
ipOrDomain = parsed.ip;
|
||||
port = parsed.port;
|
||||
} else {
|
||||
ipOrDomain = input;
|
||||
}
|
||||
}
|
||||
|
||||
if (ipOrDomain.startsWith('[') && ipOrDomain.endsWith(']')) {
|
||||
ipOrDomain = ipOrDomain.slice(1, -1);
|
||||
}
|
||||
|
||||
const parsed = parseIpv4WithPort(ipOrDomain);
|
||||
if (parsed) {
|
||||
ipOrDomain = parsed.ip;
|
||||
if (parsed.port && !port) {
|
||||
port = parsed.port;
|
||||
}
|
||||
}
|
||||
|
||||
const ipValid = isIpValid(ipOrDomain);
|
||||
const domainValid = isValidDomain(ipOrDomain);
|
||||
|
||||
if (!ipValid && !domainValid) {
|
||||
return { allowed: false };
|
||||
}
|
||||
|
||||
if (isInAllowlist(ipOrDomain, port, effectiveAllowlist)) {
|
||||
if (ipValid) {
|
||||
return { allowed: true, resolvedIp: allowlistedIpResolved(ipOrDomain, port, wasUrlParsed) };
|
||||
}
|
||||
|
||||
if (domainValid) {
|
||||
try {
|
||||
const resolvedIp = await nslookup(ipOrDomain);
|
||||
const resolvedIpWithPort = !wasUrlParsed && port ? `${resolvedIp}:${port}` : resolvedIp;
|
||||
return { allowed: true, resolvedIp: resolvedIpWithPort };
|
||||
} catch {
|
||||
return { allowed: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ipValid && isIpInAnyRange(ipOrDomain)) {
|
||||
return { allowed: false };
|
||||
}
|
||||
if (domainValid && /metadata\.google\.internal/i.test(ipOrDomain)) {
|
||||
return { allowed: false };
|
||||
}
|
||||
|
||||
if (!domainValid) {
|
||||
const ipWithPort = !wasUrlParsed && port ? `${ipOrDomain}:${port}` : ipOrDomain;
|
||||
return { allowed: true, resolvedIp: ipWithPort };
|
||||
}
|
||||
|
||||
try {
|
||||
const resolvedIp = await nslookup(ipOrDomain);
|
||||
if (isIpInAnyRange(resolvedIp)) {
|
||||
return { allowed: false };
|
||||
}
|
||||
|
||||
const resolvedIpWithPort = !wasUrlParsed && port ? `${resolvedIp}:${port}` : resolvedIp;
|
||||
return { allowed: true, resolvedIp: resolvedIpWithPort };
|
||||
} catch {
|
||||
return { allowed: false };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user