blackveil-dns 2.0.10 → 2.1.0
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 +18 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +96 -11
- package/dist/index.js.map +1 -1
- package/dist/stdio.js +2999 -872
- package/dist/stdio.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -24,6 +24,10 @@ Open-source DNS & email security scanner for Claude, Cursor, VS Code, and MCP cl
|
|
|
24
24
|
|
|
25
25
|
## Try it in 30 seconds
|
|
26
26
|
|
|
27
|
+
**Claude Desktop** (one-click install):
|
|
28
|
+
|
|
29
|
+
Download the [Blackveil DNS extension](https://github.com/MadaBurns/bv-claude-dns/releases/latest/download/bv-claude-dns.mcpb) and open it — all 33 tools available instantly.
|
|
30
|
+
|
|
27
31
|
**Claude Code** (one command):
|
|
28
32
|
|
|
29
33
|
```bash
|
|
@@ -361,6 +365,20 @@ Enforce DNS security grades in your pipeline with the [Blackveil DNS GitHub Acti
|
|
|
361
365
|
|
|
362
366
|
The action outputs `score`, `grade`, `maturity`, `scoring-profile`, and `passed` for downstream steps.
|
|
363
367
|
|
|
368
|
+
### Package Release Controls
|
|
369
|
+
|
|
370
|
+
`blackveil-dns` npm publishing is gated by `.github/workflows/publish.yml`.
|
|
371
|
+
|
|
372
|
+
On `v*` tags, the workflow enforces:
|
|
373
|
+
- `npm run validate:internal-deps`
|
|
374
|
+
- Typecheck (`npx tsc --noEmit`)
|
|
375
|
+
- Lint (`npm run lint`)
|
|
376
|
+
- Test (`npm test`)
|
|
377
|
+
- Security audit (`npm audit --audit-level=high`)
|
|
378
|
+
- Changelog presence for the target version
|
|
379
|
+
|
|
380
|
+
Publish only proceeds after all gates pass.
|
|
381
|
+
|
|
364
382
|
## Monitoring
|
|
365
383
|
|
|
366
384
|
Get weekly DNS security reports in Slack or Discord. See [`examples/slack-discord-webhook/`](examples/slack-discord-webhook/) for a ready-to-deploy Cloudflare Cron Trigger recipe.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CheckResult, ScoringConfig, ScanScore, DomainContext } from '@blackveil/dns-checks/scoring';
|
|
2
2
|
export { CATEGORY_DISPLAY_WEIGHTS, CheckCategory, CheckResult, DomainContext, DomainProfile, Finding, FindingConfidence, SEVERITY_PENALTIES, ScanScore, Severity, buildCheckResult, computeCategoryScore, computeScanScore, createFinding, detectDomainContext, getProfileWeights, inferFindingConfidence, scoreToGrade } from '@blackveil/dns-checks/scoring';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
export { parseDmarcTags } from '@blackveil/dns-checks';
|
|
4
5
|
|
|
5
6
|
/** Standard DNS record type codes */
|
|
@@ -52,7 +53,7 @@ interface DohResponse {
|
|
|
52
53
|
}
|
|
53
54
|
/** Configuration for a custom secondary DoH resolver (e.g., bv-dns on Oracle Cloud). */
|
|
54
55
|
interface SecondaryDohConfig {
|
|
55
|
-
/** DoH endpoint URL (e.g. https://
|
|
56
|
+
/** DoH endpoint URL (e.g. https://doh.example.com/dns-query) */
|
|
56
57
|
endpoint: string;
|
|
57
58
|
/** Optional auth token sent as X-BV-Token header */
|
|
58
59
|
token?: string;
|
|
@@ -159,7 +160,7 @@ declare function sanitizeDomain(input: string): string;
|
|
|
159
160
|
declare function sanitizeInput(input: string, maxLength?: number): string;
|
|
160
161
|
|
|
161
162
|
/** Server version — keep in sync with package.json */
|
|
162
|
-
declare const SERVER_VERSION = "2.0
|
|
163
|
+
declare const SERVER_VERSION = "2.1.0";
|
|
163
164
|
|
|
164
165
|
/**
|
|
165
166
|
* Check BIMI records for a domain.
|
|
@@ -246,7 +247,12 @@ declare function checkSubdomainTakeover(domain: string, dnsOptions?: QueryDnsOpt
|
|
|
246
247
|
*/
|
|
247
248
|
declare function checkTlsrpt(domain: string, dnsOptions?: QueryDnsOptions): Promise<CheckResult>;
|
|
248
249
|
|
|
249
|
-
|
|
250
|
+
/** Output format. Trims and lowercases input before validation. */
|
|
251
|
+
declare const FormatSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodEnum<{
|
|
252
|
+
full: "full";
|
|
253
|
+
compact: "compact";
|
|
254
|
+
}>>;
|
|
255
|
+
type OutputFormat = z.infer<typeof FormatSchema>;
|
|
250
256
|
|
|
251
257
|
interface ImpactNarrative {
|
|
252
258
|
impact?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import * as punycode from 'punycode/';
|
|
2
3
|
|
|
3
4
|
// src/lib/dns-types.ts
|
|
@@ -57,6 +58,40 @@ var DOH_EDGE_CACHE_TTL = 300;
|
|
|
57
58
|
var INFLIGHT_CLEANUP_MS = 3e4;
|
|
58
59
|
var DNS_RETRY_BASE_DELAY_MS = 75;
|
|
59
60
|
var DNS_CONFIRM_WITH_SECONDARY_ON_EMPTY = true;
|
|
61
|
+
var DnsAnswerSchema = z.object({
|
|
62
|
+
name: z.string(),
|
|
63
|
+
type: z.number(),
|
|
64
|
+
TTL: z.number().optional(),
|
|
65
|
+
data: z.string()
|
|
66
|
+
});
|
|
67
|
+
var DnsAuthoritySchema = z.object({
|
|
68
|
+
name: z.string(),
|
|
69
|
+
type: z.number(),
|
|
70
|
+
TTL: z.number().optional(),
|
|
71
|
+
data: z.string()
|
|
72
|
+
});
|
|
73
|
+
var DohResponseSchema = z.object({
|
|
74
|
+
Status: z.number().finite(),
|
|
75
|
+
TC: z.boolean().optional(),
|
|
76
|
+
RD: z.boolean().optional(),
|
|
77
|
+
RA: z.boolean().optional(),
|
|
78
|
+
AD: z.boolean().optional(),
|
|
79
|
+
CD: z.boolean().optional(),
|
|
80
|
+
Question: z.array(z.object({ name: z.string(), type: z.unknown() })).optional(),
|
|
81
|
+
Answer: z.array(DnsAnswerSchema).optional(),
|
|
82
|
+
Authority: z.array(DnsAuthoritySchema).optional()
|
|
83
|
+
}).passthrough();
|
|
84
|
+
var CaaRecordSchema = z.object({
|
|
85
|
+
flags: z.number(),
|
|
86
|
+
tag: z.string(),
|
|
87
|
+
value: z.string()
|
|
88
|
+
});
|
|
89
|
+
z.object({
|
|
90
|
+
usage: z.number(),
|
|
91
|
+
selector: z.number(),
|
|
92
|
+
matchingType: z.number(),
|
|
93
|
+
certData: z.string()
|
|
94
|
+
});
|
|
60
95
|
|
|
61
96
|
// src/lib/dns-transport.ts
|
|
62
97
|
var DOH_ENDPOINT = "https://cloudflare-dns.com/dns-query";
|
|
@@ -84,8 +119,9 @@ async function fetchDohResponse(url, timeoutMs) {
|
|
|
84
119
|
});
|
|
85
120
|
if (!response.ok) return null;
|
|
86
121
|
const data = await response.json();
|
|
87
|
-
|
|
88
|
-
return
|
|
122
|
+
const parsed = DohResponseSchema.safeParse(data);
|
|
123
|
+
if (!parsed.success) return null;
|
|
124
|
+
return parsed.data;
|
|
89
125
|
} catch {
|
|
90
126
|
return null;
|
|
91
127
|
} finally {
|
|
@@ -108,8 +144,9 @@ async function fetchDohWithAuth(url, timeoutMs, token) {
|
|
|
108
144
|
});
|
|
109
145
|
if (!response.ok) return null;
|
|
110
146
|
const data = await response.json();
|
|
111
|
-
|
|
112
|
-
return
|
|
147
|
+
const parsed = DohResponseSchema.safeParse(data);
|
|
148
|
+
if (!parsed.success) return null;
|
|
149
|
+
return parsed.data;
|
|
113
150
|
} catch {
|
|
114
151
|
return null;
|
|
115
152
|
} finally {
|
|
@@ -180,10 +217,11 @@ async function queryDnsUncached(domain, type, dnssecCheck = false, opts) {
|
|
|
180
217
|
throw new DnsQueryError(`DoH returned HTTP ${response.status}`, domain, type, response.status);
|
|
181
218
|
}
|
|
182
219
|
const raw = await response.json();
|
|
183
|
-
|
|
220
|
+
const validated = DohResponseSchema.safeParse(raw);
|
|
221
|
+
if (!validated.success) {
|
|
184
222
|
throw new DnsQueryError("Invalid DoH response format", domain, type);
|
|
185
223
|
}
|
|
186
|
-
const data =
|
|
224
|
+
const data = validated.data;
|
|
187
225
|
if (confirmWithSecondaryOnEmpty && !opts?.skipSecondaryConfirmation && !hasTypedAnswers(data, type)) {
|
|
188
226
|
if (opts?.secondaryDoh?.endpoint) {
|
|
189
227
|
const bvDns = await fetchDohWithAuth(
|
|
@@ -245,15 +283,17 @@ function parseCaaRecord(data) {
|
|
|
245
283
|
if (isNaN(flags) || isNaN(tagLen) || hexBytes.length < 2 + tagLen) return null;
|
|
246
284
|
const tag = hexBytes.slice(2, 2 + tagLen).map((hexByte) => String.fromCharCode(parseInt(hexByte, 16))).join("");
|
|
247
285
|
const value = hexBytes.slice(2 + tagLen).map((hexByte) => String.fromCharCode(parseInt(hexByte, 16))).join("");
|
|
248
|
-
|
|
286
|
+
const record = { flags, tag: tag.toLowerCase(), value };
|
|
287
|
+
return CaaRecordSchema.safeParse(record).success ? record : null;
|
|
249
288
|
}
|
|
250
289
|
const match = data.match(/^(\d+)\s+(\S+)\s+"?([^"]*)"?\s*$/);
|
|
251
290
|
if (match) {
|
|
252
|
-
|
|
291
|
+
const record = {
|
|
253
292
|
flags: parseInt(match[1], 10),
|
|
254
293
|
tag: match[2].toLowerCase(),
|
|
255
294
|
value: match[3]
|
|
256
295
|
};
|
|
296
|
+
return CaaRecordSchema.safeParse(record).success ? record : null;
|
|
257
297
|
}
|
|
258
298
|
return null;
|
|
259
299
|
}
|
|
@@ -1125,9 +1165,7 @@ function sanitizeInput(input, maxLength = 500) {
|
|
|
1125
1165
|
}
|
|
1126
1166
|
|
|
1127
1167
|
// src/lib/server-version.ts
|
|
1128
|
-
var SERVER_VERSION = "2.0
|
|
1129
|
-
|
|
1130
|
-
// packages/dns-checks/dist/index.js
|
|
1168
|
+
var SERVER_VERSION = "2.1.0";
|
|
1131
1169
|
var SEVERITY_PENALTIES2 = {
|
|
1132
1170
|
critical: 40,
|
|
1133
1171
|
high: 25,
|
|
@@ -4214,6 +4252,53 @@ async function checkHTTPSecurity(domain, fetchFn, options) {
|
|
|
4214
4252
|
}
|
|
4215
4253
|
return buildCheckResult2("http_security", findings);
|
|
4216
4254
|
}
|
|
4255
|
+
var CheckCategorySchema = z.enum([
|
|
4256
|
+
"spf",
|
|
4257
|
+
"dmarc",
|
|
4258
|
+
"dkim",
|
|
4259
|
+
"dnssec",
|
|
4260
|
+
"ssl",
|
|
4261
|
+
"mta_sts",
|
|
4262
|
+
"ns",
|
|
4263
|
+
"caa",
|
|
4264
|
+
"subdomain_takeover",
|
|
4265
|
+
"mx",
|
|
4266
|
+
"bimi",
|
|
4267
|
+
"tlsrpt",
|
|
4268
|
+
"lookalikes",
|
|
4269
|
+
"shadow_domains",
|
|
4270
|
+
"txt_hygiene",
|
|
4271
|
+
"http_security",
|
|
4272
|
+
"dane",
|
|
4273
|
+
"mx_reputation",
|
|
4274
|
+
"srv",
|
|
4275
|
+
"zone_hygiene",
|
|
4276
|
+
"dane_https",
|
|
4277
|
+
"svcb_https"
|
|
4278
|
+
]);
|
|
4279
|
+
var SeveritySchema = z.enum(["critical", "high", "medium", "low", "info"]);
|
|
4280
|
+
z.enum(["deterministic", "heuristic", "verified"]);
|
|
4281
|
+
z.enum(["core", "protective", "hardening"]);
|
|
4282
|
+
var FindingSchema = z.object({
|
|
4283
|
+
category: CheckCategorySchema,
|
|
4284
|
+
title: z.string(),
|
|
4285
|
+
severity: SeveritySchema,
|
|
4286
|
+
detail: z.string(),
|
|
4287
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
4288
|
+
});
|
|
4289
|
+
z.object({
|
|
4290
|
+
category: CheckCategorySchema,
|
|
4291
|
+
passed: z.boolean(),
|
|
4292
|
+
score: z.number(),
|
|
4293
|
+
findings: z.array(FindingSchema)
|
|
4294
|
+
});
|
|
4295
|
+
z.object({
|
|
4296
|
+
overall: z.number(),
|
|
4297
|
+
grade: z.string(),
|
|
4298
|
+
categoryScores: z.record(z.string(), z.number()),
|
|
4299
|
+
findings: z.array(FindingSchema),
|
|
4300
|
+
summary: z.string()
|
|
4301
|
+
});
|
|
4217
4302
|
|
|
4218
4303
|
// src/tools/check-bimi.ts
|
|
4219
4304
|
function makeQueryDNS(dnsOptions) {
|