fix: Update anonymous telemetry to remove values that are not needed (#25040)

Co-authored-by: yehorkardash <yehor.kardash@n8n.io>
This commit is contained in:
Jon
2026-02-04 09:06:21 +00:00
committed by GitHub
parent 832e580b39
commit d85b6888f0
3 changed files with 128 additions and 20 deletions
-1
View File
@@ -3032,7 +3032,6 @@ export interface INodeGraphItem {
operation?: string;
domain?: string; // HTTP Request node v1
domain_base?: string; // HTTP Request node v2
domain_path?: string; // HTTP Request node v2
position: [number, number];
mode?: string;
credential_type?: string; // HTTP Request node v2
+92 -7
View File
@@ -1,3 +1,5 @@
import { ApplicationError } from '@n8n/errors';
import {
AGENT_LANGCHAIN_NODE_TYPE,
AGENT_TOOL_LANGCHAIN_NODE_TYPE,
@@ -26,8 +28,8 @@ import {
WEBHOOK_NODE_TYPE,
WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
} from './constants';
import { ApplicationError } from '@n8n/errors';
import type { NodeApiError } from './errors/node-api.error';
import { DEFAULT_EVALUATION_METRIC } from './evaluation-helpers';
import type {
IConnection,
IConnections,
@@ -47,7 +49,6 @@ import type {
import { NodeConnectionTypes } from './interfaces';
import { getNodeParameters, isSubNodeType } from './node-helpers';
import { jsonParse } from './utils';
import { DEFAULT_EVALUATION_METRIC } from './evaluation-helpers';
const isNodeApiError = (error: unknown): error is NodeApiError =>
typeof error === 'object' && error !== null && 'name' in error && error?.name === 'NodeApiError';
@@ -105,18 +106,103 @@ function areOverlapping(
const URL_PARTS_REGEX = /(?<protocolPlusDomain>.*?\..*?)(?<pathname>\/.*)/;
// List of common multi-level TLDs (public suffixes)
// Covers 95%+ of real-world domains for telemetry privacy purposes
const MULTI_LEVEL_TLDS = new Set([
// UK
'co.uk',
'gov.uk',
'org.uk',
'ac.uk',
'sch.uk',
// Australia
'com.au',
'gov.au',
'edu.au',
'org.au',
'net.au',
// New Zealand
'co.nz',
'org.nz',
'net.nz',
'govt.nz',
'ac.nz',
// Japan
'co.jp',
'or.jp',
'ne.jp',
'ac.jp',
'go.jp',
// Brazil
'com.br',
'gov.br',
'org.br',
'edu.br',
// India
'co.in',
'org.in',
'net.in',
'gov.in',
'edu.in',
// South Africa
'co.za',
'org.za',
'gov.za',
'ac.za',
// AWS S3 (special case for cloud services)
's3.amazonaws.com',
]);
export function getDomainBase(raw: string, urlParts = URL_PARTS_REGEX): string {
let hostname: string;
try {
const url = new URL(raw);
return [url.protocol, url.hostname].join('//');
hostname = url.hostname;
} catch {
const match = urlParts.exec(raw);
if (!match?.groups?.protocolPlusDomain) return '';
return match.groups.protocolPlusDomain;
// Extract hostname from malformed URL
hostname = match.groups.protocolPlusDomain.replace(/^https?:\/\//, '');
}
// Handle edge cases
if (!hostname || hostname === 'localhost') return hostname;
// Handle IP addresses (v4 and v6) - return as-is
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname.includes(':')) {
return hostname;
}
const parts = hostname.split('.');
// Single-word domain (e.g., "localhost", "example")
if (parts.length === 1) {
return hostname;
}
// Check for multi-level TLDs (e.g., co.uk, com.au)
if (parts.length >= 3) {
const lastTwoParts = `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
if (MULTI_LEVEL_TLDS.has(lastTwoParts)) {
// Return last 3 parts for multi-level TLD
return parts.slice(-3).join('.');
}
// Check for 3-level TLDs (e.g., s3.amazonaws.com)
if (parts.length >= 4) {
const lastThreeParts = `${parts[parts.length - 3]}.${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
if (MULTI_LEVEL_TLDS.has(lastThreeParts)) {
// Return last 4 parts for 3-level TLD
return parts.slice(-4).join('.');
}
}
}
// Default: return last 2 parts (standard TLD like .com, .org, .net)
return parts.slice(-2).join('.');
}
function isSensitive(segment: string) {
@@ -308,7 +394,6 @@ export function generateNodesGraph(
const { url } = node.parameters as { url: string };
nodeItem.domain_base = getDomainBase(url);
nodeItem.domain_path = getDomainPath(url);
nodeItem.method = node.parameters.requestMethod as string;
} else if (HTTP_REQUEST_TOOL_LANGCHAIN_NODE_TYPE === node.type) {
if (!nodeItem.toolSettings) nodeItem.toolSettings = {};
@@ -50,6 +50,33 @@ describe('getDomainBase should return protocol plus domain', () => {
expect(getDomainBase(full)).toBe(protocolPlusDomain);
}
});
test('should handle multi-level TLDs correctly', () => {
// Test cases for multi-level TLDs (country codes, special services)
const testCases = [
{ input: 'https://api.example.co.uk/path', expected: 'example.co.uk' },
{ input: 'https://user.service.com.au/api', expected: 'service.com.au' },
{ input: 'https://api.example.co.jp/test', expected: 'example.co.jp' },
{ input: 'https://test.example.gov.uk/data', expected: 'example.gov.uk' },
{ input: 'https://sub.domain.org.nz/path', expected: 'domain.org.nz' },
{ input: 'api.example.co.uk/path', expected: 'example.co.uk' },
];
testCases.forEach(({ input, expected }) => {
expect(getDomainBase(input)).toBe(expected);
});
});
test('should handle edge cases', () => {
// Single word domains, localhost, etc.
expect(getDomainBase('https://localhost/path')).toBe('localhost');
expect(getDomainBase('https://example/path')).toBe('example');
});
test('should handle IP addresses', () => {
expect(getDomainBase('https://192.168.1.1/path')).toBe('192.168.1.1');
expect(getDomainBase('https://[::1]/path')).toBe('[::1]');
});
});
describe('getDomainPath should return pathname, excluding query string', () => {
@@ -941,7 +968,6 @@ describe('generateNodesGraph', () => {
credential_type: 'httpBasicAuth',
credential_set: true,
domain_base: 'google.com',
domain_path: '/path/test',
},
},
notes: {},
@@ -996,7 +1022,6 @@ describe('generateNodesGraph', () => {
credential_type: 'activeCampaignApi',
credential_set: true,
domain_base: 'google.com',
domain_path: '/path/test',
},
},
notes: {},
@@ -1201,7 +1226,6 @@ describe('generateNodesGraph', () => {
position: [600, 240],
credential_set: false,
domain_base: '',
domain_path: '',
},
},
notes: {},
@@ -2268,42 +2292,42 @@ function validUrls(idMaker: typeof alphanumericId | typeof email, char = CHAR) {
return [
{
full: `https://test.com/api/v1/users/${firstId}`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}`,
},
{
full: `https://test.com/api/v1/users/${firstId}/`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `https://test.com/api/v1/users?id=${firstId}`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: '/api/v1/users',
},
{
full: `https://test.com/api/v1/users?id=${firstId}&post=${secondId}`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: '/api/v1/users',
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}`,
protocolPlusDomain: 'https://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}`,
},
];
@@ -2323,7 +2347,7 @@ function malformedUrls(idMaker: typeof numericId | typeof email, char = CHAR) {
},
{
full: `htp://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'htp://test.com',
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{