blackveil-dns 2.10.9 → 2.10.10
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/README.md +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +39 -6
- package/dist/index.js.map +1 -1
- package/dist/stdio.js +41 -6
- package/dist/stdio.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Open-source DNS & email security scanner for Claude, Cursor, VS Code, and MCP cl
|
|
|
9
9
|
[](https://github.com/MadaBurns/bv-mcp/stargazers)
|
|
10
10
|
[](https://www.npmjs.com/package/blackveil-dns)
|
|
11
11
|
[](https://www.npmjs.com/package/blackveil-dns)
|
|
12
|
-
[](https://github.com/MadaBurns/bv-mcp/actions)
|
|
13
13
|
[](https://github.com/MadaBurns/bv-mcp/actions)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
[](https://modelcontextprotocol.io/)
|
package/dist/index.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ declare function sanitizeDomain(input: string): string;
|
|
|
192
192
|
declare function sanitizeInput(input: string, maxLength?: number): string;
|
|
193
193
|
|
|
194
194
|
/** Server version — keep in sync with package.json */
|
|
195
|
-
declare const SERVER_VERSION = "2.10.
|
|
195
|
+
declare const SERVER_VERSION = "2.10.10";
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
198
|
* Map of every tool name to its Zod argument schema.
|
|
@@ -231,6 +231,11 @@ declare const TOOLS: McpTool[];
|
|
|
231
231
|
* Check BIMI records for a domain.
|
|
232
232
|
* Validates the presence and configuration of BIMI TXT records,
|
|
233
233
|
* including logo URL format and VMC authority evidence.
|
|
234
|
+
*
|
|
235
|
+
* BIMI `l=` and `a=` tags are extracted from a TXT record at default._bimi.<domain>
|
|
236
|
+
* and are entirely attacker-controlled. We pass safeFetch instead of the raw
|
|
237
|
+
* `fetch` so the destination hostname is validated before any outbound request
|
|
238
|
+
* (H2 fix from the 2026-05-08 security audit).
|
|
234
239
|
*/
|
|
235
240
|
declare function checkBimi(domain: string, dnsOptions?: QueryDnsOptions): Promise<CheckResult>;
|
|
236
241
|
|
package/dist/index.js
CHANGED
|
@@ -105,7 +105,7 @@ z.object({
|
|
|
105
105
|
var REDACTED = "[redacted]";
|
|
106
106
|
var MAX_LOG_STRING_LENGTH = 256;
|
|
107
107
|
var MAX_ERROR_STRING_LENGTH = 1024;
|
|
108
|
-
var SENSITIVE_KEY_PATTERN = /(^ip$|authorization|mcp-session-id|session|token|api[-_]?key|secret|password|cookie|rawbody)/i;
|
|
108
|
+
var SENSITIVE_KEY_PATTERN = /(^ip$|cf-connecting-ip|authorization|mcp-session-id|session|token|api[-_]?key|secret|password|cookie|rawbody)/i;
|
|
109
109
|
function isSensitiveKey(key) {
|
|
110
110
|
return !/^has[A-Z]/.test(key) && SENSITIVE_KEY_PATTERN.test(key);
|
|
111
111
|
}
|
|
@@ -580,6 +580,24 @@ function sanitizeDomain(input) {
|
|
|
580
580
|
return "";
|
|
581
581
|
}
|
|
582
582
|
}
|
|
583
|
+
function validateOutboundUrl(input) {
|
|
584
|
+
if (!input || typeof input !== "string") {
|
|
585
|
+
return { valid: false, error: "URL is required" };
|
|
586
|
+
}
|
|
587
|
+
let url;
|
|
588
|
+
try {
|
|
589
|
+
url = new URL(input);
|
|
590
|
+
} catch {
|
|
591
|
+
return { valid: false, error: "URL is malformed" };
|
|
592
|
+
}
|
|
593
|
+
if (url.protocol !== "https:") {
|
|
594
|
+
return { valid: false, error: `URL must use https (got "${url.protocol}")` };
|
|
595
|
+
}
|
|
596
|
+
if (url.username || url.password) {
|
|
597
|
+
return { valid: false, error: "URL must not contain userinfo" };
|
|
598
|
+
}
|
|
599
|
+
return validateDomain(url.hostname);
|
|
600
|
+
}
|
|
583
601
|
function sanitizeInput(input, maxLength = 500) {
|
|
584
602
|
if (typeof input !== "string") return "";
|
|
585
603
|
const sanitized = input.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
@@ -587,7 +605,7 @@ function sanitizeInput(input, maxLength = 500) {
|
|
|
587
605
|
}
|
|
588
606
|
|
|
589
607
|
// src/lib/server-version.ts
|
|
590
|
-
var SERVER_VERSION = "2.10.
|
|
608
|
+
var SERVER_VERSION = "2.10.10";
|
|
591
609
|
var DomainSchema = z.string().min(1).max(253);
|
|
592
610
|
z.string().regex(/^[0-9a-f]{64}$/);
|
|
593
611
|
var DkimSelectorSchema = z.string().transform((s) => s.trim().toLowerCase()).pipe(z.string().max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/));
|
|
@@ -682,7 +700,7 @@ var GetBenchmarkArgs = z.object({
|
|
|
682
700
|
format: FormatSchema.optional().describe("Output verbosity. Auto-detected if omitted.")
|
|
683
701
|
}).passthrough();
|
|
684
702
|
var GetProviderInsightsArgs = z.object({
|
|
685
|
-
provider: z.string().min(1).describe('Provider (e.g., "google workspace").'),
|
|
703
|
+
provider: z.string().min(1).max(200).describe('Provider (e.g., "google workspace").'),
|
|
686
704
|
profile: BenchmarkProfileSchema.optional().describe('Profile (default "mail_enabled").'),
|
|
687
705
|
format: FormatSchema.optional().describe("Output verbosity. Auto-detected if omitted.")
|
|
688
706
|
}).passthrough();
|
|
@@ -1143,12 +1161,27 @@ function makeQueryDNS(dnsOptions) {
|
|
|
1143
1161
|
};
|
|
1144
1162
|
}
|
|
1145
1163
|
|
|
1164
|
+
// src/lib/safe-fetch.ts
|
|
1165
|
+
function urlOf(input) {
|
|
1166
|
+
if (typeof input === "string") return input;
|
|
1167
|
+
if (input instanceof URL) return input.href;
|
|
1168
|
+
return input.url;
|
|
1169
|
+
}
|
|
1170
|
+
var safeFetch = async (input, init) => {
|
|
1171
|
+
const url = urlOf(input);
|
|
1172
|
+
const validation = validateOutboundUrl(url);
|
|
1173
|
+
if (!validation.valid) {
|
|
1174
|
+
throw new TypeError(`Outbound fetch blocked: ${validation.error ?? "invalid URL"}`);
|
|
1175
|
+
}
|
|
1176
|
+
return fetch(input, init);
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1146
1179
|
// src/tools/check-bimi.ts
|
|
1147
1180
|
async function checkBimi(domain, dnsOptions) {
|
|
1148
1181
|
return checkBIMI(
|
|
1149
1182
|
domain,
|
|
1150
1183
|
makeQueryDNS(dnsOptions),
|
|
1151
|
-
{ timeout: dnsOptions?.timeoutMs ?? 5e3, fetchFn:
|
|
1184
|
+
{ timeout: dnsOptions?.timeoutMs ?? 5e3, fetchFn: safeFetch }
|
|
1152
1185
|
);
|
|
1153
1186
|
}
|
|
1154
1187
|
async function checkCaa(domain, dnsOptions) {
|
|
@@ -3171,7 +3204,7 @@ async function fetchWithRedirects(url, timeoutMs) {
|
|
|
3171
3204
|
}
|
|
3172
3205
|
if (!nextUrl.startsWith("https://")) break;
|
|
3173
3206
|
try {
|
|
3174
|
-
response = await
|
|
3207
|
+
response = await safeFetch(nextUrl, {
|
|
3175
3208
|
method: "HEAD",
|
|
3176
3209
|
redirect: "manual",
|
|
3177
3210
|
headers: { "User-Agent": SCANNER_USER_AGENT },
|
|
@@ -3275,7 +3308,7 @@ async function checkHttpSecurityInner(domain) {
|
|
|
3275
3308
|
headers: dualResult.headers
|
|
3276
3309
|
});
|
|
3277
3310
|
}
|
|
3278
|
-
const response = await
|
|
3311
|
+
const response = await safeFetch(input, init);
|
|
3279
3312
|
capturedHeaders = response.headers;
|
|
3280
3313
|
return response;
|
|
3281
3314
|
};
|