@blamejs/core 0.9.28 → 0.9.38
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/CHANGELOG.md +885 -875
- package/index.js +18 -1
- package/lib/agent-snapshot.js +346 -0
- package/lib/agent-trace.js +218 -0
- package/lib/guard-all.js +1 -0
- package/lib/guard-dsn.js +379 -0
- package/lib/guard-envelope.js +294 -0
- package/lib/guard-smtp-command.js +484 -0
- package/lib/guard-snapshot-envelope.js +168 -0
- package/lib/guard-trace-context.js +172 -0
- package/lib/ip-utils.js +102 -0
- package/lib/mail-auth.js +4 -35
- package/lib/mail-greylist.js +448 -0
- package/lib/mail-helo.js +473 -0
- package/lib/mail-rbl.js +392 -0
- package/lib/mail.js +2 -1
- package/lib/network-dns-resolver.js +500 -0
- package/lib/network.js +1 -0
- package/lib/redis-client.js +2 -1
- package/lib/safe-dns.js +665 -0
- package/lib/tracing.js +36 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/safe-dns.js
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.safeDns
|
|
4
|
+
* @nav Parsers
|
|
5
|
+
* @title Safe DNS
|
|
6
|
+
* @order 130
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Bounded DNS-response parser. Substrate for v0.9.31
|
|
10
|
+
* `b.network.dns.resolver` and every consumer that walks raw DNS
|
|
11
|
+
* wire-format bytes (DKIM TXT lookup, MTA-STS verify, DANE TLSA,
|
|
12
|
+
* RBL queries, SVCB / HTTPS discovery, DNSBL via DNS).
|
|
13
|
+
*
|
|
14
|
+
* Caps every dimension an attacker can grow to DoS the resolver
|
|
15
|
+
* path:
|
|
16
|
+
*
|
|
17
|
+
* - **Response byte cap** (default 4 KiB; EDNS0 negotiated max
|
|
18
|
+
* 64 KiB — RFC 6891).
|
|
19
|
+
* - **Label count per name** (default 127 — RFC 1035 §2.3.4
|
|
20
|
+
* absolute cap is 255 octets which bounds to ~127 labels;
|
|
21
|
+
* most legitimate names stay well under 20).
|
|
22
|
+
* - **Compression-pointer chain depth** (default 16 — RFC 1035
|
|
23
|
+
* allows pointer-to-pointer; unbounded chains cause infinite
|
|
24
|
+
* loops without a depth cap. Common parser-bomb vector).
|
|
25
|
+
* - **CNAME chain depth** (default 8 — matches BIND9's operational
|
|
26
|
+
* cap on canonical-name translations; RFC 1912 §2.4 warns against
|
|
27
|
+
* long CNAME chains; we cap to defend RFC 9156 §3.1 amplification
|
|
28
|
+
* + redirection-loop classes).
|
|
29
|
+
* - **RR count per section** (default 64 answers, 32 authority,
|
|
30
|
+
* 32 additional — total response bounded above by the byte
|
|
31
|
+
* cap, but per-section caps short-circuit malicious sections).
|
|
32
|
+
* - **TXT rdata total length** (default 64 KiB — RFC 1035
|
|
33
|
+
* §3.3.14 allows up to 65535 octets per RR, but real-world
|
|
34
|
+
* SPF / DKIM / MTA-STS records never approach that; cap
|
|
35
|
+
* defends against amplification).
|
|
36
|
+
*
|
|
37
|
+
* Throws `SafeDnsError` on every cap exceeded, malformed name
|
|
38
|
+
* compression, truncated RR, oversize EDNS0 OPT pseudo-RR, RDLENGTH
|
|
39
|
+
* overflow past message end. The parser is purely functional — no
|
|
40
|
+
* I/O, no async — operators run it inline in the resolver path.
|
|
41
|
+
*
|
|
42
|
+
* Defends the DNS-amplification + parser-bomb classes generally —
|
|
43
|
+
* `CVE-2022-3204` (NRDelegationAttack — oversized authority + additional
|
|
44
|
+
* sections backing a malicious non-responsive delegation), `CVE-2023-50387`
|
|
45
|
+
* (KeyTrap — DNSKEY+RRSIG combinatorial DoS in validators, mitigated
|
|
46
|
+
* here by per-section RR caps that bound the input to validation),
|
|
47
|
+
* `CVE-2023-50868` (NSEC3-encloser companion), `CVE-2024-1737` (BIND9
|
|
48
|
+
* resource exhaustion via large RRsets per hostname). RFC 9156 §3
|
|
49
|
+
* amplification class.
|
|
50
|
+
*
|
|
51
|
+
* @card
|
|
52
|
+
* Bounded DNS-response parser. Substrate for the v0.9.31 validating
|
|
53
|
+
* resolver — caps response bytes, label count, compression-pointer
|
|
54
|
+
* chain depth, CNAME chain depth, per-section RR count, TXT rdata
|
|
55
|
+
* total length, EDNS0 OPT pseudo-RR size.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
var C = require("./constants");
|
|
59
|
+
var { defineClass } = require("./framework-error");
|
|
60
|
+
|
|
61
|
+
var SafeDnsError = defineClass("SafeDnsError", { alwaysPermanent: true });
|
|
62
|
+
|
|
63
|
+
// allow:raw-byte-literal — RFC 1035 §3.1 single-label cap (octet 0 high
|
|
64
|
+
// 2 bits reserved for compression pointer; label-length field is 6 bits).
|
|
65
|
+
var DNS_MAX_LABEL_BYTES = 63;
|
|
66
|
+
|
|
67
|
+
// allow:raw-byte-literal — RFC 1035 §3.1 wire-format name absolute cap
|
|
68
|
+
// (sum of all label-length bytes + label bytes + terminator).
|
|
69
|
+
var DNS_MAX_NAME_BYTES = 255;
|
|
70
|
+
|
|
71
|
+
// allow:raw-byte-literal — RFC 1035 §4.2.1 fixed header size.
|
|
72
|
+
var DNS_HEADER_BYTES = 12;
|
|
73
|
+
|
|
74
|
+
// allow:raw-byte-literal — RFC 1035 §3.2.1 RR fixed prefix
|
|
75
|
+
// (TYPE 2 + CLASS 2 + TTL 4 + RDLENGTH 2 = 10 octets after NAME).
|
|
76
|
+
var DNS_RR_FIXED_BYTES = 10;
|
|
77
|
+
|
|
78
|
+
// allow:raw-byte-literal — RFC 6891 §6.1 OPT pseudo-RR upper bound for
|
|
79
|
+
// EDNS0 payload size we'll accept. 64 KiB is the protocol absolute
|
|
80
|
+
// max; resolver-side default is much smaller.
|
|
81
|
+
var EDNS0_HARD_MAX = 65535;
|
|
82
|
+
|
|
83
|
+
// allow:raw-byte-literal — RFC 1035 §3.2.2 record-type codes we route
|
|
84
|
+
// through type-specific decoders. Anything not listed parses as raw
|
|
85
|
+
// rdata bytes (operator inspects the RDLENGTH-bounded slice).
|
|
86
|
+
var RTYPE_A = 1;
|
|
87
|
+
var RTYPE_NS = 2;
|
|
88
|
+
var RTYPE_CNAME = 5;
|
|
89
|
+
var RTYPE_SOA = 6;
|
|
90
|
+
var RTYPE_PTR = 12;
|
|
91
|
+
var RTYPE_MX = 15;
|
|
92
|
+
var RTYPE_TXT = 16; // allow:raw-byte-literal — RFC 1035 §3.2.2 TXT record type code
|
|
93
|
+
var RTYPE_AAAA = 28;
|
|
94
|
+
var RTYPE_SRV = 33;
|
|
95
|
+
var RTYPE_OPT = 41;
|
|
96
|
+
var RTYPE_DS = 43;
|
|
97
|
+
var RTYPE_RRSIG = 46;
|
|
98
|
+
var RTYPE_DNSKEY = 48; // allow:raw-byte-literal — RFC 4034 DNSKEY record type code
|
|
99
|
+
var RTYPE_TLSA = 52;
|
|
100
|
+
|
|
101
|
+
var RTYPE_NAMES = Object.freeze({
|
|
102
|
+
1: "A", 2: "NS", 5: "CNAME", 6: "SOA", 12: "PTR", 15: "MX",
|
|
103
|
+
16: "TXT", 28: "AAAA", 33: "SRV", 41: "OPT", 43: "DS", // allow:raw-byte-literal — IANA DNS record type codes
|
|
104
|
+
46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 50: "NSEC3", 52: "TLSA", // allow:raw-byte-literal — IANA DNS record type codes
|
|
105
|
+
64: "SVCB", 65: "HTTPS", // allow:raw-byte-literal — IANA DNS record type codes
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
var DEFAULT_MAX_RESPONSE_BYTES = C.BYTES.kib(4);
|
|
109
|
+
var DEFAULT_MAX_EDNS0_BYTES = C.BYTES.kib(4);
|
|
110
|
+
var DEFAULT_MAX_LABELS = 127; // allow:raw-byte-literal — RFC 1035 §2.3.4 label count cap (count, not bytes)
|
|
111
|
+
var DEFAULT_MAX_POINTER_DEPTH = 16; // allow:raw-byte-literal — compression-pointer chain depth (count, not bytes)
|
|
112
|
+
var DEFAULT_MAX_CNAME_DEPTH = 8;
|
|
113
|
+
var DEFAULT_MAX_ANSWER_RRS = 64; // allow:raw-byte-literal — RR count cap (count, not bytes)
|
|
114
|
+
var DEFAULT_MAX_AUTHORITY_RRS = 32; // allow:raw-byte-literal — RR count cap (count, not bytes)
|
|
115
|
+
var DEFAULT_MAX_ADDITIONAL_RRS = 32; // allow:raw-byte-literal — RR count cap (count, not bytes)
|
|
116
|
+
var DEFAULT_MAX_TXT_RDATA = C.BYTES.kib(64);
|
|
117
|
+
|
|
118
|
+
var DEFAULT_PROFILE = "strict";
|
|
119
|
+
|
|
120
|
+
var PROFILES = Object.freeze({
|
|
121
|
+
strict: {
|
|
122
|
+
maxResponseBytes: DEFAULT_MAX_RESPONSE_BYTES,
|
|
123
|
+
maxEdns0Bytes: DEFAULT_MAX_EDNS0_BYTES,
|
|
124
|
+
maxLabels: DEFAULT_MAX_LABELS,
|
|
125
|
+
maxPointerDepth: DEFAULT_MAX_POINTER_DEPTH,
|
|
126
|
+
maxCnameDepth: DEFAULT_MAX_CNAME_DEPTH,
|
|
127
|
+
maxAnswerRrs: DEFAULT_MAX_ANSWER_RRS,
|
|
128
|
+
maxAuthorityRrs: DEFAULT_MAX_AUTHORITY_RRS,
|
|
129
|
+
maxAdditionalRrs: DEFAULT_MAX_ADDITIONAL_RRS,
|
|
130
|
+
maxTxtRdata: DEFAULT_MAX_TXT_RDATA,
|
|
131
|
+
},
|
|
132
|
+
balanced: {
|
|
133
|
+
maxResponseBytes: C.BYTES.kib(16),
|
|
134
|
+
maxEdns0Bytes: C.BYTES.kib(16),
|
|
135
|
+
maxLabels: DEFAULT_MAX_LABELS,
|
|
136
|
+
maxPointerDepth: DEFAULT_MAX_POINTER_DEPTH,
|
|
137
|
+
maxCnameDepth: 16, // allow:raw-byte-literal — RR count, not bytes
|
|
138
|
+
maxAnswerRrs: 128, // allow:raw-byte-literal — RR count
|
|
139
|
+
maxAuthorityRrs: 64, // allow:raw-byte-literal — RR count
|
|
140
|
+
maxAdditionalRrs: 64, // allow:raw-byte-literal — RR count
|
|
141
|
+
maxTxtRdata: C.BYTES.kib(128),
|
|
142
|
+
},
|
|
143
|
+
permissive: {
|
|
144
|
+
maxResponseBytes: C.BYTES.kib(64),
|
|
145
|
+
maxEdns0Bytes: C.BYTES.kib(64),
|
|
146
|
+
maxLabels: DEFAULT_MAX_LABELS,
|
|
147
|
+
maxPointerDepth: 32, // allow:raw-byte-literal — pointer chain count
|
|
148
|
+
maxCnameDepth: 32, // allow:raw-byte-literal — chain count
|
|
149
|
+
maxAnswerRrs: 256, // allow:raw-byte-literal — RR count
|
|
150
|
+
maxAuthorityRrs: 128, // allow:raw-byte-literal — RR count
|
|
151
|
+
maxAdditionalRrs: 128, // allow:raw-byte-literal — RR count
|
|
152
|
+
maxTxtRdata: C.BYTES.kib(512),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
157
|
+
hipaa: "strict",
|
|
158
|
+
"pci-dss": "strict",
|
|
159
|
+
gdpr: "strict",
|
|
160
|
+
soc2: "strict",
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @primitive b.safeDns.parseResponse
|
|
165
|
+
* @signature b.safeDns.parseResponse(buf, opts?)
|
|
166
|
+
* @since 0.9.31
|
|
167
|
+
* @status stable
|
|
168
|
+
* @related b.safeDns.boundEdns0, b.safeDns.checkCnameChainDepth
|
|
169
|
+
*
|
|
170
|
+
* Parse a DNS wire-format response into a structured shape. Returns
|
|
171
|
+
* `{ id, rcode, flags, question, answer, authority, additional,
|
|
172
|
+
* edns0 }`. Each RR carries `{ name, type, typeName, class, ttl,
|
|
173
|
+
* rdata, decoded }` — `rdata` is the rdlength-bounded byte slice,
|
|
174
|
+
* `decoded` is the type-specific parse where the parser knows the
|
|
175
|
+
* type (A / AAAA / CNAME / NS / PTR / MX / TXT / SOA / SRV / DS /
|
|
176
|
+
* DNSKEY / TLSA / RRSIG / NSEC / NSEC3 / SVCB / HTTPS), otherwise
|
|
177
|
+
* `null`.
|
|
178
|
+
*
|
|
179
|
+
* Throws `SafeDnsError` with codes:
|
|
180
|
+
* `safe-dns/bad-input` / `oversize-response` / `truncated-header`
|
|
181
|
+
* / `truncated-rr` / `truncated-name` / `oversize-label` /
|
|
182
|
+
* `oversize-name` / `oversize-pointer-depth` / `oversize-labels` /
|
|
183
|
+
* `oversize-answer-rrs` / `oversize-authority-rrs` /
|
|
184
|
+
* `oversize-additional-rrs` / `oversize-txt-rdata` /
|
|
185
|
+
* `oversize-edns0` / `malformed-rdlength` / `bad-profile`.
|
|
186
|
+
*
|
|
187
|
+
* @opts
|
|
188
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
189
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* var parsed = b.safeDns.parseResponse(wireBytes);
|
|
193
|
+
* parsed.answer.forEach(function (rr) {
|
|
194
|
+
* if (rr.typeName === "TXT") console.log(rr.decoded.join(""));
|
|
195
|
+
* });
|
|
196
|
+
*/
|
|
197
|
+
function parseResponse(buf, opts) {
|
|
198
|
+
opts = opts || {};
|
|
199
|
+
if (!Buffer.isBuffer(buf)) {
|
|
200
|
+
throw new SafeDnsError("safe-dns/bad-input",
|
|
201
|
+
"safeDns.parseResponse: buf must be a Buffer; got " + (typeof buf));
|
|
202
|
+
}
|
|
203
|
+
var caps = _resolveProfile(opts);
|
|
204
|
+
if (buf.length > caps.maxResponseBytes) {
|
|
205
|
+
throw new SafeDnsError("safe-dns/oversize-response",
|
|
206
|
+
"safeDns.parseResponse: " + buf.length + " bytes exceeds maxResponseBytes=" +
|
|
207
|
+
caps.maxResponseBytes + " (RFC 6891 §6.1 EDNS0 advertised buffer size)");
|
|
208
|
+
}
|
|
209
|
+
if (buf.length < DNS_HEADER_BYTES) {
|
|
210
|
+
throw new SafeDnsError("safe-dns/truncated-header",
|
|
211
|
+
"safeDns.parseResponse: response truncated below header size (" +
|
|
212
|
+
buf.length + " < " + DNS_HEADER_BYTES + ")");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
var id = buf.readUInt16BE(0);
|
|
216
|
+
var flags = buf.readUInt16BE(2);
|
|
217
|
+
var qdcount = buf.readUInt16BE(4);
|
|
218
|
+
var ancount = buf.readUInt16BE(6);
|
|
219
|
+
var nscount = buf.readUInt16BE(8);
|
|
220
|
+
var arcount = buf.readUInt16BE(10);
|
|
221
|
+
|
|
222
|
+
if (ancount > caps.maxAnswerRrs) {
|
|
223
|
+
throw new SafeDnsError("safe-dns/oversize-answer-rrs",
|
|
224
|
+
"safeDns.parseResponse: ancount=" + ancount + " exceeds maxAnswerRrs=" +
|
|
225
|
+
caps.maxAnswerRrs + " (RFC 9156 amplification defense)");
|
|
226
|
+
}
|
|
227
|
+
if (nscount > caps.maxAuthorityRrs) {
|
|
228
|
+
throw new SafeDnsError("safe-dns/oversize-authority-rrs",
|
|
229
|
+
"safeDns.parseResponse: nscount=" + nscount + " exceeds maxAuthorityRrs=" +
|
|
230
|
+
caps.maxAuthorityRrs);
|
|
231
|
+
}
|
|
232
|
+
if (arcount > caps.maxAdditionalRrs) {
|
|
233
|
+
throw new SafeDnsError("safe-dns/oversize-additional-rrs",
|
|
234
|
+
"safeDns.parseResponse: arcount=" + arcount + " exceeds maxAdditionalRrs=" +
|
|
235
|
+
caps.maxAdditionalRrs);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var state = { off: DNS_HEADER_BYTES, buf: buf, caps: caps };
|
|
239
|
+
var question = [];
|
|
240
|
+
for (var q = 0; q < qdcount; q += 1) {
|
|
241
|
+
var qname = _readName(state, 0);
|
|
242
|
+
if (state.off + 4 > buf.length) { // allow:raw-byte-literal — RFC 1035 question fixed tail (QTYPE 2 + QCLASS 2)
|
|
243
|
+
throw new SafeDnsError("safe-dns/truncated-rr",
|
|
244
|
+
"safeDns.parseResponse: question RR truncated mid-fixed-tail");
|
|
245
|
+
}
|
|
246
|
+
var qtype = buf.readUInt16BE(state.off);
|
|
247
|
+
var qclass = buf.readUInt16BE(state.off + 2);
|
|
248
|
+
state.off += 4; // allow:raw-byte-literal — RFC 1035 QTYPE 2 + QCLASS 2 advance
|
|
249
|
+
question.push({
|
|
250
|
+
name: qname,
|
|
251
|
+
type: qtype,
|
|
252
|
+
typeName: RTYPE_NAMES[qtype] || ("TYPE" + qtype),
|
|
253
|
+
class: qclass,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
var answer = [];
|
|
258
|
+
var authority = [];
|
|
259
|
+
var additional = [];
|
|
260
|
+
var edns0 = null;
|
|
261
|
+
|
|
262
|
+
for (var a = 0; a < ancount; a += 1) answer.push(_readRr(state));
|
|
263
|
+
for (var n = 0; n < nscount; n += 1) authority.push(_readRr(state));
|
|
264
|
+
for (var ad = 0; ad < arcount; ad += 1) {
|
|
265
|
+
var rr = _readRr(state);
|
|
266
|
+
if (rr.type === RTYPE_OPT) {
|
|
267
|
+
edns0 = _decodeOpt(rr, caps);
|
|
268
|
+
} else {
|
|
269
|
+
additional.push(rr);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
id: id,
|
|
275
|
+
rcode: flags & 0x0f, // allow:raw-byte-literal — RFC 1035 §4.1.1 RCODE mask
|
|
276
|
+
flags: flags,
|
|
277
|
+
question: question,
|
|
278
|
+
answer: answer,
|
|
279
|
+
authority: authority,
|
|
280
|
+
additional: additional,
|
|
281
|
+
edns0: edns0,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @primitive b.safeDns.boundEdns0
|
|
287
|
+
* @signature b.safeDns.boundEdns0(advertised, opts?)
|
|
288
|
+
* @since 0.9.31
|
|
289
|
+
* @status stable
|
|
290
|
+
*
|
|
291
|
+
* Clamp an operator-supplied EDNS0 advertised buffer size to the
|
|
292
|
+
* profile cap. Resolver code calls this when constructing a query's
|
|
293
|
+
* OPT pseudo-RR so a misconfigured operator can't advertise a buffer
|
|
294
|
+
* larger than the profile permits.
|
|
295
|
+
*
|
|
296
|
+
* @opts
|
|
297
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
298
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* var udpMax = b.safeDns.boundEdns0(operatorConfig.ednsBuffer);
|
|
302
|
+
*/
|
|
303
|
+
function boundEdns0(advertised, opts) {
|
|
304
|
+
opts = opts || {};
|
|
305
|
+
var caps = _resolveProfile(opts);
|
|
306
|
+
if (typeof advertised !== "number" || !isFinite(advertised) || advertised < 0) {
|
|
307
|
+
throw new SafeDnsError("safe-dns/bad-input",
|
|
308
|
+
"safeDns.boundEdns0: advertised must be a non-negative finite number");
|
|
309
|
+
}
|
|
310
|
+
if (advertised > EDNS0_HARD_MAX) {
|
|
311
|
+
throw new SafeDnsError("safe-dns/oversize-edns0",
|
|
312
|
+
"safeDns.boundEdns0: advertised=" + advertised + " exceeds protocol max=" + EDNS0_HARD_MAX);
|
|
313
|
+
}
|
|
314
|
+
return Math.min(advertised, caps.maxEdns0Bytes);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @primitive b.safeDns.checkCnameChainDepth
|
|
319
|
+
* @signature b.safeDns.checkCnameChainDepth(depth, opts?)
|
|
320
|
+
* @since 0.9.31
|
|
321
|
+
* @status stable
|
|
322
|
+
*
|
|
323
|
+
* Throw if a CNAME-following loop has exceeded the profile's chain
|
|
324
|
+
* depth cap. Called by the resolver as it walks CNAME redirections
|
|
325
|
+
* across follow-up queries (each new query bumps the counter).
|
|
326
|
+
*
|
|
327
|
+
* @opts
|
|
328
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
329
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* for (var i = 0; i < 100; i += 1) {
|
|
333
|
+
* b.safeDns.checkCnameChainDepth(i);
|
|
334
|
+
* // ...follow the CNAME if there is one, else break...
|
|
335
|
+
* break;
|
|
336
|
+
* }
|
|
337
|
+
*/
|
|
338
|
+
function checkCnameChainDepth(depth, opts) {
|
|
339
|
+
opts = opts || {};
|
|
340
|
+
var caps = _resolveProfile(opts);
|
|
341
|
+
if (typeof depth !== "number" || !isFinite(depth) || depth < 0) {
|
|
342
|
+
throw new SafeDnsError("safe-dns/bad-input",
|
|
343
|
+
"safeDns.checkCnameChainDepth: depth must be a non-negative finite number");
|
|
344
|
+
}
|
|
345
|
+
if (depth > caps.maxCnameDepth) {
|
|
346
|
+
throw new SafeDnsError("safe-dns/oversize-cname-depth",
|
|
347
|
+
"safeDns.checkCnameChainDepth: depth=" + depth + " exceeds maxCnameDepth=" +
|
|
348
|
+
caps.maxCnameDepth + " (RFC 1912 §2.4 chain-loop defense; matches BIND9's cap of 8 canonical-name translations)");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @primitive b.safeDns.compliancePosture
|
|
354
|
+
* @signature b.safeDns.compliancePosture(posture)
|
|
355
|
+
* @since 0.9.31
|
|
356
|
+
* @status stable
|
|
357
|
+
*
|
|
358
|
+
* Return the effective profile name for a compliance posture, or
|
|
359
|
+
* `null` for unknown posture names (operator typo surfaces here).
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* b.safeDns.compliancePosture("hipaa"); // → "strict"
|
|
363
|
+
*/
|
|
364
|
+
function compliancePosture(posture) {
|
|
365
|
+
return COMPLIANCE_POSTURES[posture] || null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function _readName(state, pointerDepth) {
|
|
369
|
+
if (pointerDepth > state.caps.maxPointerDepth) {
|
|
370
|
+
throw new SafeDnsError("safe-dns/oversize-pointer-depth",
|
|
371
|
+
"safeDns.readName: compression-pointer chain depth=" + pointerDepth +
|
|
372
|
+
" exceeds maxPointerDepth=" + state.caps.maxPointerDepth + " (RFC 1035 §4.1.4 loop defense)");
|
|
373
|
+
}
|
|
374
|
+
var labels = [];
|
|
375
|
+
var totalBytes = 0;
|
|
376
|
+
var jumped = false;
|
|
377
|
+
var afterPointerOff = -1;
|
|
378
|
+
var off = state.off;
|
|
379
|
+
while (true) {
|
|
380
|
+
if (off >= state.buf.length) {
|
|
381
|
+
throw new SafeDnsError("safe-dns/truncated-name",
|
|
382
|
+
"safeDns.readName: name walk past end of message");
|
|
383
|
+
}
|
|
384
|
+
var byte = state.buf[off];
|
|
385
|
+
if (byte === 0) {
|
|
386
|
+
off += 1;
|
|
387
|
+
totalBytes += 1;
|
|
388
|
+
if (totalBytes > DNS_MAX_NAME_BYTES) {
|
|
389
|
+
throw new SafeDnsError("safe-dns/oversize-name",
|
|
390
|
+
"safeDns.readName: wire-name=" + totalBytes + " bytes exceeds RFC 1035 cap=" +
|
|
391
|
+
DNS_MAX_NAME_BYTES);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
if ((byte & 0xc0) === 0xc0) { // allow:raw-byte-literal — RFC 1035 §4.1.4 compression pointer mask
|
|
396
|
+
if (off + 1 >= state.buf.length) {
|
|
397
|
+
throw new SafeDnsError("safe-dns/truncated-name",
|
|
398
|
+
"safeDns.readName: compression pointer truncated");
|
|
399
|
+
}
|
|
400
|
+
var ptrOff = ((byte & 0x3f) << 8) | state.buf[off + 1]; // allow:raw-byte-literal — RFC 1035 §4.1.4 14-bit pointer offset
|
|
401
|
+
if (ptrOff >= state.buf.length) {
|
|
402
|
+
throw new SafeDnsError("safe-dns/truncated-name",
|
|
403
|
+
"safeDns.readName: compression pointer offset past message end");
|
|
404
|
+
}
|
|
405
|
+
// First compression pointer ends the in-line label walk
|
|
406
|
+
// (line break below). `jumped` can never already be true here;
|
|
407
|
+
// assign unconditionally per Codex code-quality review.
|
|
408
|
+
afterPointerOff = off + 2; // allow:raw-byte-literal — RFC 1035 §4.1.4 2-byte pointer width
|
|
409
|
+
jumped = true;
|
|
410
|
+
var subState = { off: ptrOff, buf: state.buf, caps: state.caps };
|
|
411
|
+
var tailName = _readName(subState, pointerDepth + 1);
|
|
412
|
+
if (tailName.length) labels.push(tailName);
|
|
413
|
+
totalBytes += 2; // allow:raw-byte-literal — RFC 1035 §4.1.4 2-byte pointer width
|
|
414
|
+
if (totalBytes > DNS_MAX_NAME_BYTES) {
|
|
415
|
+
throw new SafeDnsError("safe-dns/oversize-name",
|
|
416
|
+
"safeDns.readName: composite name=" + totalBytes + " bytes exceeds RFC 1035 cap=" +
|
|
417
|
+
DNS_MAX_NAME_BYTES);
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
if (byte > DNS_MAX_LABEL_BYTES) {
|
|
422
|
+
throw new SafeDnsError("safe-dns/oversize-label",
|
|
423
|
+
"safeDns.readName: label length=" + byte + " exceeds RFC 1035 cap=" + DNS_MAX_LABEL_BYTES);
|
|
424
|
+
}
|
|
425
|
+
if (off + 1 + byte > state.buf.length) {
|
|
426
|
+
throw new SafeDnsError("safe-dns/truncated-name",
|
|
427
|
+
"safeDns.readName: label content past message end");
|
|
428
|
+
}
|
|
429
|
+
labels.push(state.buf.toString("ascii", off + 1, off + 1 + byte));
|
|
430
|
+
if (labels.length > state.caps.maxLabels) {
|
|
431
|
+
throw new SafeDnsError("safe-dns/oversize-labels",
|
|
432
|
+
"safeDns.readName: label count=" + labels.length + " exceeds maxLabels=" + state.caps.maxLabels);
|
|
433
|
+
}
|
|
434
|
+
off += 1 + byte;
|
|
435
|
+
totalBytes += 1 + byte;
|
|
436
|
+
if (totalBytes > DNS_MAX_NAME_BYTES) {
|
|
437
|
+
throw new SafeDnsError("safe-dns/oversize-name",
|
|
438
|
+
"safeDns.readName: wire-name=" + totalBytes + " bytes exceeds RFC 1035 cap=" +
|
|
439
|
+
DNS_MAX_NAME_BYTES);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
state.off = jumped ? afterPointerOff : off;
|
|
443
|
+
return labels.join(".");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function _readRr(state) {
|
|
447
|
+
var name = _readName(state, 0);
|
|
448
|
+
if (state.off + DNS_RR_FIXED_BYTES > state.buf.length) {
|
|
449
|
+
throw new SafeDnsError("safe-dns/truncated-rr",
|
|
450
|
+
"safeDns.readRr: RR truncated mid-fixed-prefix");
|
|
451
|
+
}
|
|
452
|
+
var rtype = state.buf.readUInt16BE(state.off);
|
|
453
|
+
var rclass = state.buf.readUInt16BE(state.off + 2); // allow:raw-byte-literal — RFC 1035 §3.2.1 CLASS offset
|
|
454
|
+
var ttl = state.buf.readUInt32BE(state.off + 4); // allow:raw-byte-literal — RFC 1035 §3.2.1 TTL offset
|
|
455
|
+
var rdlen = state.buf.readUInt16BE(state.off + 8); // allow:raw-byte-literal — RFC 1035 §3.2.1 RDLENGTH offset
|
|
456
|
+
state.off += DNS_RR_FIXED_BYTES;
|
|
457
|
+
if (state.off + rdlen > state.buf.length) {
|
|
458
|
+
throw new SafeDnsError("safe-dns/malformed-rdlength",
|
|
459
|
+
"safeDns.readRr: RDLENGTH=" + rdlen + " runs past message end (off=" + state.off +
|
|
460
|
+
" len=" + state.buf.length + ")");
|
|
461
|
+
}
|
|
462
|
+
var rdataStart = state.off;
|
|
463
|
+
var rdata = state.buf.slice(rdataStart, rdataStart + rdlen);
|
|
464
|
+
state.off += rdlen;
|
|
465
|
+
|
|
466
|
+
var decoded = null;
|
|
467
|
+
if (rtype === RTYPE_A && rdlen === 4) { // allow:raw-byte-literal — RFC 1035 §3.4.1 A record is 4 octets
|
|
468
|
+
decoded = rdata[0] + "." + rdata[1] + "." + rdata[2] + "." + rdata[3]; // allow:raw-byte-literal — dotted-quad indices into 4-octet A rdata
|
|
469
|
+
} else if (rtype === RTYPE_AAAA && rdlen === 16) { // allow:raw-byte-literal — RFC 3596 §2.2 AAAA record is 16 octets
|
|
470
|
+
decoded = _formatIpv6(rdata);
|
|
471
|
+
} else if (rtype === RTYPE_CNAME || rtype === RTYPE_NS || rtype === RTYPE_PTR) {
|
|
472
|
+
var subState = { off: rdataStart, buf: state.buf, caps: state.caps };
|
|
473
|
+
decoded = _readName(subState, 0);
|
|
474
|
+
} else if (rtype === RTYPE_MX && rdlen >= 3) { // allow:raw-byte-literal — RFC 1035 §3.3.9 MX preference 2 + min exchange 1
|
|
475
|
+
var pref = rdata.readUInt16BE(0);
|
|
476
|
+
var mxState = { off: rdataStart + 2, buf: state.buf, caps: state.caps }; // allow:raw-byte-literal — MX preference field width
|
|
477
|
+
var exchange = _readName(mxState, 0);
|
|
478
|
+
decoded = { preference: pref, exchange: exchange };
|
|
479
|
+
} else if (rtype === RTYPE_TXT) {
|
|
480
|
+
decoded = _decodeTxt(rdata, rdlen, state.caps);
|
|
481
|
+
} else if (rtype === RTYPE_SOA) {
|
|
482
|
+
decoded = _decodeSoa(state.buf, rdataStart, rdlen, state.caps);
|
|
483
|
+
} else if (rtype === RTYPE_SRV && rdlen >= 7) { // allow:raw-byte-literal — RFC 2782 SRV fixed prefix 6 + min target 1
|
|
484
|
+
var srvState = { off: rdataStart + 6, buf: state.buf, caps: state.caps }; // allow:raw-byte-literal — RFC 2782 priority 2 + weight 2 + port 2
|
|
485
|
+
var target = _readName(srvState, 0);
|
|
486
|
+
decoded = {
|
|
487
|
+
priority: rdata.readUInt16BE(0),
|
|
488
|
+
weight: rdata.readUInt16BE(2), // allow:raw-byte-literal — RFC 2782 weight offset
|
|
489
|
+
port: rdata.readUInt16BE(4), // allow:raw-byte-literal — RFC 2782 port offset
|
|
490
|
+
target: target,
|
|
491
|
+
};
|
|
492
|
+
} else if (rtype === RTYPE_DS && rdlen >= 4) { // allow:raw-byte-literal — RFC 4034 §5.1 DS fixed prefix 4 + digest
|
|
493
|
+
decoded = {
|
|
494
|
+
keyTag: rdata.readUInt16BE(0),
|
|
495
|
+
algorithm: rdata.readUInt8(2),
|
|
496
|
+
digestType: rdata.readUInt8(3),
|
|
497
|
+
digest: rdata.slice(4), // allow:raw-byte-literal — RFC 4034 §5.1 digest start
|
|
498
|
+
};
|
|
499
|
+
} else if (rtype === RTYPE_DNSKEY && rdlen >= 4) { // allow:raw-byte-literal — RFC 4034 §2.1 DNSKEY fixed prefix 4 + pubkey
|
|
500
|
+
decoded = {
|
|
501
|
+
flags: rdata.readUInt16BE(0),
|
|
502
|
+
protocol: rdata.readUInt8(2),
|
|
503
|
+
algorithm: rdata.readUInt8(3),
|
|
504
|
+
publicKey: rdata.slice(4), // allow:raw-byte-literal — RFC 4034 §2.1 publicKey start
|
|
505
|
+
};
|
|
506
|
+
} else if (rtype === RTYPE_RRSIG && rdlen >= 18) { // allow:raw-byte-literal — RFC 4034 §3.1 RRSIG fixed prefix 18 + signer + signature
|
|
507
|
+
var rrsigState = { off: rdataStart + 18, buf: state.buf, caps: state.caps }; // allow:raw-byte-literal — RFC 4034 §3.1 fixed prefix width
|
|
508
|
+
var signer = _readName(rrsigState, 0);
|
|
509
|
+
decoded = {
|
|
510
|
+
typeCovered: rdata.readUInt16BE(0),
|
|
511
|
+
algorithm: rdata.readUInt8(2),
|
|
512
|
+
labels: rdata.readUInt8(3),
|
|
513
|
+
originalTtl: rdata.readUInt32BE(4), // allow:raw-byte-literal — RFC 4034 §3.1 originalTtl offset
|
|
514
|
+
sigExpiry: rdata.readUInt32BE(8), // allow:raw-byte-literal — RFC 4034 §3.1 expiry offset
|
|
515
|
+
sigInception: rdata.readUInt32BE(12), // allow:raw-byte-literal — RFC 4034 §3.1 inception offset
|
|
516
|
+
keyTag: rdata.readUInt16BE(16), // allow:raw-byte-literal — RFC 4034 §3.1 keyTag offset
|
|
517
|
+
signerName: signer,
|
|
518
|
+
signature: state.buf.slice(rrsigState.off, rdataStart + rdlen),
|
|
519
|
+
};
|
|
520
|
+
} else if (rtype === RTYPE_TLSA && rdlen >= 3) { // allow:raw-byte-literal — RFC 6698 §2.1 TLSA fixed prefix 3 + certData
|
|
521
|
+
decoded = {
|
|
522
|
+
usage: rdata.readUInt8(0),
|
|
523
|
+
selector: rdata.readUInt8(1),
|
|
524
|
+
matchingType: rdata.readUInt8(2),
|
|
525
|
+
certData: rdata.slice(3), // allow:raw-byte-literal — RFC 6698 §2.1 certData start
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
name: name,
|
|
531
|
+
type: rtype,
|
|
532
|
+
typeName: RTYPE_NAMES[rtype] || ("TYPE" + rtype),
|
|
533
|
+
class: rclass,
|
|
534
|
+
ttl: ttl,
|
|
535
|
+
rdata: rdata,
|
|
536
|
+
decoded: decoded,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Format a 16-byte AAAA rdata buffer as a canonical IPv6 string per
|
|
541
|
+
// RFC 5952 §4: lowercase hex; suppress leading zeros in each group;
|
|
542
|
+
// compress the longest run of all-zero groups (>= 2) with "::"; on
|
|
543
|
+
// ties prefer the leftmost run; if the address is IPv4-mapped
|
|
544
|
+
// (::ffff:0:0/96) emit the trailing 32 bits as dotted-quad per
|
|
545
|
+
// RFC 5952 §5.
|
|
546
|
+
function _formatIpv6(rdata) {
|
|
547
|
+
var groups = new Array(8); // allow:raw-byte-literal — RFC 4291 §2.2 8 IPv6 groups
|
|
548
|
+
for (var g = 0; g < 8; g += 1) groups[g] = rdata.readUInt16BE(g * 2); // allow:raw-byte-literal — RFC 4291 §2.2 group byte stride
|
|
549
|
+
|
|
550
|
+
// RFC 5952 §5 — IPv4-mapped: first 80 bits zero, next 16 bits 0xFFFF.
|
|
551
|
+
var isV4Mapped = true;
|
|
552
|
+
for (var z = 0; z < 5; z += 1) if (groups[z] !== 0) { isV4Mapped = false; break; } // allow:raw-byte-literal — RFC 5952 §5 v4-mapped zero-prefix groups
|
|
553
|
+
if (isV4Mapped && groups[5] !== 0xffff) isV4Mapped = false; // allow:raw-byte-literal — RFC 5952 §5 v4-mapped marker group
|
|
554
|
+
if (isV4Mapped) {
|
|
555
|
+
var dotted = rdata[12] + "." + rdata[13] + "." + rdata[14] + "." + rdata[15]; // allow:raw-byte-literal — RFC 5952 §5 trailing v4 octets
|
|
556
|
+
return "::ffff:" + dotted;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Find the longest run of zeros (length >= 2 to use "::" per RFC 5952 §4.2.2).
|
|
560
|
+
var bestStart = -1;
|
|
561
|
+
var bestLen = 0;
|
|
562
|
+
var curStart = -1;
|
|
563
|
+
var curLen = 0;
|
|
564
|
+
for (var i = 0; i < 8; i += 1) { // allow:raw-byte-literal — RFC 4291 §2.2 IPv6 group iteration
|
|
565
|
+
if (groups[i] === 0) {
|
|
566
|
+
if (curStart === -1) curStart = i;
|
|
567
|
+
curLen += 1;
|
|
568
|
+
if (curLen > bestLen) { bestStart = curStart; bestLen = curLen; }
|
|
569
|
+
} else {
|
|
570
|
+
curStart = -1;
|
|
571
|
+
curLen = 0;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
var hex = groups.map(function (n) { return n.toString(16); }); // allow:raw-byte-literal — hex radix
|
|
575
|
+
if (bestLen < 2) return hex.join(":");
|
|
576
|
+
var head = hex.slice(0, bestStart).join(":");
|
|
577
|
+
var tail = hex.slice(bestStart + bestLen).join(":");
|
|
578
|
+
return head + "::" + tail;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function _decodeTxt(rdata, rdlen, caps) {
|
|
582
|
+
if (rdlen > caps.maxTxtRdata) {
|
|
583
|
+
throw new SafeDnsError("safe-dns/oversize-txt-rdata",
|
|
584
|
+
"safeDns.decodeTxt: TXT rdata=" + rdlen + " exceeds maxTxtRdata=" + caps.maxTxtRdata);
|
|
585
|
+
}
|
|
586
|
+
var strings = [];
|
|
587
|
+
var off = 0;
|
|
588
|
+
while (off < rdlen) {
|
|
589
|
+
var len = rdata.readUInt8(off);
|
|
590
|
+
off += 1;
|
|
591
|
+
if (off + len > rdlen) {
|
|
592
|
+
throw new SafeDnsError("safe-dns/malformed-rdlength",
|
|
593
|
+
"safeDns.decodeTxt: character-string length=" + len + " runs past rdata end");
|
|
594
|
+
}
|
|
595
|
+
strings.push(rdata.toString("utf8", off, off + len));
|
|
596
|
+
off += len;
|
|
597
|
+
}
|
|
598
|
+
return strings;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function _decodeSoa(buf, rdataStart, rdlen, caps) {
|
|
602
|
+
var state = { off: rdataStart, buf: buf, caps: caps };
|
|
603
|
+
var mname = _readName(state, 0);
|
|
604
|
+
var rname = _readName(state, 0);
|
|
605
|
+
if (state.off + 20 > rdataStart + rdlen) { // allow:raw-byte-literal — RFC 1035 §3.3.13 SOA tail = SERIAL 4 + REFRESH 4 + RETRY 4 + EXPIRE 4 + MINIMUM 4 = 20 octets
|
|
606
|
+
throw new SafeDnsError("safe-dns/malformed-rdlength",
|
|
607
|
+
"safeDns.decodeSoa: SOA tail truncated");
|
|
608
|
+
}
|
|
609
|
+
var serial = buf.readUInt32BE(state.off);
|
|
610
|
+
var refresh = buf.readUInt32BE(state.off + 4); // allow:raw-byte-literal — RFC 1035 §3.3.13 REFRESH offset
|
|
611
|
+
var retry = buf.readUInt32BE(state.off + 8); // allow:raw-byte-literal — RFC 1035 §3.3.13 RETRY offset
|
|
612
|
+
var expire = buf.readUInt32BE(state.off + 12); // allow:raw-byte-literal — RFC 1035 §3.3.13 EXPIRE offset
|
|
613
|
+
var minimum = buf.readUInt32BE(state.off + 16); // allow:raw-byte-literal — RFC 1035 §3.3.13 MINIMUM offset
|
|
614
|
+
return {
|
|
615
|
+
mname: mname, rname: rname,
|
|
616
|
+
serial: serial, refresh: refresh, retry: retry, expire: expire, minimum: minimum,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function _decodeOpt(rr, caps) {
|
|
621
|
+
// RFC 6891 §6.1.2 — for OPT, CLASS holds the requestor's UDP payload
|
|
622
|
+
// size (advertised buffer), TTL holds extended RCODE + version +
|
|
623
|
+
// flags. We surface those and refuse oversize advertisement.
|
|
624
|
+
var advertised = rr.class;
|
|
625
|
+
if (advertised > caps.maxEdns0Bytes) {
|
|
626
|
+
throw new SafeDnsError("safe-dns/oversize-edns0",
|
|
627
|
+
"safeDns.decodeOpt: advertised buffer size=" + advertised +
|
|
628
|
+
" exceeds maxEdns0Bytes=" + caps.maxEdns0Bytes);
|
|
629
|
+
}
|
|
630
|
+
var extendedRcode = (rr.ttl >>> 24) & 0xff; // allow:raw-byte-literal — RFC 6891 §6.1.3 extended RCODE upper byte
|
|
631
|
+
var version = (rr.ttl >>> 16) & 0xff; // allow:raw-byte-literal — RFC 6891 §6.1.3 version byte
|
|
632
|
+
var dnssecOk = (rr.ttl & 0x8000) !== 0; // allow:raw-byte-literal — RFC 4035 §3.2.1 DO bit
|
|
633
|
+
return {
|
|
634
|
+
advertisedUdpSize: advertised,
|
|
635
|
+
extendedRcode: extendedRcode,
|
|
636
|
+
version: version,
|
|
637
|
+
dnssecOk: dnssecOk,
|
|
638
|
+
rdata: rr.rdata,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function _resolveProfile(opts) {
|
|
643
|
+
if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
|
|
644
|
+
return PROFILES[COMPLIANCE_POSTURES[opts.posture]];
|
|
645
|
+
}
|
|
646
|
+
var p = opts.profile || DEFAULT_PROFILE;
|
|
647
|
+
if (!PROFILES[p]) {
|
|
648
|
+
throw new SafeDnsError("safe-dns/bad-profile",
|
|
649
|
+
"safeDns: unknown profile '" + p + "' (valid: strict / balanced / permissive)");
|
|
650
|
+
}
|
|
651
|
+
return PROFILES[p];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
module.exports = {
|
|
655
|
+
parseResponse: parseResponse,
|
|
656
|
+
boundEdns0: boundEdns0,
|
|
657
|
+
checkCnameChainDepth: checkCnameChainDepth,
|
|
658
|
+
compliancePosture: compliancePosture,
|
|
659
|
+
PROFILES: PROFILES,
|
|
660
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
661
|
+
RTYPE_NAMES: RTYPE_NAMES,
|
|
662
|
+
SafeDnsError: SafeDnsError,
|
|
663
|
+
NAME: "dns",
|
|
664
|
+
KIND: "dns-response",
|
|
665
|
+
};
|