@slicedearth/whoisleuth-cli 1.32.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.
Files changed (189) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
  3. package/NOTICE +76 -0
  4. package/README.md +84 -0
  5. package/TRADEMARKS.md +41 -0
  6. package/bin/whoisleuth.mjs +35 -0
  7. package/cli/archive-inspect.mjs +198 -0
  8. package/cli/arguments.mjs +1022 -0
  9. package/cli/artifact-verify.mjs +210 -0
  10. package/cli/bulk-checkpoint.mjs +218 -0
  11. package/cli/bulk-command-runner.mjs +109 -0
  12. package/cli/bulk-output.mjs +112 -0
  13. package/cli/bulk.mjs +133 -0
  14. package/cli/compare.mjs +238 -0
  15. package/cli/completion.mjs +175 -0
  16. package/cli/csv.mjs +1 -0
  17. package/cli/discover.mjs +50 -0
  18. package/cli/discovery-command-runner.mjs +36 -0
  19. package/cli/discovery-observation-snapshot.mjs +315 -0
  20. package/cli/discovery-scan-command-runner.mjs +141 -0
  21. package/cli/discovery-scan.mjs +325 -0
  22. package/cli/discovery-snapshot.mjs +133 -0
  23. package/cli/discovery-workflow.mjs +73 -0
  24. package/cli/doctor.mjs +137 -0
  25. package/cli/errors.mjs +21 -0
  26. package/cli/evidence-command-arguments.mjs +145 -0
  27. package/cli/evidence-command-runner.mjs +106 -0
  28. package/cli/evidence-signing.mjs +215 -0
  29. package/cli/exit-codes.mjs +9 -0
  30. package/cli/export-evidence.mjs +28 -0
  31. package/cli/formatters/evidence-report.mjs +3 -0
  32. package/cli/formatters/html.mjs +69 -0
  33. package/cli/formatters/json.mjs +135 -0
  34. package/cli/formatters/markdown.mjs +3 -0
  35. package/cli/formatters/terminal.mjs +658 -0
  36. package/cli/http.mjs +27 -0
  37. package/cli/lookup-command-runner.mjs +87 -0
  38. package/cli/lookup-diff.mjs +171 -0
  39. package/cli/mail-review.mjs +204 -0
  40. package/cli/manual.mjs +34 -0
  41. package/cli/network-command-runner.mjs +88 -0
  42. package/cli/output-file.mjs +97 -0
  43. package/cli/page-compare.mjs +123 -0
  44. package/cli/posture.mjs +20 -0
  45. package/cli/progress-events.mjs +53 -0
  46. package/cli/progress.mjs +56 -0
  47. package/cli/registry-access.mjs +3 -0
  48. package/cli/registry-support.mjs +120 -0
  49. package/cli/risk-calibration.mjs +294 -0
  50. package/cli/runner-types.mjs +1 -0
  51. package/cli/runner.mjs +726 -0
  52. package/cli/saved-lookup.mjs +87 -0
  53. package/cli/source-reliability.mjs +727 -0
  54. package/cli/strict-exit.mjs +55 -0
  55. package/cli/terminal-presentation.mjs +147 -0
  56. package/docs/cli.md +827 -0
  57. package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
  58. package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
  59. package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
  60. package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
  61. package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
  62. package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
  63. package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
  64. package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
  65. package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
  66. package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
  67. package/frontend/src/lib/analysis/bulk-sort.js +49 -0
  68. package/frontend/src/lib/analysis/campaign-model.js +265 -0
  69. package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
  70. package/frontend/src/lib/analysis/case-migration-model.js +248 -0
  71. package/frontend/src/lib/analysis/case-model.js +5 -0
  72. package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
  73. package/frontend/src/lib/analysis/case-record-core.js +232 -0
  74. package/frontend/src/lib/analysis/case-record-model.js +7 -0
  75. package/frontend/src/lib/analysis/case-record-operations.js +224 -0
  76. package/frontend/src/lib/analysis/case-response-model.js +640 -0
  77. package/frontend/src/lib/analysis/case-response-packet.js +573 -0
  78. package/frontend/src/lib/analysis/case-storage-model.js +93 -0
  79. package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
  80. package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
  81. package/frontend/src/lib/analysis/http-summary.js +165 -0
  82. package/frontend/src/lib/analysis/investigation-guide.js +500 -0
  83. package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
  84. package/frontend/src/lib/analysis/page-baseline.js +272 -0
  85. package/frontend/src/lib/analysis/page-similarity.js +179 -0
  86. package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
  87. package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
  88. package/frontend/src/lib/analysis/scoring.js +135 -0
  89. package/frontend/src/lib/analysis/shortlist-model.js +209 -0
  90. package/frontend/src/lib/analysis/utils.js +265 -0
  91. package/frontend/src/lib/analysis/watchlist-history.js +643 -0
  92. package/frontend/src/lib/analysis/watchlist-store.js +127 -0
  93. package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
  94. package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
  95. package/frontend/src/lib/analysis/workspace-archive.js +472 -0
  96. package/lib/abort.mjs +26 -0
  97. package/lib/abusech-auth.mjs +19 -0
  98. package/lib/availability.mjs +716 -0
  99. package/lib/bounded-contract-normalizers.mjs +61 -0
  100. package/lib/browser-library-profile.mjs +310 -0
  101. package/lib/bulk-comparison-evidence.mjs +83 -0
  102. package/lib/classify.mjs +93 -0
  103. package/lib/client-behavior-profile.mjs +83 -0
  104. package/lib/credential-surface-profile.mjs +66 -0
  105. package/lib/csv.mjs +23 -0
  106. package/lib/ct-query.mjs +27 -0
  107. package/lib/ct-search.mjs +393 -0
  108. package/lib/distributed-operation-budget.mjs +461 -0
  109. package/lib/dns-delegation-health.mjs +344 -0
  110. package/lib/dns-intelligence.mjs +739 -0
  111. package/lib/dns-mx.mjs +24 -0
  112. package/lib/dns-resolver-selection.mjs +33 -0
  113. package/lib/domain-posture-analysis.mjs +254 -0
  114. package/lib/domain-posture-parsers.mjs +358 -0
  115. package/lib/domain-posture.mjs +745 -0
  116. package/lib/error-detail.mjs +10 -0
  117. package/lib/evidence-export.mjs +431 -0
  118. package/lib/evidence-report-markdown.mjs +71 -0
  119. package/lib/evidence-report.mjs +391 -0
  120. package/lib/external-intelligence-risk.mjs +108 -0
  121. package/lib/favicon.mjs +146 -0
  122. package/lib/feature-policy.mjs +70 -0
  123. package/lib/generated/retire-browser-catalog.mjs +8724 -0
  124. package/lib/generated/unicode-confusables-17.mjs +93 -0
  125. package/lib/hostname.mjs +38 -0
  126. package/lib/html-signals.mjs +680 -0
  127. package/lib/http-intelligence.mjs +232 -0
  128. package/lib/idn-confusable-policy.mjs +86 -0
  129. package/lib/idn-confusables.mjs +465 -0
  130. package/lib/lookup-cache.mjs +130 -0
  131. package/lib/lookup-progress.mjs +291 -0
  132. package/lib/lookup-source-progress.mjs +109 -0
  133. package/lib/lookup-threat-provider-inventory.mjs +13 -0
  134. package/lib/lookup.mjs +592 -0
  135. package/lib/observation.mjs +114 -0
  136. package/lib/observed-network-context.mjs +355 -0
  137. package/lib/operation-budget.mjs +489 -0
  138. package/lib/page-fingerprints.mjs +481 -0
  139. package/lib/page-role-profile.mjs +117 -0
  140. package/lib/perceptual-hash.mjs +451 -0
  141. package/lib/rdap-attempts.mjs +34 -0
  142. package/lib/rdap-bootstrap.mjs +173 -0
  143. package/lib/rdap-capabilities.mjs +292 -0
  144. package/lib/rdap-client.mjs +125 -0
  145. package/lib/rdap-normalization.mjs +691 -0
  146. package/lib/rdap-registrar.mjs +196 -0
  147. package/lib/rdap-transport.mjs +39 -0
  148. package/lib/rdap-types.mjs +1 -0
  149. package/lib/rdap-validation.mjs +94 -0
  150. package/lib/rdap.mjs +16 -0
  151. package/lib/registry-access.mjs +32 -0
  152. package/lib/registry-capabilities.mjs +117 -0
  153. package/lib/registry-capability-catalogue.mjs +1206 -0
  154. package/lib/registry-comparison.mjs +318 -0
  155. package/lib/registry-dates.mjs +190 -0
  156. package/lib/registry-insights.mjs +325 -0
  157. package/lib/response-policy.mjs +256 -0
  158. package/lib/risk-scoring.mjs +164 -0
  159. package/lib/safe-fetch.mjs +446 -0
  160. package/lib/security-txt.mjs +359 -0
  161. package/lib/service-binding-dns.mjs +608 -0
  162. package/lib/sslbl-certificates.generated.mjs +143 -0
  163. package/lib/sslbl-intelligence.mjs +241 -0
  164. package/lib/static-html-analysis.mjs +340 -0
  165. package/lib/structured-data-identity.mjs +323 -0
  166. package/lib/threat-intelligence-contract.mjs +5 -0
  167. package/lib/threat-intelligence-definition-registry.mjs +34 -0
  168. package/lib/threat-intelligence-fixtures.mjs +82 -0
  169. package/lib/threat-intelligence-manifests.mjs +512 -0
  170. package/lib/threat-intelligence-runtime.mjs +662 -0
  171. package/lib/threat-intelligence-targets.mjs +168 -0
  172. package/lib/threat-intelligence-types.mjs +3 -0
  173. package/lib/threatfox-intelligence.mjs +307 -0
  174. package/lib/tls-intelligence.mjs +814 -0
  175. package/lib/typosquat-generator.mjs +884 -0
  176. package/lib/urlhaus-intelligence.mjs +361 -0
  177. package/lib/urlscan-intelligence.mjs +349 -0
  178. package/lib/website-security-posture.mjs +325 -0
  179. package/lib/website-technology.mjs +437 -0
  180. package/lib/whois-authority.mjs +98 -0
  181. package/lib/whois-chain.mjs +261 -0
  182. package/lib/whois-contacts.mjs +163 -0
  183. package/lib/whois-contracts.mjs +3 -0
  184. package/lib/whois-normalization.mjs +27 -0
  185. package/lib/whois-transport.mjs +100 -0
  186. package/lib/whois-values.mjs +92 -0
  187. package/lib/whois.mjs +1187 -0
  188. package/package.json +55 -0
  189. package/third-party-notices.txt +5571 -0
@@ -0,0 +1,291 @@
1
+ // Portable bounded NDJSON Lookup progress contract. Partial events are
2
+ // presentation-only; only a validated final ordinary Lookup result may cross
3
+ // the persistence boundary.
4
+ export const LOOKUP_PROGRESS_SCHEMA = 'whoisleuth.lookup-progress';
5
+ export const LOOKUP_PROGRESS_VERSION = 1;
6
+ export const MAX_LOOKUP_PROGRESS_SOURCES = 16;
7
+ export const MAX_LOOKUP_PROGRESS_EVENTS = MAX_LOOKUP_PROGRESS_SOURCES + 2;
8
+ export const MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES = 256 * 1024;
9
+ export const MAX_LOOKUP_PROGRESS_FINAL_BYTES = 8 * 1024 * 1024;
10
+ export const MAX_LOOKUP_PROGRESS_STREAM_BYTES = 12 * 1024 * 1024;
11
+ const SOURCE_IDS = Object.freeze([
12
+ 'rdap',
13
+ 'whois',
14
+ 'domain_evidence',
15
+ 'reverse_dns',
16
+ 'registrar_rdap',
17
+ 'network_context',
18
+ 'security_txt',
19
+ 'external_intelligence',
20
+ 'malware_host_intelligence',
21
+ 'malware_ioc_intelligence',
22
+ ]);
23
+ const SOURCE_ID_SET = new Set(SOURCE_IDS);
24
+ const SOURCE_STATES = new Set([
25
+ 'success',
26
+ 'partial',
27
+ 'not_found',
28
+ 'skipped',
29
+ 'error',
30
+ 'unsupported',
31
+ 'unavailable',
32
+ 'rate_limited',
33
+ ]);
34
+ const SOURCE_ID_RE = /^[a-z][a-z0-9_]{0,39}$/u;
35
+ const TEXT_ENCODER = new TextEncoder();
36
+ function byteLength(value) {
37
+ return TEXT_ENCODER.encode(value).byteLength;
38
+ }
39
+ function record(value) {
40
+ return value && typeof value === 'object' && !Array.isArray(value)
41
+ ? value
42
+ : null;
43
+ }
44
+ function exactKeys(value, expected, label) {
45
+ const keys = Object.keys(value);
46
+ if (keys.length !== expected.length || keys.some((key) => !expected.includes(key))) {
47
+ throw new TypeError(`${label} has an invalid field set.`);
48
+ }
49
+ }
50
+ function sequence(value) {
51
+ if (!Number.isSafeInteger(value) || Number(value) < 0 || Number(value) >= MAX_LOOKUP_PROGRESS_EVENTS) {
52
+ throw new TypeError('Lookup progress sequence is invalid.');
53
+ }
54
+ return Number(value);
55
+ }
56
+ function source(value) {
57
+ if (typeof value !== 'string' || !SOURCE_ID_RE.test(value) || !SOURCE_ID_SET.has(value)) {
58
+ throw new TypeError('Lookup progress source is invalid.');
59
+ }
60
+ return value;
61
+ }
62
+ function sources(value) {
63
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_LOOKUP_PROGRESS_SOURCES) {
64
+ throw new TypeError('Lookup progress sources are missing or exceed their bound.');
65
+ }
66
+ const normalized = value.map(source);
67
+ if (new Set(normalized).size !== normalized.length) {
68
+ throw new TypeError('Lookup progress sources contain duplicates.');
69
+ }
70
+ return normalized;
71
+ }
72
+ function cloneBounded(value, maximumBytes, label) {
73
+ let serialized;
74
+ try {
75
+ serialized = JSON.stringify(value);
76
+ }
77
+ catch {
78
+ throw new TypeError(`${label} is not JSON serializable.`);
79
+ }
80
+ if (serialized === undefined || byteLength(serialized) > maximumBytes) {
81
+ throw new TypeError(`${label} exceeds its ${maximumBytes}-byte bound.`);
82
+ }
83
+ return JSON.parse(serialized);
84
+ }
85
+ export function createLookupProgressStart(mode, rawSources) {
86
+ const planned = sources(rawSources);
87
+ return Object.freeze({
88
+ schema: LOOKUP_PROGRESS_SCHEMA,
89
+ version: LOOKUP_PROGRESS_VERSION,
90
+ event: 'start',
91
+ sequence: 0,
92
+ mode,
93
+ sources: Object.freeze(planned),
94
+ persistence: 'prohibited',
95
+ });
96
+ }
97
+ export function createLookupProgressSource(sequenceValue, sourceValue, state, fragment, options = {}) {
98
+ const normalizedSequence = sequence(sequenceValue);
99
+ if (normalizedSequence === 0 || !SOURCE_STATES.has(state)) {
100
+ throw new TypeError('Lookup source event sequence or state is invalid.');
101
+ }
102
+ return Object.freeze({
103
+ schema: LOOKUP_PROGRESS_SCHEMA,
104
+ version: LOOKUP_PROGRESS_VERSION,
105
+ event: 'source',
106
+ sequence: normalizedSequence,
107
+ source: source(sourceValue),
108
+ state,
109
+ complete: options.complete === true,
110
+ truncated: options.truncated === true,
111
+ fragment: cloneBounded(fragment, MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES, 'Lookup progress fragment'),
112
+ persistence: 'prohibited',
113
+ });
114
+ }
115
+ export function createLookupProgressFinal(sequenceValue, rawSources, result) {
116
+ const normalizedSequence = sequence(sequenceValue);
117
+ if (normalizedSequence < 2)
118
+ throw new TypeError('Lookup final event sequence is invalid.');
119
+ return Object.freeze({
120
+ schema: LOOKUP_PROGRESS_SCHEMA,
121
+ version: LOOKUP_PROGRESS_VERSION,
122
+ event: 'final',
123
+ sequence: normalizedSequence,
124
+ sources: Object.freeze(sources(rawSources)),
125
+ result: cloneBounded(result, MAX_LOOKUP_PROGRESS_FINAL_BYTES, 'Final Lookup result'),
126
+ persistence: 'authoritative_after_validation',
127
+ });
128
+ }
129
+ export function parseLookupProgressEvent(raw) {
130
+ const value = record(raw);
131
+ if (!value
132
+ || value.schema !== LOOKUP_PROGRESS_SCHEMA
133
+ || value.version !== LOOKUP_PROGRESS_VERSION) {
134
+ throw new TypeError('Lookup progress event uses an unsupported contract.');
135
+ }
136
+ if (value.event === 'start') {
137
+ exactKeys(value, ['schema', 'version', 'event', 'sequence', 'mode', 'sources', 'persistence'], 'Lookup start event');
138
+ if (sequence(value.sequence) !== 0
139
+ || !['fast', 'deep'].includes(String(value.mode))
140
+ || value.persistence !== 'prohibited') {
141
+ throw new TypeError('Lookup start event is invalid.');
142
+ }
143
+ return createLookupProgressStart(value.mode, sources(value.sources));
144
+ }
145
+ if (value.event === 'source') {
146
+ exactKeys(value, [
147
+ 'schema', 'version', 'event', 'sequence', 'source', 'state',
148
+ 'complete', 'truncated', 'fragment', 'persistence',
149
+ ], 'Lookup source event');
150
+ if (typeof value.complete !== 'boolean'
151
+ || typeof value.truncated !== 'boolean'
152
+ || value.persistence !== 'prohibited'
153
+ || typeof value.state !== 'string'
154
+ || !SOURCE_STATES.has(value.state)) {
155
+ throw new TypeError('Lookup source event is invalid.');
156
+ }
157
+ return createLookupProgressSource(sequence(value.sequence), source(value.source), value.state, value.fragment, { complete: value.complete, truncated: value.truncated });
158
+ }
159
+ if (value.event === 'final') {
160
+ exactKeys(value, ['schema', 'version', 'event', 'sequence', 'sources', 'result', 'persistence'], 'Lookup final event');
161
+ if (value.persistence !== 'authoritative_after_validation') {
162
+ throw new TypeError('Lookup final event is invalid.');
163
+ }
164
+ return createLookupProgressFinal(sequence(value.sequence), sources(value.sources), value.result);
165
+ }
166
+ throw new TypeError('Lookup progress event type is invalid.');
167
+ }
168
+ export function encodeLookupProgressEvent(event) {
169
+ return `${JSON.stringify(parseLookupProgressEvent(event))}\n`;
170
+ }
171
+ export function createLookupProgressReducer(options) {
172
+ if (typeof options.validateFinalResult !== 'function') {
173
+ throw new TypeError('Lookup progress reducer requires a final-result validator.');
174
+ }
175
+ let planned = [];
176
+ let mode = null;
177
+ let expectedSequence = 0;
178
+ let finalResult = null;
179
+ let finalReceived = false;
180
+ const settled = new Map();
181
+ function apply(rawEvent) {
182
+ const event = parseLookupProgressEvent(rawEvent);
183
+ if (event.sequence !== expectedSequence) {
184
+ throw new TypeError(`Lookup progress sequence ${event.sequence} arrived while ${expectedSequence} was required.`);
185
+ }
186
+ if (event.event === 'start') {
187
+ if (mode !== null)
188
+ throw new TypeError('Lookup progress stream started more than once.');
189
+ mode = event.mode;
190
+ planned = [...event.sources];
191
+ }
192
+ else if (event.event === 'source') {
193
+ if (mode === null || finalReceived || !planned.includes(event.source) || settled.has(event.source)) {
194
+ throw new TypeError('Lookup source event is duplicate, unplanned, or out of order.');
195
+ }
196
+ settled.set(event.source, Object.freeze({
197
+ source: event.source,
198
+ state: event.state,
199
+ complete: event.complete,
200
+ truncated: event.truncated,
201
+ fragment: cloneBounded(event.fragment, MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES, 'Lookup progress fragment'),
202
+ }));
203
+ }
204
+ else {
205
+ if (mode === null || finalReceived
206
+ || event.sources.length !== planned.length
207
+ || event.sources.some((item, index) => item !== planned[index])
208
+ || settled.size !== planned.length) {
209
+ throw new TypeError('Final Lookup event arrived before every planned source settled.');
210
+ }
211
+ const settledSources = planned.map((item) => settled.get(item)).filter((item) => Boolean(item));
212
+ if (!options.validateFinalResult(event.result, settledSources)) {
213
+ throw new TypeError('Final Lookup result failed its ordinary response validator.');
214
+ }
215
+ finalResult = cloneBounded(event.result, MAX_LOOKUP_PROGRESS_FINAL_BYTES, 'Final Lookup result');
216
+ finalReceived = true;
217
+ }
218
+ expectedSequence += 1;
219
+ return snapshot();
220
+ }
221
+ function snapshot() {
222
+ return Object.freeze({
223
+ started: mode !== null,
224
+ mode,
225
+ plannedSources: Object.freeze([...planned]),
226
+ settledSources: Object.freeze(planned.map((item) => settled.get(item)).filter((item) => Boolean(item))),
227
+ finalReceived,
228
+ persistable: false,
229
+ });
230
+ }
231
+ function finish() {
232
+ if (!finalReceived) {
233
+ throw new TypeError('Lookup progress stream ended before a validated final result.');
234
+ }
235
+ return cloneBounded(finalResult, MAX_LOOKUP_PROGRESS_FINAL_BYTES, 'Final Lookup result');
236
+ }
237
+ return Object.freeze({ apply, snapshot, finish });
238
+ }
239
+ export function createLookupProgressNdjsonDecoder(onEvent) {
240
+ if (typeof onEvent !== 'function')
241
+ throw new TypeError('NDJSON decoder requires an event consumer.');
242
+ const decoder = new TextDecoder('utf-8', { fatal: true });
243
+ let pending = '';
244
+ let totalBytes = 0;
245
+ let eventCount = 0;
246
+ function consumeLine(line) {
247
+ if (!line)
248
+ return;
249
+ if (byteLength(line) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
250
+ throw new TypeError('Lookup progress line exceeds its byte bound.');
251
+ }
252
+ eventCount += 1;
253
+ if (eventCount > MAX_LOOKUP_PROGRESS_EVENTS) {
254
+ throw new TypeError('Lookup progress stream contains too many events.');
255
+ }
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(line);
259
+ }
260
+ catch {
261
+ throw new TypeError('Lookup progress stream contains malformed JSON.');
262
+ }
263
+ onEvent(parseLookupProgressEvent(parsed));
264
+ }
265
+ function push(chunk) {
266
+ if (!(chunk instanceof Uint8Array))
267
+ throw new TypeError('NDJSON chunks must be byte arrays.');
268
+ totalBytes += chunk.byteLength;
269
+ if (totalBytes > MAX_LOOKUP_PROGRESS_STREAM_BYTES) {
270
+ throw new TypeError('Lookup progress stream exceeds its total byte bound.');
271
+ }
272
+ pending += decoder.decode(chunk, { stream: true });
273
+ let newline = pending.indexOf('\n');
274
+ while (newline >= 0) {
275
+ const line = pending.slice(0, newline).replace(/\r$/u, '');
276
+ pending = pending.slice(newline + 1);
277
+ consumeLine(line);
278
+ newline = pending.indexOf('\n');
279
+ }
280
+ if (byteLength(pending) > MAX_LOOKUP_PROGRESS_FINAL_BYTES + MAX_LOOKUP_PROGRESS_FRAGMENT_BYTES) {
281
+ throw new TypeError('Lookup progress line exceeds its byte bound.');
282
+ }
283
+ }
284
+ function finish() {
285
+ pending += decoder.decode();
286
+ if (pending)
287
+ consumeLine(pending.replace(/\r$/u, ''));
288
+ pending = '';
289
+ }
290
+ return Object.freeze({ push, finish });
291
+ }
@@ -0,0 +1,109 @@
1
+ // Minimal presentation-only source settlement summaries for incremental Deep
2
+ // Lookup. These deliberately exclude raw RDAP/WHOIS bodies, contacts, URLs,
3
+ // provider payloads, and request errors. The ordinary final Lookup response
4
+ // remains the only authoritative and persistable result.
5
+ const RESULT_STATES = new Set([
6
+ 'available',
7
+ 'expiring',
8
+ 'for_sale',
9
+ 'registered',
10
+ 'unknown',
11
+ ]);
12
+ const DIRECT_STATES = new Set([
13
+ 'success',
14
+ 'partial',
15
+ 'not_found',
16
+ 'skipped',
17
+ 'error',
18
+ 'unsupported',
19
+ 'unavailable',
20
+ 'rate_limited',
21
+ ]);
22
+ function record(value) {
23
+ return value && typeof value === 'object' && !Array.isArray(value)
24
+ ? value
25
+ : {};
26
+ }
27
+ function plannedLookupProgressSources(classified, options = {}) {
28
+ const sources = ['rdap', 'whois'];
29
+ if (classified.type === 'domain') {
30
+ sources.push('domain_evidence', 'registrar_rdap', 'network_context');
31
+ if (options.securityTxt)
32
+ sources.push('security_txt');
33
+ if (options.externalIntelligence)
34
+ sources.push('external_intelligence');
35
+ if (options.malwareHostIntelligence)
36
+ sources.push('malware_host_intelligence');
37
+ if (options.malwareIocIntelligence)
38
+ sources.push('malware_ioc_intelligence');
39
+ }
40
+ else if (classified.type === 'ipv4' || classified.type === 'ipv6') {
41
+ sources.push('reverse_dns');
42
+ }
43
+ return Object.freeze(sources);
44
+ }
45
+ function normalizedState(source, outcome, value) {
46
+ if (outcome === 'rejected')
47
+ return 'error';
48
+ if (value === null || value === undefined)
49
+ return 'skipped';
50
+ if (source === 'rdap') {
51
+ const rdap = record(value);
52
+ if (rdap.upstreamStatus === 404)
53
+ return 'not_found';
54
+ return rdap.upstreamStatus === 200 ? 'success' : 'unsupported';
55
+ }
56
+ if (source === 'whois') {
57
+ if (!Array.isArray(value))
58
+ return 'error';
59
+ if (value.length <= 1)
60
+ return 'unsupported';
61
+ return 'success';
62
+ }
63
+ if (source === 'domain_evidence') {
64
+ return record(value).deepScanComplete === false ? 'partial' : 'success';
65
+ }
66
+ const status = record(value).status;
67
+ if (typeof status === 'string' && DIRECT_STATES.has(status)) {
68
+ return status;
69
+ }
70
+ if (status === 'complete' || status === 'observed' || status === 'found')
71
+ return 'success';
72
+ if (status === 'not_applicable' || status === 'disabled')
73
+ return 'skipped';
74
+ return 'success';
75
+ }
76
+ function normalizeLookupSourceSettlement(source, outcome, value) {
77
+ const state = normalizedState(source, outcome, value);
78
+ const sourceRecord = record(value);
79
+ const resultState = source === 'domain_evidence'
80
+ && typeof sourceRecord.state === 'string'
81
+ && RESULT_STATES.has(sourceRecord.state)
82
+ ? sourceRecord.state
83
+ : null;
84
+ const complete = !['partial', 'error', 'unavailable', 'rate_limited'].includes(state);
85
+ const truncated = sourceRecord.truncated === true
86
+ || sourceRecord.recordsTruncated === true
87
+ || sourceRecord.bodyTruncated === true;
88
+ const limitation = outcome === 'rejected'
89
+ ? 'This source did not complete. No absence or safety conclusion was inferred.'
90
+ : state === 'skipped'
91
+ ? 'This source was not requested or was not applicable to the target.'
92
+ : state === 'unsupported'
93
+ ? 'This source could not provide authoritative evidence for this target.'
94
+ : state === 'partial'
95
+ ? 'This source returned incomplete evidence; missing fields remain unknown.'
96
+ : null;
97
+ return Object.freeze({
98
+ source,
99
+ state,
100
+ complete,
101
+ truncated,
102
+ fragment: Object.freeze({
103
+ status: state,
104
+ ...(resultState ? { resultState } : {}),
105
+ ...(limitation ? { limitation } : {}),
106
+ }),
107
+ });
108
+ }
109
+ export { normalizeLookupSourceSettlement, plannedLookupProgressSources, };
@@ -0,0 +1,13 @@
1
+ // One explicit inventory for optional threat-intelligence providers that can
2
+ // contribute to Deep Lookup. Runtime orchestration and the offline conformance
3
+ // suite both consume these exact definitions so adding a provider cannot omit
4
+ // the shared timeout, truncation, neutral-miss, and provenance checks silently.
5
+ import { THREATFOX_PROVIDER } from "./threatfox-intelligence.mjs";
6
+ import { URLHAUS_PROVIDER } from "./urlhaus-intelligence.mjs";
7
+ import { URLSCAN_PROVIDER } from "./urlscan-intelligence.mjs";
8
+ const LOOKUP_THREAT_INTELLIGENCE_PROVIDERS = Object.freeze([
9
+ URLSCAN_PROVIDER,
10
+ URLHAUS_PROVIDER,
11
+ THREATFOX_PROVIDER,
12
+ ]);
13
+ export { LOOKUP_THREAT_INTELLIGENCE_PROVIDERS, THREATFOX_PROVIDER, URLHAUS_PROVIDER, URLSCAN_PROVIDER, };