discord-message-transcript-base 1.3.1-dev.2.34 → 1.3.1-dev.3.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/sanitizer.d.ts +2 -1
- package/dist/core/sanitizer.js +45 -18
- package/dist/types/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/core/sanitizer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hexColor, JsonAttachment, TranscriptOptionsBase } from "../types/types.js";
|
|
1
|
+
import { hexColor, JsonAttachment, LookupResult, TranscriptOptionsBase } from "../types/types.js";
|
|
2
2
|
export declare const FALLBACK_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
3
3
|
export declare function sanitize(text: string): string;
|
|
4
4
|
export declare function isValidHexColor(colorInput: string, canReturnNull: false): hexColor;
|
|
@@ -6,3 +6,4 @@ export declare function isValidHexColor(colorInput: string | null, canReturnNull
|
|
|
6
6
|
export declare function resolveImageURL(url: string, options: TranscriptOptionsBase, canReturnNull: false, attachments?: JsonAttachment[]): Promise<string>;
|
|
7
7
|
export declare function resolveImageURL(url: string | null, options: TranscriptOptionsBase, canReturnNull: true, attachments?: JsonAttachment[]): Promise<string | null>;
|
|
8
8
|
export declare function isSafeForHTML(url: string, options: TranscriptOptionsBase): Promise<boolean>;
|
|
9
|
+
export declare function resolveAllIps(host: string): Promise<LookupResult[]>;
|
package/dist/core/sanitizer.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import dns from "node:dns/promises";
|
|
2
1
|
import net from "node:net";
|
|
3
2
|
import { CustomWarn } from "./customMessages.js";
|
|
3
|
+
import { Resolver } from "dns/promises";
|
|
4
4
|
export const FALLBACK_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
5
|
+
const DNS_SERVERS = ["1.1.1.1", "8.8.8.8"];
|
|
5
6
|
const DNS_LOOKUP_TIMEOUT = 5000;
|
|
7
|
+
const TRUSTED_DISCORD_HOSTS = ["discordapp.com", "discordapp.net"];
|
|
6
8
|
export function sanitize(text) {
|
|
7
9
|
return text
|
|
8
10
|
.replace(/&/g, "&")
|
|
@@ -56,6 +58,10 @@ export async function isSafeForHTML(url, options) {
|
|
|
56
58
|
CustomWarn(`Unsafe URL rejected: Invalid URL format\nURL: ${url}`, disableWarnings);
|
|
57
59
|
return false;
|
|
58
60
|
}
|
|
61
|
+
const host = u.hostname.toLowerCase();
|
|
62
|
+
// If is from discord accept
|
|
63
|
+
if (isTrustedDiscordHost(host))
|
|
64
|
+
return true;
|
|
59
65
|
// Don't accept if isn't https or http
|
|
60
66
|
if (!["http:", "https:"].includes(u.protocol)) {
|
|
61
67
|
CustomWarn(`Unsafe URL rejected: Invalid protocol "${u.protocol}"\nURL: ${url}`, disableWarnings);
|
|
@@ -69,7 +75,6 @@ export async function isSafeForHTML(url, options) {
|
|
|
69
75
|
CustomWarn(`Unsafe URL rejected: Invalid port "${u.port}"\nURL: ${url}`, disableWarnings);
|
|
70
76
|
return false;
|
|
71
77
|
}
|
|
72
|
-
const host = u.hostname.toLowerCase();
|
|
73
78
|
// Block localhost and loopback addresses (SSRF protection)
|
|
74
79
|
if (host === "localhost" ||
|
|
75
80
|
host === "127.0.0.1" ||
|
|
@@ -77,21 +82,12 @@ export async function isSafeForHTML(url, options) {
|
|
|
77
82
|
CustomWarn(`Unsafe URL rejected: Blacklisted host "${host}"\nURL: ${url}`, disableWarnings);
|
|
78
83
|
return false;
|
|
79
84
|
}
|
|
80
|
-
async function lookupWithTimeout(host) {
|
|
81
|
-
return await Promise.race([
|
|
82
|
-
dns.lookup(host, { all: true }),
|
|
83
|
-
new Promise((_, reject) => setTimeout(() => {
|
|
84
|
-
CustomWarn(`DNS lookup timeout for ${host}`, disableWarnings);
|
|
85
|
-
reject(new Error());
|
|
86
|
-
}, DNS_LOOKUP_TIMEOUT))
|
|
87
|
-
]);
|
|
88
|
-
}
|
|
89
85
|
let ips;
|
|
90
86
|
try {
|
|
91
|
-
ips = await
|
|
87
|
+
ips = await resolveAllIps(host);
|
|
92
88
|
}
|
|
93
|
-
catch {
|
|
94
|
-
CustomWarn(`Unsafe URL rejected: DNS lookup failed or timed out for host "${host}"\nURL: ${url}`, disableWarnings);
|
|
89
|
+
catch (e) {
|
|
90
|
+
CustomWarn(`Unsafe URL rejected: DNS lookup failed or timed out for host "${host}". Error: ${e.message}\nURL: ${url}`, disableWarnings);
|
|
95
91
|
return false;
|
|
96
92
|
}
|
|
97
93
|
// Block private/internal network IPs (SSRF protection)
|
|
@@ -104,13 +100,38 @@ export async function isSafeForHTML(url, options) {
|
|
|
104
100
|
const path = u.pathname.toLowerCase();
|
|
105
101
|
// External SVGs can execute scripts → allow only from Discord CDN
|
|
106
102
|
if (path.endsWith(".svg")) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
103
|
+
CustomWarn(`Unsafe URL rejected: External SVG not from Discord CDN\nURL: ${url}`, disableWarnings);
|
|
104
|
+
return false;
|
|
111
105
|
}
|
|
112
106
|
return true;
|
|
113
107
|
}
|
|
108
|
+
export async function resolveAllIps(host) {
|
|
109
|
+
const resolver = new Resolver();
|
|
110
|
+
resolver.setServers(DNS_SERVERS);
|
|
111
|
+
const lookupPromise = (async () => {
|
|
112
|
+
const results = [];
|
|
113
|
+
const [v4, v6] = await Promise.allSettled([
|
|
114
|
+
resolver.resolve4(host),
|
|
115
|
+
resolver.resolve6(host)
|
|
116
|
+
]);
|
|
117
|
+
if (v4.status === "fulfilled") {
|
|
118
|
+
for (const ip of v4.value) {
|
|
119
|
+
results.push({ address: ip, family: 4 });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (v6.status === "fulfilled") {
|
|
123
|
+
for (const ip of v6.value) {
|
|
124
|
+
results.push({ address: ip, family: 6 });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (results.length === 0) {
|
|
128
|
+
throw new Error(`No DNS records found for ${host}`);
|
|
129
|
+
}
|
|
130
|
+
return results;
|
|
131
|
+
})();
|
|
132
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`DNS timeout for ${host}`)), DNS_LOOKUP_TIMEOUT));
|
|
133
|
+
return Promise.race([lookupPromise, timeoutPromise]);
|
|
134
|
+
}
|
|
114
135
|
function isPrivateIp(ip) {
|
|
115
136
|
if (!net.isIP(ip))
|
|
116
137
|
return true;
|
|
@@ -129,3 +150,9 @@ function isPrivateIp(ip) {
|
|
|
129
150
|
(parts[0] === 169 && parts[1] === 254) ||
|
|
130
151
|
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31));
|
|
131
152
|
}
|
|
153
|
+
function isTrustedDiscordHost(host) {
|
|
154
|
+
host = host.toLowerCase();
|
|
155
|
+
return TRUSTED_DISCORD_HOSTS.some(trusted => {
|
|
156
|
+
return host === trusted || host.endsWith("." + trusted);
|
|
157
|
+
});
|
|
158
|
+
}
|
package/dist/types/types.d.ts
CHANGED