blackveil-dns 2.9.2 → 2.10.1

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 CHANGED
@@ -82,7 +82,7 @@ Transport support:
82
82
  ## Tools
83
83
 
84
84
  ```
85
- 44 MCP tools · 7 prompts · 6 resources
85
+ 51 MCP tools · 7 prompts · 6 resources
86
86
 
87
87
  Email Auth Infrastructure Brand & Threats Meta
88
88
  ──────────── ──────────────── ───────────────── ──────────────────────
@@ -111,10 +111,12 @@ Transport support:
111
111
 
112
112
  ## Quality & Reliability
113
113
 
114
- The server is continuously validated using a **comprehensive chaos test suite** that covers all 9 detected MCP client types:
114
+ The server is continuously validated using a **comprehensive chaos test suite** that covers all detected MCP client types:
115
115
 
116
- - **Interactive clients**: `claude_code`, `cursor`, `vscode`, `claude_desktop`, `windsurf` (auto-format: `compact`)
117
- - **Non-interactive clients**: `mcp_remote`, `blackveil_dns_action`, `bv_claude_dns_proxy`, `unknown` (auto-format: `full`)
116
+ - **Interactive clients**: `claude_mobile`, `claude_code`, `cursor`, `vscode`, `claude_desktop`, `windsurf` (auto-format: `compact`)
117
+ - **Non-interactive clients**: `mcp_remote`, `blackveil_dns_action`, `bv_claude_dns_proxy`, `bv_load_test`, `unknown` (auto-format: `full`)
118
+
119
+ The `bv_load_test` class identifies internal load/chaos/tranco-scan traffic so it stays out of real-client analytics segments.
118
120
 
119
121
  The test suite ensures session stability, authentication precedence, and transport-specific edge cases across Streamable HTTP and Legacy SSE.
120
122
 
@@ -187,6 +189,28 @@ For full hosted setup examples, stdio usage, OAuth setup, and legacy fallback en
187
189
 
188
190
  ---
189
191
 
192
+ ## Example prompts
193
+
194
+ These demonstrate core functionality — paste any of them into Claude with the Blackveil DNS connector enabled:
195
+
196
+ | Prompt | What it does |
197
+ |--------|-------------|
198
+ | `Scan blackveilsecurity.com and tell me what needs fixing` | Full security audit — score, grade, prioritized findings |
199
+ | `Compare the email security of google.com and microsoft.com` | Side-by-side comparison of two domains' postures |
200
+ | `Generate a DMARC record for example.com with reject policy` | Produces a ready-to-publish DNS record |
201
+ | `What attack paths exist for example.com?` | Enumerates spoofing, takeover, and hijack vectors |
202
+ | `Map example.com's compliance against NIST 800-177` | Maps findings to compliance framework controls |
203
+
204
+ ---
205
+
206
+ ## Support
207
+
208
+ - **Bug reports & feature requests:** [GitHub Issues](https://github.com/MadaBurns/bv-mcp/issues)
209
+ - **Security vulnerabilities:** [security@blackveilsecurity.com](mailto:security@blackveilsecurity.com) (see [SECURITY.md](SECURITY.md))
210
+ - **General questions:** [GitHub Discussions](https://github.com/MadaBurns/bv-mcp/discussions)
211
+
212
+ ---
213
+
190
214
  ## Responsible use
191
215
 
192
216
  This tool is intended for **authorized security assessments** of domains you own or have explicit permission to test. Do not use it for unauthorized reconnaissance, harassment, or any activity that violates applicable laws. Findings from attack simulation, spoofability, and subdomain discovery tools should be used to **improve your own security posture**, not to exploit others.
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.9.2";
195
+ declare const SERVER_VERSION = "2.10.1";
196
196
 
197
197
  /**
198
198
  * Map of every tool name to its Zod argument schema.
@@ -296,6 +296,10 @@ declare function checkNs(domain: string, dnsOptions?: QueryDnsOptions): Promise<
296
296
  * Check SPF records for a domain.
297
297
  * Looks for v=spf1 TXT records and validates their configuration.
298
298
  * Recursively expands include chains to compute true DNS lookup count.
299
+ *
300
+ * Top-level DNS failures (timeout, DoH HTTP error, invalid response) are
301
+ * converted to a high-severity finding so callers receive a structured
302
+ * CheckResult instead of a thrown error.
299
303
  */
300
304
  declare function checkSpf(domain: string, dnsOptions?: QueryDnsOptions): Promise<CheckResult>;
301
305
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { PROFILE_WEIGHTS, buildCheckResult, createFinding, detectDomainContext, getProfileWeights, computeScanScore, scoreToGrade } from '@blackveil/dns-checks/scoring';
3
3
  export { CATEGORY_DISPLAY_WEIGHTS, SEVERITY_PENALTIES, buildCheckResult, computeCategoryScore, computeScanScore, createFinding, detectDomainContext, getProfileWeights, inferFindingConfidence, scoreToGrade } from '@blackveil/dns-checks/scoring';
4
4
  import * as punycode from 'punycode/';
5
- import { checkBIMI, checkCAA, checkDKIM, checkDMARC, checkDNSSEC, checkMTASTS, checkMX, createFinding as createFinding$1, checkNS, checkSPF, checkSSL, checkSubdomainTakeover as checkSubdomainTakeover$1, checkTLSRPT, buildCheckResult as buildCheckResult$1, checkSubdomailing as checkSubdomailing$1, checkSVCBHTTPS, checkDANEHTTPS, checkDANE, checkHTTPSecurity, parseDmarcTags } from '@blackveil/dns-checks';
5
+ import { checkBIMI, checkCAA, checkDKIM, checkDMARC, checkDNSSEC, checkMTASTS, checkMX, createFinding as createFinding$1, checkNS, checkSPF, checkSSL, checkSubdomainTakeover as checkSubdomainTakeover$1, checkTLSRPT, buildCheckResult as buildCheckResult$1, checkSubdomailing as checkSubdomailing$1, checkSVCBHTTPS, checkDANEHTTPS, checkDANE, parseDmarcTags, checkHTTPSecurity } from '@blackveil/dns-checks';
6
6
  export { parseDmarcTags } from '@blackveil/dns-checks';
7
7
  import 'cloudflare:workers';
8
8
  import 'drizzle-orm';
@@ -587,7 +587,7 @@ function sanitizeInput(input, maxLength = 500) {
587
587
  }
588
588
 
589
589
  // src/lib/server-version.ts
590
- var SERVER_VERSION = "2.9.2";
590
+ var SERVER_VERSION = "2.10.1";
591
591
  var DomainSchema = z.string().min(1).max(253);
592
592
  z.string().regex(/^[0-9a-f]{64}$/);
593
593
  var DkimSelectorSchema = z.string().transform((s) => s.trim().toLowerCase()).pipe(z.string().max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/));
@@ -2028,11 +2028,29 @@ async function checkNs(domain, dnsOptions) {
2028
2028
  );
2029
2029
  }
2030
2030
  async function checkSpf(domain, dnsOptions) {
2031
- return checkSPF(
2032
- domain,
2033
- makeQueryDNS(dnsOptions),
2034
- { timeout: dnsOptions?.timeoutMs ?? 5e3 }
2035
- );
2031
+ try {
2032
+ return await checkSPF(
2033
+ domain,
2034
+ makeQueryDNS(dnsOptions),
2035
+ { timeout: dnsOptions?.timeoutMs ?? 5e3 }
2036
+ );
2037
+ } catch (err) {
2038
+ const message = err instanceof Error ? err.message : String(err);
2039
+ const isTimeout = /timed? out|timeout/i.test(message);
2040
+ return buildCheckResult("spf", [
2041
+ createFinding(
2042
+ "spf",
2043
+ isTimeout ? "SPF check timed out" : "SPF check could not complete",
2044
+ "high",
2045
+ isTimeout ? `DNS lookup for the domain timed out before the SPF record could be resolved: ${message}` : `DNS lookup failed before the SPF record could be resolved: ${message}`,
2046
+ {
2047
+ errorKind: isTimeout ? "timeout" : "dns_error",
2048
+ confidence: "heuristic",
2049
+ missingControl: true
2050
+ }
2051
+ )
2052
+ ]);
2053
+ }
2036
2054
  }
2037
2055
  async function checkSsl(domain) {
2038
2056
  return checkSSL(domain, fetch, { timeout: HTTPS_TIMEOUT_MS });
@@ -3205,7 +3223,31 @@ async function fetchBodyForWafDetection(url, timeoutMs) {
3205
3223
  return "";
3206
3224
  }
3207
3225
  }
3226
+ var TOTAL_BUDGET_MS = 1e4;
3208
3227
  async function checkHttpSecurity(domain) {
3228
+ let budgetTimeoutId;
3229
+ const budgetExceeded = new Promise((resolve) => {
3230
+ budgetTimeoutId = setTimeout(() => resolve("budget_exceeded"), TOTAL_BUDGET_MS);
3231
+ });
3232
+ try {
3233
+ const raced = await Promise.race([checkHttpSecurityInner(domain), budgetExceeded]);
3234
+ if (raced === "budget_exceeded") {
3235
+ const finding = createFinding(
3236
+ "http_security",
3237
+ "HTTP security check timed out",
3238
+ "high",
3239
+ `Could not complete HTTP security header analysis for ${domain} within ${TOTAL_BUDGET_MS}ms. Host was likely unreachable or extremely slow.`,
3240
+ { missingControl: true, confidence: "heuristic", errorKind: "timeout" }
3241
+ );
3242
+ const base = buildCheckResult("http_security", [finding]);
3243
+ return { ...base, score: 0, passed: false, checkStatus: "timeout" };
3244
+ }
3245
+ return raced;
3246
+ } finally {
3247
+ if (budgetTimeoutId !== void 0) clearTimeout(budgetTimeoutId);
3248
+ }
3249
+ }
3250
+ async function checkHttpSecurityInner(domain) {
3209
3251
  const dualResult = await dualFetchHeaders(domain, HTTPS_TIMEOUT_MS);
3210
3252
  if (dualResult) {
3211
3253
  const headersForWaf = dualResult.headers;