feat: Add option to restrict credential usage in http request node (#17583)

This commit is contained in:
Jon
2025-08-28 17:35:14 +01:00
committed by GitHub
parent 93e08d8735
commit f7f70f241e
10 changed files with 585 additions and 16 deletions

View File

@@ -44,6 +44,7 @@ export {
randomString,
isSafeObjectProperty,
setSafeObjectProperty,
isDomainAllowed,
} from './utils';
export {
isINodeProperties,

View File

@@ -358,3 +358,43 @@ export function setSafeObjectProperty(
target[property] = value;
}
}
export function isDomainAllowed(
urlString: string,
options: {
allowedDomains: string;
},
): boolean {
if (!options.allowedDomains || options.allowedDomains.trim() === '') {
return true; // If no restrictions are set, allow all domains
}
try {
const url = new URL(urlString);
const hostname = url.hostname;
const allowedDomainsList = options.allowedDomains
.split(',')
.map((domain) => domain.trim())
.filter(Boolean);
for (const allowedDomain of allowedDomainsList) {
// Handle wildcard domains (*.example.com)
if (allowedDomain.startsWith('*.')) {
const domainSuffix = allowedDomain.substring(2); // Remove the *. part
if (hostname.endsWith(domainSuffix)) {
return true;
}
}
// Exact match
else if (hostname === allowedDomain) {
return true;
}
}
return false;
} catch (error) {
// If URL parsing fails, deny access to be safe
return false;
}
}

View File

@@ -5,6 +5,7 @@ import {
jsonParse,
jsonStringify,
deepCopy,
isDomainAllowed,
isObjectEmpty,
fileTypeFromMimeType,
randomInt,
@@ -461,3 +462,133 @@ describe('sleepWithAbort', () => {
clearTimeoutSpy.mockRestore();
});
});
describe('isDomainAllowed', () => {
describe('when no allowed domains are specified', () => {
it('should allow all domains when allowedDomains is empty', () => {
expect(isDomainAllowed('https://example.com', { allowedDomains: '' })).toBe(true);
});
it('should allow all domains when allowedDomains contains only whitespace', () => {
expect(isDomainAllowed('https://example.com', { allowedDomains: ' ' })).toBe(true);
});
});
describe('in strict validation mode', () => {
it('should allow exact domain matches', () => {
expect(
isDomainAllowed('https://example.com', {
allowedDomains: 'example.com',
}),
).toBe(true);
});
it('should allow domains from a comma-separated list', () => {
expect(
isDomainAllowed('https://example.com', {
allowedDomains: 'test.com,example.com,other.org',
}),
).toBe(true);
});
it('should handle whitespace in allowed domains list', () => {
expect(
isDomainAllowed('https://example.com', {
allowedDomains: ' test.com , example.com , other.org ',
}),
).toBe(true);
});
it('should block non-matching domains', () => {
expect(
isDomainAllowed('https://malicious.com', {
allowedDomains: 'example.com',
}),
).toBe(false);
});
it('should block subdomains not set', () => {
expect(
isDomainAllowed('https://sub.example.com', {
allowedDomains: 'example.com',
}),
).toBe(false);
});
});
describe('with wildcard domains', () => {
it('should allow matching wildcard domains', () => {
expect(
isDomainAllowed('https://test.example.com', {
allowedDomains: '*.example.com',
}),
).toBe(true);
});
it('should allow nested subdomains with wildcards', () => {
expect(
isDomainAllowed('https://deep.nested.example.com', {
allowedDomains: '*.example.com',
}),
).toBe(true);
});
it('should block non-matching domains with wildcards', () => {
expect(
isDomainAllowed('https://example.org', {
allowedDomains: '*.example.com',
}),
).toBe(false);
});
});
describe('edge cases', () => {
it('should handle invalid URLs safely', () => {
expect(
isDomainAllowed('not-a-valid-url', {
allowedDomains: 'example.com',
}),
).toBe(false);
});
it('should handle URLs with ports', () => {
expect(
isDomainAllowed('https://example.com:8080/path', {
allowedDomains: 'example.com',
}),
).toBe(true);
});
it('should handle URLs with authentication', () => {
expect(
isDomainAllowed('https://user:pass@example.com', {
allowedDomains: 'example.com',
}),
).toBe(true);
});
it('should handle URLs with query parameters and fragments', () => {
expect(
isDomainAllowed('https://example.com/path?query=test#fragment', {
allowedDomains: 'example.com',
}),
).toBe(true);
});
it('should handle IP addresses', () => {
expect(
isDomainAllowed('https://192.168.1.1', {
allowedDomains: '192.168.1.1',
}),
).toBe(true);
});
it('should handle empty URLs', () => {
expect(
isDomainAllowed('', {
allowedDomains: 'example.com',
}),
).toBe(false);
});
});
});