fix(tracer): resolve discord community cidr filter (#50) and improve leak check display

This commit is contained in:
Dushnilin
2026-09-14 06:27:31 +03:00
parent 959bcb59f5
commit ea0de78cca
5 changed files with 463 additions and 170 deletions
@@ -128,7 +128,7 @@ export function renderLeakCheckModal() {
const ipAlertClass = !ip_leak.proxy_online
? 'alert-message info'
: isDirectRouting
? 'alert-message info'
? 'alert-message warning'
: 'alert-message success';
const ipAlertText = !ip_leak.proxy_online
@@ -266,32 +266,36 @@ export function renderLeakCheckModal() {
);
// --- 2. DNS Leak Section ---
const dnsAlertClass =
!dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0
? 'alert-message info'
: dns_leak.dns_leaked
? 'alert-message warning'
: 'alert-message success';
const proxyDnsServers = dns_leak.dns_servers || [];
const directDnsServers = dns_leak.direct_dns_servers || [];
const hasProxyDns = proxyDnsServers.length > 0;
const dnsAlertText =
!dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0
const dnsAlertClass = !hasProxyDns
? 'alert-message info'
: dns_leak.dns_leaked
? 'alert-message warning'
: 'alert-message success';
const dnsAlertText = !hasProxyDns
? _(
'DNS resolvers through proxy are not captured (proxy is offline or test domain is not intercepted).',
)
: dns_leak.dns_leaked
? _(
'No DNS resolvers captured through proxy (proxy is offline or test domain is not intercepted).',
'ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections.',
)
: dns_leak.dns_leaked
? _(
'ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections.',
)
: _(
'🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers.',
);
: _(
'🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers.',
);
const dnsTableRows = (dns_leak.dns_servers || []).map((s) =>
const makeDnsRow = (s: Tachyon.DNSResolverInfo, pathLabel: string) =>
E('tr', { class: 'tr cbi-section-table-row' }, [
E('td', { class: 'td' }, [E('code', {}, s.ip)]),
E('td', { class: 'td' }, s.country || '—'),
E('td', { class: 'td' }, s.isp || '—'),
E('td', { class: 'td', style: 'text-align: center;' }, [
E(
'td',
{ class: 'td', style: 'text-align: center;' },
s.is_isp
? E(
'span',
@@ -311,9 +315,24 @@ export function renderLeakCheckModal() {
},
_('SAFE'),
),
]),
]),
),
E(
'td',
{
class: 'td',
style: 'font-size: 10px; color: var(--text-color-medium, #6c757d);',
},
pathLabel,
),
]);
const proxyDnsRows = proxyDnsServers.map((s) =>
makeDnsRow(s, _('via Proxy')),
);
const directDnsRows = directDnsServers.map((s) =>
makeDnsRow(s, _('via WAN')),
);
const allDnsRows = [...proxyDnsRows, ...directDnsRows];
const dnsTable = E(
'table',
@@ -332,20 +351,21 @@ export function renderLeakCheckModal() {
{ class: 'th', style: 'text-align: center;' },
_('Verdict'),
),
E('th', { class: 'th' }, _('Path')),
]),
]),
E(
'tbody',
{},
dnsTableRows.length > 0
? dnsTableRows
allDnsRows.length > 0
? allDnsRows
: [
E('tr', { class: 'tr' }, [
E(
'td',
{
class: 'td',
colSpan: 4,
colSpan: 5,
style: 'text-align: center; opacity: 0.7;',
},
_('No DNS resolvers recorded'),
@@ -1,23 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { beforeEach, describe, expect, it, vi } from 'vitest';
// ---------------------------------------------------------------------------
// Hoisted mocks — must come before any imports that transitively use globals
// ---------------------------------------------------------------------------
const mocks = vi.hoisted(() => {
class MockMutationObserver {
observe() {}
disconnect() {}
takeRecords() {
return [];
}
}
(globalThis as any).MutationObserver = MockMutationObserver;
function createDummyElement(tag: string) {
return {
// Minimal DOM mock that tracks tree structure for inspection
function createEl(tag: string) {
const el: any = {
tagName: tag.toUpperCase(),
children: [] as any[],
style: {} as Record<string, string>,
textContent: '',
innerHTML: '',
class: '',
colSpan: 0,
setAttribute() {},
getAttribute() {
return null;
@@ -25,27 +22,28 @@ const mocks = vi.hoisted(() => {
addEventListener() {},
querySelectorAll: () => [],
querySelector: () => null,
replaceChildren: function (...nodes: any[]) {
replaceChildren(...nodes: any[]) {
(this as any).children = nodes;
},
appendChild: function (node: any) {
(this as any).children.push(node);
appendChild(node: any) {
if (node && typeof node === 'object') {
(this as any).children.push(node);
} else if (node) {
(this as any).textContent += String(node);
}
},
append: function (...nodes: any[]) {
nodes.forEach((node) => (this as any).children.push(node));
},
classList: {
add() {},
remove() {},
append(...nodes: any[]) {
nodes.forEach((n) => (this as any).appendChild(n));
},
classList: { add() {}, remove() {} },
};
return el;
}
(globalThis as any).document = {
body: {} as any,
createElement: (tag: string) => createDummyElement(tag) as any,
createElementNS: (_ns: string, tag: string) =>
createDummyElement(tag) as any,
createElement: (tag: string) => createEl(tag),
createElementNS: (_ns: string, tag: string) => createEl(tag),
getElementById: () => null,
querySelector: () => null,
querySelectorAll: () => [],
@@ -59,18 +57,16 @@ const mocks = vi.hoisted(() => {
if (style && typeof style === 'string') {
style.split(';').forEach((rule: string) => {
const [k, v] = rule.split(':').map((s: string) => s.trim());
if (k && v) {
el.style[k] = v;
}
if (k && v) el.style[k] = v;
});
} else if (style && typeof style === 'object') {
Object.assign(el.style, style);
}
}
if (children) {
if (children !== undefined && children !== null) {
if (Array.isArray(children)) {
children.forEach((c) => {
if (c) el.appendChild(c);
if (c != null) el.appendChild(c);
});
} else {
el.appendChild(children);
@@ -80,74 +76,125 @@ const mocks = vi.hoisted(() => {
};
(globalThis as any)._ = (str: string) => str;
(globalThis as any).rpc = { declare: vi.fn() };
(globalThis as any).uci = { sections: vi.fn().mockResolvedValue([]) };
(globalThis as any).localStorage = { getItem: vi.fn(), setItem: vi.fn() };
class MockMutationObserver {
observe() {}
disconnect() {}
takeRecords() {
return [];
}
}
(globalThis as any).MutationObserver = MockMutationObserver;
const showModal = vi.fn();
const hideModal = vi.fn();
(globalThis as any).ui = {
showModal,
hideModal,
addNotification: vi.fn(),
};
(globalThis as any).ui = { showModal, hideModal, addNotification: vi.fn() };
return {
showModal,
hideModal,
};
return { showModal, hideModal };
});
// ---------------------------------------------------------------------------
// Imports after mocks
// ---------------------------------------------------------------------------
import { renderLeakCheckModal } from '../partials/renderLeakCheckModal';
import { TachyonShellMethods } from '../../../methods/shell';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Recursively collect all text content from a mock element tree. */
function collectText(node: any): string {
if (!node || typeof node !== 'object') return String(node ?? '');
let text = node.textContent || '';
for (const child of node.children || []) {
text += collectText(child);
}
return text;
}
/** Find all elements matching a class attribute value anywhere in the tree. */
function findByClass(node: any, cls: string): any[] {
const results: any[] = [];
if (!node || typeof node !== 'object') return results;
if (typeof node.class === 'string' && node.class.includes(cls)) {
results.push(node);
}
for (const child of node.children || []) {
results.push(...findByClass(child, cls));
}
return results;
}
/** Extract text from the second argument of the last showModal call. */
function getModalContent(): any {
const calls = mocks.showModal.mock.calls;
return calls[calls.length - 1]?.[1];
}
// Default "happy path" leak result
function makeSuccessResult(overrides: Record<string, any> = {}) {
return {
success: true as const,
data: {
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
direct_country: 'Russia',
direct_city: 'Moscow',
direct_isp: 'Rostelecom',
proxy_ip: '185.220.101.5',
proxy_country: 'Netherlands',
proxy_city: 'Amsterdam',
proxy_org: 'Mullvad',
proxy_online: true,
},
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '185.220.101.5',
dns_servers: [
{
ip: '1.1.1.1',
country: 'United States',
isp: 'Cloudflare',
is_isp: false,
},
],
direct_dns_servers: [
{
ip: '212.188.4.10',
country: 'Russia',
isp: 'Rostelecom',
is_isp: true,
},
],
proxy_online: true,
},
...overrides,
},
};
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('renderLeakCheckModal', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.showModal.mockReset();
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: true,
data: {
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
direct_country: 'Russia',
direct_isp: 'Rostelecom',
proxy_ip: '185.220.101.5',
proxy_country: 'Netherlands',
proxy_org: 'Mullvad',
proxy_online: true,
},
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '185.220.101.5',
dns_servers: [
{
ip: '1.1.1.1',
country: 'United States',
isp: 'Cloudflare',
is_isp: false,
},
],
direct_dns_servers: [
{
ip: '212.188.4.10',
country: 'Russia',
isp: 'Rostelecom',
is_isp: true,
},
],
proxy_online: true,
},
},
});
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult(),
);
});
it('renders and displays the IP & DNS Leak Detection modal', () => {
renderLeakCheckModal();
// ── Modal lifecycle ────────────────────────────────────────────────────────
it('renders the modal with correct title', () => {
renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1);
expect(mocks.showModal).toHaveBeenCalledWith(
expect.stringContaining('Tachyon IP & DNS Leak Detection'),
@@ -155,49 +202,118 @@ describe('renderLeakCheckModal', () => {
);
});
it('calls TachyonShellMethods.leakCheck on start', async () => {
const leakCheckSpy = vi.spyOn(TachyonShellMethods, 'leakCheck');
it('auto-starts the leak check on open', () => {
const spy = vi.spyOn(TachyonShellMethods, 'leakCheck');
renderLeakCheckModal();
expect(leakCheckSpy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(1);
});
it('handles leak detection when IP is leaked', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: true,
data: {
// ── IP section — SECURE path ───────────────────────────────────────────────
it('shows SECURE alert when proxy is online and IPs differ', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('SECURE');
expect(text).toContain('185.220.101.5');
});
});
it('shows SECURE badge (not DIRECT or INACTIVE) when IPs differ', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('SECURE');
expect(badgeTexts).not.toContain('DIRECT');
expect(badgeTexts).not.toContain('INACTIVE');
});
});
it('alert class is success when proxy is online and IPs differ', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
// Find the IP alert div
const alertDivs = findByClass(content, 'alert-message');
const successAlerts = alertDivs.filter((d) =>
d.class?.includes('success'),
);
expect(successAlerts.length).toBeGreaterThan(0);
});
});
// ── IP section — DIRECT / leaked path ─────────────────────────────────────
it('shows DIRECT badge when proxy_ip equals direct_ip', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: true,
direct_ip: '95.173.136.25',
proxy_ip: '95.173.136.25',
proxy_online: true,
},
dns_leak: {
dns_leaked: true,
direct_ip: '95.173.136.25',
proxy_ip: '95.173.136.25',
dns_servers: [
{
ip: '212.188.4.10',
country: 'Russia',
isp: 'Rostelecom',
is_isp: true,
},
],
direct_dns_servers: [],
proxy_online: true,
},
},
});
}),
);
renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1);
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('DIRECT');
});
});
it('handles offline proxy gracefully', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: true,
data: {
it('shows warning alert class when IP is leaked', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: true,
direct_ip: '95.173.136.25',
proxy_ip: '95.173.136.25',
proxy_online: true,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const alertDivs = findByClass(content, 'alert-message');
const warningAlerts = alertDivs.filter((d) =>
d.class?.includes('warning'),
);
expect(warningAlerts.length).toBeGreaterThan(0);
});
});
// ── IP section — proxy offline ─────────────────────────────────────────────
it('shows INACTIVE badge when proxy_online=false', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
proxy_online: false,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('INACTIVE');
});
});
it('shows info alert class when proxy offline', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
@@ -212,10 +328,151 @@ describe('renderLeakCheckModal', () => {
direct_dns_servers: [],
proxy_online: false,
},
},
});
}),
);
renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1);
await vi.waitFor(() => {
const content = getModalContent();
const alertDivs = findByClass(content, 'alert-message');
const infoAlerts = alertDivs.filter((d) => d.class?.includes('info'));
expect(infoAlerts.length).toBeGreaterThan(0);
});
});
// ── DNS section ────────────────────────────────────────────────────────────
it('renders proxy DNS resolvers with "via Proxy" path label', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('1.1.1.1');
expect(text).toContain('via Proxy');
});
});
it('renders direct_dns_servers with "via WAN" path label', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
// direct_dns_servers IP from default mock
expect(text).toContain('212.188.4.10');
expect(text).toContain('via WAN');
});
});
it('marks ISP DNS servers with ISP DNS badge', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('ISP DNS');
});
});
it('marks non-ISP DNS servers with SAFE badge', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('SAFE');
});
});
it('shows direct DNS servers even when proxy offline', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
proxy_online: false,
},
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
dns_servers: [],
direct_dns_servers: [
{ ip: '8.8.8.8', country: 'US', isp: 'Google', is_isp: false },
],
proxy_online: false,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('8.8.8.8');
expect(text).toContain('via WAN');
});
});
it('shows "No DNS resolvers recorded" when both lists are empty', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '185.220.101.5',
dns_servers: [],
direct_dns_servers: [],
proxy_online: true,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('No DNS resolvers recorded');
});
});
// ── Error handling ─────────────────────────────────────────────────────────
it('shows error alert when leakCheck returns success=false', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: false,
error: 'Connection timeout',
} as any);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Connection timeout');
});
});
it('shows error alert on unexpected exception from leakCheck', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockRejectedValue(
new Error('Network error'),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Network error');
});
});
// ── Re-run button ──────────────────────────────────────────────────────────
it('renders Re-run Leak Test button', () => {
renderLeakCheckModal();
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Re-run Leak Test');
});
it('renders Close button', () => {
renderLeakCheckModal();
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Close');
});
});
@@ -15082,7 +15082,7 @@ function renderLeakCheckModal() {
resultsContainer.innerHTML = "";
const { ip_leak, dns_leak } = data;
const isDirectRouting = ip_leak.proxy_online && (ip_leak.leaked || ip_leak.direct_ip === ip_leak.proxy_ip);
const ipAlertClass = !ip_leak.proxy_online ? "alert-message info" : isDirectRouting ? "alert-message info" : "alert-message success";
const ipAlertClass = !ip_leak.proxy_online ? "alert-message info" : isDirectRouting ? "alert-message warning" : "alert-message success";
const ipAlertText = !ip_leak.proxy_online ? _("Proxy outbound is inactive or not configured for local testing.") : isDirectRouting ? _(
"ℹ️ Direct connection (WAN): Public IP matches your ISP. Under selective routing (by domains or blocklists), unblocked resources bypass the proxy — this is standard operation."
) : _("🛡️ SECURE: Public IP is concealed behind the proxy outbound.");
@@ -15195,38 +15195,56 @@ function renderLeakCheckModal() {
ipTable
]
);
const dnsAlertClass = !dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0 ? "alert-message info" : dns_leak.dns_leaked ? "alert-message warning" : "alert-message success";
const dnsAlertText = !dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0 ? _(
"No DNS resolvers captured through proxy (proxy is offline or test domain is not intercepted)."
const proxyDnsServers = dns_leak.dns_servers || [];
const directDnsServers = dns_leak.direct_dns_servers || [];
const hasProxyDns = proxyDnsServers.length > 0;
const dnsAlertClass = !hasProxyDns ? "alert-message info" : dns_leak.dns_leaked ? "alert-message warning" : "alert-message success";
const dnsAlertText = !hasProxyDns ? _(
"DNS resolvers through proxy are not captured (proxy is offline or test domain is not intercepted)."
) : dns_leak.dns_leaked ? _(
"ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections."
) : _(
"🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers."
);
const dnsTableRows = (dns_leak.dns_servers || []).map(
(s) => E("tr", { class: "tr cbi-section-table-row" }, [
E("td", { class: "td" }, [E("code", {}, s.ip)]),
E("td", { class: "td" }, s.country || "—"),
E("td", { class: "td" }, s.isp || "—"),
E("td", { class: "td", style: "text-align: center;" }, [
s.is_isp ? E(
"span",
{
class: "badge",
style: "background: #fd7e14; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;"
},
_("ISP DNS")
) : E(
"span",
{
class: "badge",
style: "background: #28a745; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;"
},
_("SAFE")
)
])
])
const makeDnsRow = (s, pathLabel) => E("tr", { class: "tr cbi-section-table-row" }, [
E("td", { class: "td" }, [E("code", {}, s.ip)]),
E("td", { class: "td" }, s.country || "—"),
E("td", { class: "td" }, s.isp || "—"),
E(
"td",
{ class: "td", style: "text-align: center;" },
s.is_isp ? E(
"span",
{
class: "badge",
style: "background: #fd7e14; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;"
},
_("ISP DNS")
) : E(
"span",
{
class: "badge",
style: "background: #28a745; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;"
},
_("SAFE")
)
),
E(
"td",
{
class: "td",
style: "font-size: 10px; color: var(--text-color-medium, #6c757d);"
},
pathLabel
)
]);
const proxyDnsRows = proxyDnsServers.map(
(s) => makeDnsRow(s, _("via Proxy"))
);
const directDnsRows = directDnsServers.map(
(s) => makeDnsRow(s, _("via WAN"))
);
const allDnsRows = [...proxyDnsRows, ...directDnsRows];
const dnsTable = E(
"table",
{
@@ -15243,19 +15261,20 @@ function renderLeakCheckModal() {
"th",
{ class: "th", style: "text-align: center;" },
_("Verdict")
)
),
E("th", { class: "th" }, _("Path"))
])
]),
E(
"tbody",
{},
dnsTableRows.length > 0 ? dnsTableRows : [
allDnsRows.length > 0 ? allDnsRows : [
E("tr", { class: "tr" }, [
E(
"td",
{
class: "td",
colSpan: 4,
colSpan: 5,
style: "text-align: center; opacity: 0.7;"
},
_("No DNS resolvers recorded")
@@ -12130,9 +12130,6 @@ async function performTrace(query) {
for (const community of communityLists) {
let subnets = await readSubnetListFile(community);
if (community === "discord") {
subnets = subnets.filter((s) => !isCloudflareSharedCidr(s));
}
const matchedCommunityCidr = matchIpInCidrs(ip, subnets);
if (matchedCommunityCidr) {
return {
+1 -1
View File
@@ -72,7 +72,7 @@ CONFIG="$WORK_DIR/singbox.json"
node -e '
const fs = require("fs");
const cfg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const rules = cfg.route?.rules || [];
const rules = (cfg.route && cfg.route.rules) || [];
const hijackDnsPort = rules.find(r => r.action === "hijack-dns" && r.inbound === "server-home_node-in" && r.port === 53);
if (!hijackDnsPort) {