@slicedearth/whoisleuth-cli 2.5.1 → 2.6.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/DISCLOSURE +1 -1
- package/cli/artifact-validation/case-response.mjs +51 -8
- package/cli/artifact-validation/lookup-evidence.mjs +15 -1
- package/cli/artifact-validation/signed-review.mjs +2 -51
- package/cli/artifact-verify.mjs +9 -2
- package/cli/bulk-command-runner.mjs +4 -6
- package/cli/case-command.mjs +79 -3
- package/cli/case-pack.mjs +7 -2
- package/cli/command-definition.mjs +2 -1
- package/cli/discovery-scan-command-runner.mjs +4 -6
- package/cli/domain-feed.mjs +61 -0
- package/cli/formatters/json.mjs +1 -13
- package/cli/indicator-set-command.mjs +1 -2
- package/cli/intake-command.mjs +43 -3
- package/cli/investigation-artifacts.mjs +2 -3
- package/cli/investigation-package-review.mjs +2 -0
- package/cli/local-mmdb-review.mjs +27 -4
- package/cli/lookup-command-runner.mjs +1 -0
- package/cli/offline-evidence-review.mjs +51 -5
- package/cli/review-arguments.mjs +3 -2
- package/cli/review-command-definitions.mjs +14 -8
- package/cli/review-command-runner.mjs +6 -3
- package/cli/risk-calibration.mjs +6 -4
- package/cli/selected-input-worker.mjs +12 -2
- package/cli/support-command-runner.mjs +3 -3
- package/cli/watchlist-review.mjs +43 -0
- package/cli/workflow-arguments.mjs +14 -0
- package/cli/workflow-command-definitions.mjs +26 -1
- package/cli/workflow-command-runner.mjs +32 -0
- package/docs/cli.md +99 -2
- package/docs/contextual-reviews.md +28 -5
- package/docs/infrastructure-observations.md +126 -0
- package/docs/offline-intake-context.md +213 -0
- package/lib/credential-form-attribution.mjs +136 -0
- package/lib/credential-surface-profile.mjs +6 -2
- package/lib/dns-delegation-health.mjs +23 -7
- package/lib/dns-intelligence.mjs +18 -4
- package/lib/domain-posture-analysis.mjs +53 -2
- package/lib/domain-posture-parsers.mjs +9 -0
- package/lib/domain-posture.mjs +29 -10
- package/lib/evidence-export-privacy.mjs +5 -2
- package/lib/evidence-report.mjs +1 -0
- package/lib/favicon.mjs +13 -4
- package/lib/html-signals.mjs +6 -0
- package/lib/lookup-child-profile-contract.mjs +28 -11
- package/lib/lookup-response-contract.mjs +1 -0
- package/lib/operation-budget.mjs +4 -0
- package/lib/service-binding-dns.mjs +19 -2
- package/lib/sslbl-certificates.generated.mjs +138 -137
- package/lib/static-html-analysis.mjs +32 -4
- package/lib/technology-evidence-role.mjs +1 -51
- package/lib/tls-intelligence.mjs +30 -5
- package/lib/website-enrichment.mjs +39 -23
- package/lib/whois-chain.mjs +12 -3
- package/lib/whois-common-formats.mjs +3 -1
- package/lib/whois-contacts.mjs +13 -2
- package/package.json +7 -5
- package/packages/analysis/risk-scoring.mjs +12 -9
- package/packages/cases/case-containment-handoff.mjs +195 -0
- package/packages/cases/case-evidence-links.mjs +16 -0
- package/packages/cases/case-evidence-model.mjs +14 -3
- package/packages/cases/case-object-coverage.mjs +33 -0
- package/packages/cases/case-packet-correction.mjs +106 -0
- package/packages/cases/case-recheck-model.mjs +35 -5
- package/packages/cases/case-record-operations.mjs +74 -9
- package/packages/cases/case-record-projection.mjs +15 -3
- package/packages/cases/case-record-version-input.mjs +23 -1
- package/packages/cases/case-report.mjs +30 -0
- package/packages/cases/case-requested-evidence.mjs +1 -1
- package/packages/cases/case-response-actions.mjs +57 -1
- package/packages/cases/case-response-model.mjs +7 -0
- package/packages/cases/case-response-object.mjs +119 -0
- package/packages/cases/case-response-outcomes.mjs +81 -14
- package/packages/cases/case-response-packet-render.mjs +22 -3
- package/packages/cases/case-response-packet.mjs +30 -2
- package/packages/cases/case-response-review-inputs.mjs +48 -7
- package/packages/cases/case-workflow-metadata.mjs +1 -9
- package/packages/contracts/analyst-interchange.mjs +7 -5
- package/packages/contracts/candidate-watch-review.mjs +54 -0
- package/packages/contracts/case-portability.mjs +260 -42
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/context-review.mjs +2 -1
- package/packages/contracts/external-observation-interchange.mjs +96 -28
- package/packages/contracts/lookup-evidence.mjs +3 -2
- package/packages/contracts/message-intake.mjs +7 -1
- package/packages/contracts/risk-calibration.mjs +44 -29
- package/packages/contracts/schema-lifecycle.mjs +3 -1
- package/packages/contracts/web-capture.mjs +1 -1
- package/packages/contracts/workspace-portability.mjs +86 -16
- package/packages/evidence/collection-quality.mjs +1 -1
- package/packages/evidence/credential-form-attribution.mjs +60 -0
- package/packages/evidence/image-regions.mjs +15 -0
- package/packages/evidence/record-identity.mjs +60 -16
- package/packages/evidence/technology-evidence-role.mjs +51 -0
- package/packages/interchange/external-findings-import.mjs +32 -17
- package/packages/interchange/json-output.mjs +14 -0
- package/packages/interchange/managed-indicator-export.mjs +3 -2
- package/packages/interchange/managed-indicator-set.mjs +13 -0
- package/packages/investigation/acquisition-decision-packet.mjs +78 -0
- package/packages/investigation/context-review.mjs +3 -3
- package/packages/investigation/document-intake.mjs +9 -3
- package/packages/investigation/domain-history-review.mjs +43 -11
- package/packages/investigation/domain-transition-review.mjs +2 -0
- package/packages/investigation/infrastructure-observation.mjs +234 -0
- package/packages/investigation/intake-context.mjs +284 -0
- package/packages/investigation/intake-indicators.mjs +116 -0
- package/packages/investigation/intake-phones.mjs +155 -0
- package/packages/investigation/intake-report.mjs +2 -1
- package/packages/investigation/investigation-manifest.mjs +33 -7
- package/packages/investigation/link-intake.mjs +38 -8
- package/packages/investigation/message-intake.mjs +55 -15
- package/packages/monitoring/domain-feed.mjs +321 -0
- package/packages/workspace/brand-candidate-workflow.mjs +351 -0
- package/packages/workspace/brand-keyword-campaign.mjs +231 -0
- package/packages/workspace/brand-profile-model.mjs +46 -2
- package/packages/workspace/bulk-session-model.mjs +4 -0
- package/packages/workspace/candidate-watch-handoff.mjs +195 -0
- package/packages/workspace/detection-rule-model.mjs +10 -2
- package/packages/workspace/shortlist-model.mjs +10 -2
- package/packages/workspace/watchlist-history.mjs +31 -4
- package/packages/workspace/watchlist-store.mjs +136 -7
- package/third-party-notices.txt +29 -29
|
@@ -3,7 +3,19 @@ import { CliUsageError } from "./errors.mjs";
|
|
|
3
3
|
import {} from "./fail-policy.mjs";
|
|
4
4
|
import { commandDefaultNumber } from "./command-reference.mjs";
|
|
5
5
|
import { WORKFLOW_COMMAND_DEFINITIONS } from "./workflow-command-definitions.mjs";
|
|
6
|
+
import { parseDomainFeedSelectors } from "./domain-feed.mjs";
|
|
6
7
|
import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
|
|
8
|
+
function parseDomainFeedArguments(parsed) {
|
|
9
|
+
const operation = parsed.positionalValue('operation');
|
|
10
|
+
const contextSource = parsed.positionalValue('context');
|
|
11
|
+
if ((operation === 'watch-input') !== Boolean(contextSource))
|
|
12
|
+
throw new CliUsageError('watch-input requires one existing candidate-watch-input context file; review does not accept a context file.');
|
|
13
|
+
return { action: 'domain-feed', operation, feedId: parsed.positionalValue('feed'), source: parsed.positionalValue('source'), contextSource,
|
|
14
|
+
selection: parseDomainFeedSelectors(parsed.optionValues('--select')), output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
15
|
+
}
|
|
16
|
+
function parseWatchlistReviewArguments(parsed) {
|
|
17
|
+
return { action: 'watchlist-review', operation: parsed.positionalValue('operation'), source: parsed.positionalValue('source'), output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
18
|
+
}
|
|
7
19
|
function parseMonitorOnceArguments(parsed) {
|
|
8
20
|
const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
|
|
9
21
|
return {
|
|
@@ -80,6 +92,8 @@ function parseWorkflowRunArguments(parsed) {
|
|
|
80
92
|
};
|
|
81
93
|
}
|
|
82
94
|
export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
|
|
95
|
+
'domain-feed': parseDomainFeedArguments,
|
|
96
|
+
'watchlist-review': parseWatchlistReviewArguments,
|
|
83
97
|
'monitor-once': parseMonitorOnceArguments,
|
|
84
98
|
'workflow-plan': parseWorkflowPlanArguments,
|
|
85
99
|
'workflow-run': parseWorkflowRunArguments,
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES } from "./investigation-recipes.mjs";
|
|
2
|
-
import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file } from "./command-definition.mjs";
|
|
2
|
+
import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file, optionDefinition } from "./command-definition.mjs";
|
|
3
|
+
import { DOMAIN_FEED_CATALOGUE } from "../packages/monitoring/domain-feed.mjs";
|
|
3
4
|
export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
|
|
5
|
+
'domain-feed': commandSeed({
|
|
6
|
+
reference: { description: 'Review exact hosts or literal terms in a selected local plain-domain feed.',
|
|
7
|
+
example: 'whoisleuth domain-feed review tif-mini feed.txt --select host:candidate.example --json',
|
|
8
|
+
boundary: 'Always available offline. Reads one explicit local regular file; never downloads a feed, performs a Lookup, changes scores, retains candidates or enables a schedule. Streams at most 256 MiB and 10 million physical rows with 1,024-byte lines and a ten-minute deadline; retains at most 200 matched hosts. Repeat --select host:<hostname>, term:<literal> or exclude:<literal> for at most 200 exact hosts, 20 positive and 20 negative literal terms of 3–80 characters. A negative match vetoes even an exact host before the match bound; at least one positive term or exact host is required. Matching uses canonical ASCII/punycode substrings, never token boundaries, parent equivalence, typo/confusable expansion or regular expressions. Review emits the full raw-byte digest, separate declared file/local clocks, unknown per-host clocks and omissions. watch-input also requires a candidate-watch-input v1 context file with an empty candidates array, and emits that existing format for a separate watchlist-review plan/export. Inclusion is not a maliciousness, activity, ownership or availability verdict.' },
|
|
9
|
+
collection: { scope: 'Local raw file: 256 MiB, 10 million physical rows, 1,024 bytes per line, ten minutes; 200 retained hosts, 20 positive and 20 negative literal terms, no requests.' },
|
|
10
|
+
summary: 'Review a selected local plain-domain feed', options: ['--select', '--json', '--quiet', '--no-color'],
|
|
11
|
+
optionOverrides: { '--select': optionDefinition('text', 'Select host:<exact-hostname>, term:<literal-text> or exclude:<literal-text>; repeat for separate explicit selectors.', { occurrence: 'repeatable', acceptsOptionLikeValue: true }) },
|
|
12
|
+
positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['review', 'watch-input']),
|
|
13
|
+
positional('feed', 'enum', 1, 1, DOMAIN_FEED_CATALOGUE.map(feed => feed.id)), positional('source', 'file', 1, 1), positional('context', 'file', 0, 1)]),
|
|
14
|
+
constraints: Object.freeze([constraint({ kind: 'required', options: ['--select'] })]),
|
|
15
|
+
handlerOwner: 'workflow', networkEffect: 'offline', common: false,
|
|
16
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input']), primaryArtefacts: Object.freeze(['Bounded feed inclusion review', 'Explicit candidate watch selection']),
|
|
17
|
+
planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
|
|
18
|
+
}),
|
|
19
|
+
'watchlist-review': commandSeed({
|
|
20
|
+
reference: { description: 'Preview or export an explicit candidate selection to a local watchlist.', example: 'whoisleuth watchlist-review plan selection.json --json',
|
|
21
|
+
boundary: 'Reads bounded local JSON only. Plan shows exact domains, duplicates, per-domain outcomes, destination and review priority with zero additional requests. Export emits Watchlist schema 5 without creating a scan or baseline. Neither mode enables collection or scheduling.' },
|
|
22
|
+
collection: { scope: 'At most 2,000 selected candidate domains; no target requests.' },
|
|
23
|
+
summary: 'Review a local candidate watchlist handoff', options: ['--json', '--quiet', '--no-color'],
|
|
24
|
+
positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['plan', 'export']), positional('source', 'file', 0, 1, [], 'argv_or_stdin')]),
|
|
25
|
+
constraints: Object.freeze([]), handlerOwner: 'workflow', networkEffect: 'offline', common: false,
|
|
26
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input', 'whoisleuth.watchlists']), primaryArtefacts: Object.freeze(['Exact candidate handoff plan', 'Local watchlist export']),
|
|
27
|
+
planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
|
|
28
|
+
}),
|
|
4
29
|
"monitor-once": commandSeed({
|
|
5
30
|
reference: {
|
|
6
31
|
description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
|
|
@@ -16,7 +16,37 @@ import { MAX_SHARING_REVIEW_BYTES } from "./sharing-review.mjs";
|
|
|
16
16
|
import { createBufferedOutput } from "./output-file.mjs";
|
|
17
17
|
import { boundedInteractiveAnswer, canReadInteractiveLine, readBoundedInteractiveLine } from "./terminal-input.mjs";
|
|
18
18
|
import { WORKFLOW_INLINE_COMMANDS } from "./inline-command-families.mjs";
|
|
19
|
+
import { reviewCandidateWatchInput, parseCandidateWatchInput } from "./watchlist-review.mjs";
|
|
20
|
+
import { readDomainFeedFile } from "./domain-feed.mjs";
|
|
21
|
+
import { scanDomainFeed, buildDomainFeedWatchInput, normalizeDomainFeedSelection } from "../packages/monitoring/domain-feed.mjs";
|
|
22
|
+
import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
|
|
19
23
|
import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
|
|
24
|
+
async function runWatchlistReviewCommand(args, dependencies, context) {
|
|
25
|
+
const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source) : await context.readInput(args.source, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch selection');
|
|
26
|
+
const document = reviewCandidateWatchInput(raw, args.operation, context.now());
|
|
27
|
+
if (!args.quiet)
|
|
28
|
+
context.writeStdout(formatJsonDocument(document));
|
|
29
|
+
return EXIT_CODES.SUCCESS;
|
|
30
|
+
}
|
|
31
|
+
async function runDomainFeedCommand(args, dependencies, context) {
|
|
32
|
+
context.setFailureLabel('Offline domain-feed review');
|
|
33
|
+
const handoff = args.contextSource ? parseCandidateWatchInput(dependencies.readArtifactInput
|
|
34
|
+
? await dependencies.readArtifactInput(args.contextSource)
|
|
35
|
+
: await context.readInput(args.contextSource, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch context')) : null;
|
|
36
|
+
const watchSelection = handoff?.selection;
|
|
37
|
+
if (watchSelection && watchSelection.candidates.length)
|
|
38
|
+
throw new CliUsageError('The watch-input context must have an empty candidates array; only reviewed feed matches may be nominated.');
|
|
39
|
+
const selection = normalizeDomainFeedSelection({ ...args.selection, brandProfileId: watchSelection?.brandProfileId ?? null });
|
|
40
|
+
const review = await scanDomainFeed((dependencies.readDomainFeedInput ?? readDomainFeedFile)(args.source, dependencies.signal), {
|
|
41
|
+
feedId: args.feedId, selection, importedAt: context.now(), ...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
42
|
+
});
|
|
43
|
+
const document = watchSelection && handoff
|
|
44
|
+
? buildDomainFeedWatchInput(review, review.matches.map(match => match.domain), watchSelection, handoff.watchlists)
|
|
45
|
+
: review;
|
|
46
|
+
if (!args.quiet)
|
|
47
|
+
context.writeStdout(formatJsonDocument(document));
|
|
48
|
+
return EXIT_CODES.SUCCESS;
|
|
49
|
+
}
|
|
20
50
|
function boundWorkflowInputs(command, inputs, dependencies, context) {
|
|
21
51
|
if (!inputs.size)
|
|
22
52
|
return {};
|
|
@@ -181,6 +211,8 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
|
|
|
181
211
|
return investigationRunExitCode(document);
|
|
182
212
|
}
|
|
183
213
|
const WORKFLOW_COMMAND_HANDLERS = Object.freeze({
|
|
214
|
+
'domain-feed': runDomainFeedCommand,
|
|
215
|
+
'watchlist-review': runWatchlistReviewCommand,
|
|
184
216
|
'monitor-once': runMonitorOnceCommand,
|
|
185
217
|
'workflow-plan': runWorkflowPlanCommand,
|
|
186
218
|
'workflow-run': runWorkflowRecipeCommand,
|
package/docs/cli.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# WHOISleuth CLI guide
|
|
2
2
|
|
|
3
|
+
`review-evidence` also reviews bounded source-qualified infrastructure snapshots and explicit earlier/later comparison inputs entirely offline. See [infrastructure observations](infrastructure-observations.md) for the exact schemas, outcome semantics and retained-evidence limitations.
|
|
4
|
+
|
|
3
5
|
The first-party CLI runs on the operator's machine and does not call the hosted
|
|
4
6
|
WHOISleuth deployment. Use this guide for installation, common commands,
|
|
5
7
|
collection boundaries and output. Installed `whoisleuth --help`, focused
|
|
@@ -106,6 +108,18 @@ One strict domain, IP address or ASN can occupy command position as Lookup
|
|
|
106
108
|
shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
|
|
107
109
|
Only `bulk` accepts multiple targets.
|
|
108
110
|
|
|
111
|
+
For a downloaded plain-domain feed, use
|
|
112
|
+
`whoisleuth domain-feed review nrd7 feed.txt --select term:example --json`.
|
|
113
|
+
Repeat `--select exclude:literal` to veto a literal substring before the result
|
|
114
|
+
bound, including for exact-host selections. At most 20 positive and 20 negative
|
|
115
|
+
terms of 3–80 characters are supported; at least one positive term or exact host
|
|
116
|
+
is required. Matching uses canonical ASCII/punycode text without token, typo,
|
|
117
|
+
confusable or regular-expression expansion. These offline selectors do not infer
|
|
118
|
+
saved browser campaign identity or default priority.
|
|
119
|
+
This is an offline literal match, not a feed download or target Lookup. The
|
|
120
|
+
[candidate-review guide](https://github.com/slicedearth/whoisleuth/blob/main/docs/brand-candidate-review.md#review-a-domain-feed) covers
|
|
121
|
+
source attribution and the separate Watchlist handoff.
|
|
122
|
+
|
|
109
123
|
`discover` accepts a brand label or registrable domain and supports multi-part
|
|
110
124
|
public suffixes in `--tlds` (for example, `co.uk,com.au`). It preserves the full
|
|
111
125
|
suffix and does not collect candidate evidence; use `discover-scan` only after
|
|
@@ -179,6 +193,30 @@ Excluded links and unsupported QR payloads make the review partial, with
|
|
|
179
193
|
category counts rather than private payload text. Bare QR hostnames are not
|
|
180
194
|
automatically treated as URLs.
|
|
181
195
|
|
|
196
|
+
Current message-intake reports also retain bounded literal IPv4/IPv6 and
|
|
197
|
+
explicitly labelled MD5, SHA-1 and SHA-256 observations from supported body and
|
|
198
|
+
document text. Each observation links to its selected source part and digest;
|
|
199
|
+
it is not a Lookup target, file identification or threat verdict. Complete URLs,
|
|
200
|
+
message headers, QR payloads, HAR and identity-event fields are excluded from
|
|
201
|
+
this text scan. Historical version-1 reports do not acquire new observations.
|
|
202
|
+
|
|
203
|
+
Use `intake text selected.txt --intake-context context.json --json` to add an
|
|
204
|
+
explicit analyst-supplied distribution declaration. The separate context file
|
|
205
|
+
is limited to 8 KiB and cannot use stdin. Its channel, non-sensitive source
|
|
206
|
+
label and optional declared time, reference, observer and vantage labels are
|
|
207
|
+
included in the output, not verified as delivery or capture conditions. See
|
|
208
|
+
[selected-input indicators and declarations](offline-intake-context.md) for the
|
|
209
|
+
exact schema, privacy exclusions and extraction bounds.
|
|
210
|
+
|
|
211
|
+
Plain-text phone discovery reports only candidate counts, the source digest and
|
|
212
|
+
UTF-16 selection ranges to standard error; unselected numbers do not enter JSON
|
|
213
|
+
output. Use a version-2 `--intake-context` file to select exact ranges and declare
|
|
214
|
+
their source, basis, role and known time. The same optional file can supply a
|
|
215
|
+
displayed/destination pair for local hostname comparison. See the
|
|
216
|
+
[selection example](offline-intake-context.md#select-phone-observations-in-the-cli).
|
|
217
|
+
`--quiet` suppresses discovery guidance, and `--strict-exit` reports partial
|
|
218
|
+
phone coverage as well as other incomplete intake work.
|
|
219
|
+
|
|
182
220
|
### Local IP-location database review
|
|
183
221
|
|
|
184
222
|
`review-evidence query.json --mmdb selected.mmdb --json --strict-exit` reads
|
|
@@ -233,6 +271,21 @@ comparison and connector provenance. These workflows are offline; partial
|
|
|
233
271
|
context reviews return 4 when `--strict-exit` is selected. Browser-generated
|
|
234
272
|
reusable inputs use the same validators.
|
|
235
273
|
|
|
274
|
+
Domain-history input version 2 adds analyst-declared registration, deletion,
|
|
275
|
+
re-registration, transfer or review boundaries linked to retained snapshot or
|
|
276
|
+
pin IDs. Version 1 remains supported with its original output. Boundaries prompt
|
|
277
|
+
reassessment; they do not establish ownership or discard prior evidence.
|
|
278
|
+
|
|
279
|
+
`review-evidence containment.json --json --strict-exit` accepts a
|
|
280
|
+
`whoisleuth.internal-containment.input` version-1 document containing a supported
|
|
281
|
+
Case export, selected next-step assertion and linked pin IDs, internal or trusted
|
|
282
|
+
audience, recipient role and an explicit disclosure-review acknowledgement.
|
|
283
|
+
It exports only that selected handoff, never writes the Case or performs a
|
|
284
|
+
control. Missing or unselected supporting context stays partial (exit 4 under
|
|
285
|
+
`--strict-exit`), and a resolved Case or provider outcome does not close an open
|
|
286
|
+
internal follow-up. See [internal containment](offline-intake-context.md#selected-internal-containment-handoff)
|
|
287
|
+
for the complete input and audience limitations.
|
|
288
|
+
|
|
236
289
|
### Message-header review
|
|
237
290
|
|
|
238
291
|
`mail-headers` parses only the bounded header block from a selected message file
|
|
@@ -284,6 +337,45 @@ selected file, including an envelope when supplied. No provider API is contacted
|
|
|
284
337
|
|
|
285
338
|
### Local Case files
|
|
286
339
|
|
|
340
|
+
Manual response scope is explicit and offline. `case incident-link` takes
|
|
341
|
+
`{"url":"https://example.test/reported-page"}`; use its retained ID to bind
|
|
342
|
+
`responseObjects` on `case action`. Each object contains `kind`, `identifier`
|
|
343
|
+
and `incidentTargetId` (null for the Case domain or an associated hostname).
|
|
344
|
+
New URL objects must match that exact retained link. Exact historical identities
|
|
345
|
+
already retained in response records remain selectable after a link edit or
|
|
346
|
+
removal; they are not rebound to its replacement. An action may bind up to 20
|
|
347
|
+
objects; missing historical scope stays unknown.
|
|
348
|
+
|
|
349
|
+
`case action-event` takes `{"id":"action-id","transition":{...}}`, using
|
|
350
|
+
the same reviewed, authorised and manually submitted workflow as Console.
|
|
351
|
+
After submission, a typed `objectOutcome` such as `removed`, `restricted`,
|
|
352
|
+
`suspended`, `delisted`, `restored` or `disputed` requires explicit event
|
|
353
|
+
`responseObjects`: a partial receipt never applies to all objects by default.
|
|
354
|
+
Acknowledgement is a workflow event, not independently observed remediation.
|
|
355
|
+
|
|
356
|
+
A self-initiated correction uses a new `case action`, with `originActionId`
|
|
357
|
+
identifying the original action and `responseObjects` selecting a subset of that
|
|
358
|
+
delivery's exact objects. Its `correction` block identifies one delivery event,
|
|
359
|
+
packet digest/version and profile, plus the purpose (`correction` or
|
|
360
|
+
`retraction_request`), reason, previous and corrected statements, and retained
|
|
361
|
+
evidence-pin IDs. This is separate from `amendment`, which still requires an
|
|
362
|
+
actual provider-request event. The correction must match a retained typed packet
|
|
363
|
+
receipt on the selected delivery; a historical free-text digest alone is
|
|
364
|
+
insufficient. The usual recipient, authority and disclosure review then applies
|
|
365
|
+
to the new draft. The original packet and delivery remain unchanged.
|
|
366
|
+
|
|
367
|
+
`case recheck-question` saves `statement` and `recheck` comparison conditions.
|
|
368
|
+
Pins, questions and recheck input can retain one `responseObject`; exact-object
|
|
369
|
+
non-reproduction requires matching complete baseline/current evidence, a later
|
|
370
|
+
observation and comparable conditions. Hostname similarity is insufficient.
|
|
371
|
+
Independent removal, restriction, suspension, delisting, transfer and restoration
|
|
372
|
+
use those same comparison requirements. Disputes and warnings retain procedural
|
|
373
|
+
source attribution separately; provider claims are not independent observations.
|
|
374
|
+
`case close-object` requires one `responseObject` and the usual typed closure
|
|
375
|
+
reason/evidence links. It leaves the Case and other objects unchanged.
|
|
376
|
+
All these JSON operations require `--input`, `--output`, deliberate file
|
|
377
|
+
replacement and the existing digest/lease checks. No report or request is sent.
|
|
378
|
+
|
|
287
379
|
Create a working file, inspect its Case IDs, then append a note:
|
|
288
380
|
|
|
289
381
|
```sh
|
|
@@ -395,12 +487,17 @@ An ordinary Case JSON file alone provides structural validity, not a checksum.
|
|
|
395
487
|
They use generated entry names, not original paths. Ordinary ZIPs and folders
|
|
396
488
|
are private and unencrypted; packaging does not redact selected files. The report distinguishes
|
|
397
489
|
file identity, supported source formats, opaque content, exact capsule/source
|
|
398
|
-
links and capture-manifest attachment matches.
|
|
490
|
+
links, declared image derivation and capture-manifest attachment matches.
|
|
491
|
+
Image derivation reports an immediate-parent digest and byte length, editing
|
|
492
|
+
method and operation kinds, not proof of transformation. Parent inclusion matches
|
|
493
|
+
another selected manifest entry by exact byte digest and size; an unselected Case
|
|
494
|
+
attachment reference does not count. Parent pixels are optional; absent declarations
|
|
495
|
+
leave editing history unknown. Include the capture manifest and
|
|
399
496
|
its screenshot/DOM-digest files together to check their declared bytes; original
|
|
400
497
|
filenames are not needed to establish a match. It does not import files or establish source truth, signature trust or a
|
|
401
498
|
trusted timestamp. Unsupported or rejected entries produce a partial report;
|
|
402
499
|
`--strict-exit` returns 4. Without `--package` or `--folder`, `manifest` produces a
|
|
403
|
-
standalone JSON manifest; exact
|
|
500
|
+
standalone JSON manifest; exact version-2 and version-3 manifests remain readable.
|
|
404
501
|
|
|
405
502
|
For [BagIt 1.0](https://www.rfc-editor.org/rfc/rfc8493.html) interchange, use
|
|
406
503
|
`--bagit` with `--package` or `--folder` on both commands:
|
|
@@ -17,8 +17,9 @@ The CLI uses the same models:
|
|
|
17
17
|
whoisleuth review-evidence selected-input.json --json --strict-exit
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Each input has `schema`, `version
|
|
21
|
-
|
|
20
|
+
Each input has `schema`, a supported `version` and `evidence`. Most inputs use
|
|
21
|
+
version 1; domain history also supports version 2 as described below. Unknown
|
|
22
|
+
versions and fields are rejected. Inputs are bounded to 16 MiB; each supplied context list permits
|
|
22
23
|
200 records. `--strict-exit` returns 4 for a partial contextual review. Neither
|
|
23
24
|
adapter contacts a destination or executes selected software.
|
|
24
25
|
|
|
@@ -57,8 +58,12 @@ later. These stages never execute a command, submit a form or open a destination
|
|
|
57
58
|
|
|
58
59
|
The Case view compares retained snapshots using the existing source, hostname,
|
|
59
60
|
scan-depth and model-version rules. Registration, DNS, mail and web changes are
|
|
60
|
-
shown alongside source-qualified certificate pins.
|
|
61
|
-
|
|
61
|
+
shown alongside source-qualified certificate pins.
|
|
62
|
+
Registration and nameserver changes target the Case registration domain; mail
|
|
63
|
+
and web changes target the explicit observation hostname. Certificate pins keep
|
|
64
|
+
their own hostname, or an unknown target when none was retained. These targets
|
|
65
|
+
are preserved in review downloads, retained reports and print previews.
|
|
66
|
+
Equal-time observations are not ordered into a change sequence. Retired dependencies must involve the Case
|
|
62
67
|
domain or one of its hostnames. An expected window qualifies the review; it does
|
|
63
68
|
not approve a change automatically or establish transfer or takeover.
|
|
64
69
|
Review prompts connect comparable page, mail, delegation and registration changes
|
|
@@ -71,7 +76,8 @@ Schema: `whoisleuth.domain-history.input`. Its `evidence` contains:
|
|
|
71
76
|
- `caseExport`: an ordinary supported editable Case export, with `version` and
|
|
72
77
|
`cases`; the exact existing Case reader rejects data loss or silent repair.
|
|
73
78
|
- `caseId`: the Case selected from that export.
|
|
74
|
-
- `declarations`: `expectedChanges` and `retiredDependencies` arrays
|
|
79
|
+
- `declarations`: `expectedChanges` and `retiredDependencies` arrays; version 2
|
|
80
|
+
also requires `registrationBoundaries`. Version 1 remains readable unchanged.
|
|
75
81
|
|
|
76
82
|
An expected change is `{family, start, end, reason}`. A retired dependency is
|
|
77
83
|
`{asset, dependency, family, retiredAt, source}`. Families are `registration`,
|
|
@@ -79,6 +85,23 @@ An expected change is `{family, start, end, reason}`. A retired dependency is
|
|
|
79
85
|
reusable input download includes the selected Case and is not duplicated in
|
|
80
86
|
workspace storage when saving the report.
|
|
81
87
|
|
|
88
|
+
Version-2 registration boundaries are analyst declarations, not confirmed
|
|
89
|
+
registration epochs. Each has `{kind, occurredAt, source, rationale,
|
|
90
|
+
snapshotIds, evidencePinIds}`. Kinds are `registration_reported`,
|
|
91
|
+
`deletion_reported`, `reregistration_reported`, `transfer_reported` and
|
|
92
|
+
`review_boundary`. Supply an explicit ISO time and at least one exact reference
|
|
93
|
+
to a snapshot or evidence pin retained in the selected Case. References must be
|
|
94
|
+
unique; unavailable references fail rather than selecting a replacement.
|
|
95
|
+
Up to 40 boundaries and 20 references of each type per boundary are accepted.
|
|
96
|
+
|
|
97
|
+
The Case history form exposes the same declarations. Reports count retained
|
|
98
|
+
observations before and at/after a declared time without assigning ownership or
|
|
99
|
+
asserting that deletion, re-registration or transfer occurred. Earlier evidence,
|
|
100
|
+
decisions, exceptions and open follow-ups are not rewritten. Reports retain the
|
|
101
|
+
declared source and exact evidence references, and can be deliberately saved as
|
|
102
|
+
ordinary Case files. The reusable input remains a separate download containing
|
|
103
|
+
the selected Case, not an additional workspace copy.
|
|
104
|
+
|
|
82
105
|
## Platform objects
|
|
83
106
|
|
|
84
107
|
Schema: `whoisleuth.platform-continuity.input`. `evidence` is an array:
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Source-qualified infrastructure snapshots
|
|
2
|
+
|
|
3
|
+
The retained infrastructure inventory combines admitted browser-local evidence,
|
|
4
|
+
not all infrastructure belonging to an organisation. It makes no requests on
|
|
5
|
+
import, opening a view, filtering, selecting a host or comparing history.
|
|
6
|
+
|
|
7
|
+
Import a `whoisleuth.infrastructure-observation` version 1 document through the
|
|
8
|
+
existing external-findings import. The exact bounded snapshot is retained as one
|
|
9
|
+
Case evidence pin. Review the preview before confirming the mutation. The source
|
|
10
|
+
descriptions are supplied provenance, not authenticated source identities.
|
|
11
|
+
Legacy DNS and certificate imports remain readable: an old Case-domain DNS pin
|
|
12
|
+
does not acquire a precise queried hostname or complete collection scope.
|
|
13
|
+
|
|
14
|
+
The document records an explicit target, selected full hostnames and DNS types,
|
|
15
|
+
collection mode, observation time and coverage. Each DNS response retains its
|
|
16
|
+
queried name, response owner, type, outcome and source. An alias link uses its
|
|
17
|
+
record owner; an address returned after an alias belongs to the returned address
|
|
18
|
+
owner, not automatically to the initial query. Only retained alias links are
|
|
19
|
+
drawn. Links from different dates do not prove one contemporaneous alias chain.
|
|
20
|
+
No-data, NXDOMAIN, failed and not-checked outcomes remain different observations
|
|
21
|
+
and do not decide registration availability.
|
|
22
|
+
|
|
23
|
+
Certificate-log and TLS observations keep distinct sources and exact SHA-256
|
|
24
|
+
identities. Full names remain visible; `*.example.test` is a wildcard pattern,
|
|
25
|
+
not an enumerated hostname, and cannot be selected for Lookup or Bulk.
|
|
26
|
+
Certificate names alone do not establish current resolution or activity.
|
|
27
|
+
|
|
28
|
+
DNS operator, observed edge/CDN/proxy, application platform, runtime, embedded
|
|
29
|
+
dependency, address registration, routing origin and independently observed
|
|
30
|
+
origin are distinct source-qualified roles. Routing requires a routing source;
|
|
31
|
+
IP-registration data cannot substitute for it. Edge indicators cannot identify
|
|
32
|
+
a hidden origin. Multiple providers and conflicting observations remain separate.
|
|
33
|
+
The snapshot reader does not derive physical location, responsibility, ownership,
|
|
34
|
+
safety or maliciousness from these roles.
|
|
35
|
+
|
|
36
|
+
## Review and deliberately act
|
|
37
|
+
|
|
38
|
+
Open the inventory from saved-work search. Type, source and date filters apply to
|
|
39
|
+
the admitted set before pagination. Inspect one snapshot to review every retained
|
|
40
|
+
DNS outcome, certificate name and provider role, or select two snapshots in
|
|
41
|
+
chronological order to compare. Export preserves the exact snapshot format.
|
|
42
|
+
Topology search applies to the complete admitted one-hop relationship set, not
|
|
43
|
+
the current source page. Its diagram has a 50-relationship budget; the paginated
|
|
44
|
+
source list and exact snapshot review retain access to evidence beyond the diagram.
|
|
45
|
+
|
|
46
|
+
Lookup links fill the selected target for review without submitting it. Explicitly
|
|
47
|
+
selected hostnames can be prepared in existing Bulk. That handoff does not scan,
|
|
48
|
+
recursively expand targets, change a watchlist or enrol a host in monitoring.
|
|
49
|
+
Use the existing reviewed Lookup/Bulk/Monitor collection plans and explicit
|
|
50
|
+
save actions to collect or retain another observation.
|
|
51
|
+
|
|
52
|
+
## Historical comparison and CLI
|
|
53
|
+
|
|
54
|
+
Source, mode and exact selection scope must match, and the later snapshot must
|
|
55
|
+
have a strictly later observation time. Newly observed means newly present in
|
|
56
|
+
retained evidence, not newly created. A value not returned by a complete,
|
|
57
|
+
comparable selected collection is labelled **not returned**, never confirmed
|
|
58
|
+
absence or disappearance. Failed, partial, limited, changed-source and
|
|
59
|
+
incomparable snapshots cannot generate removal findings. Equal-time observations
|
|
60
|
+
have no temporal order. Provider-reported history stays labelled separately from
|
|
61
|
+
supplied local observations.
|
|
62
|
+
|
|
63
|
+
Comparison rows retain the exact DNS query, response owner and record type, or
|
|
64
|
+
the certificate SHA-256, alongside their source and observation clocks. Labels
|
|
65
|
+
are presentation only: relationship groups and graph exports use canonical
|
|
66
|
+
entity identities, including when long nameserver-set labels look identical.
|
|
67
|
+
|
|
68
|
+
Derived relationship-group, graph-node and cluster IDs can change when their
|
|
69
|
+
identity calculation changes, including for short labels. They are not durable
|
|
70
|
+
cross-export keys; use the canonical `entityId` where provided. Historical groups
|
|
71
|
+
without it use their complete retained value. Graph fields named `canonical`
|
|
72
|
+
and `value` are bounded display text, not a replacement for that identity. Inspect
|
|
73
|
+
the contributing Case evidence for full nameserver members. Component selections
|
|
74
|
+
and cluster adjustments are transient and are not persisted review history.
|
|
75
|
+
|
|
76
|
+
In GraphML and GEXF, the display label for `node_canonical` is now
|
|
77
|
+
`Bounded display value` (previously `Canonical value`). The field key, JSON
|
|
78
|
+
property, entity identities and values are unchanged; XML consumers should use
|
|
79
|
+
the field key rather than its display label.
|
|
80
|
+
|
|
81
|
+
The existing offline command shares the website reader and comparison owner:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
whoisleuth review-evidence infrastructure-observation.json --json
|
|
85
|
+
whoisleuth review-evidence infrastructure-comparison.json --json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The comparison input is exactly:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"schema": "whoisleuth.infrastructure-comparison.input",
|
|
93
|
+
"version": 1,
|
|
94
|
+
"earlier": { "...": "complete version-1 snapshot" },
|
|
95
|
+
"later": { "...": "complete version-1 snapshot" }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The placeholders above are illustrative, not valid evidence. See the reserved
|
|
100
|
+
fixture in `test/fixtures/infrastructure-observations/` for an exact snapshot.
|
|
101
|
+
The CLI performs no DNS, registry, BGP, TLS, HTTP or provider request.
|
|
102
|
+
|
|
103
|
+
Changed or incomplete latest retained pairs surface as existing local Review
|
|
104
|
+
Items with supporting Case navigation. Complete earlier snapshots remain
|
|
105
|
+
available as stronger baselines; later failures do not rewrite them. There is no
|
|
106
|
+
new history store or scheduling engine.
|
|
107
|
+
|
|
108
|
+
## Resource and compatibility boundary
|
|
109
|
+
|
|
110
|
+
One snapshot is limited to 128 KiB, 128 selected hostnames, six DNS types, 16
|
|
111
|
+
source identities, 512 DNS response rows with at most 32 values each, 32
|
|
112
|
+
certificate observations with at most 128 exact names each, 128 role observations
|
|
113
|
+
and 12 bounded limitations. Bounds reject oversized documents rather than
|
|
114
|
+
silently prune accepted snapshot evidence. Existing Case/workspace quotas still
|
|
115
|
+
apply. Projection/diagram budgets may admit a smaller disposable view, with
|
|
116
|
+
incomplete coverage disclosed; exact snapshot review/export remains available.
|
|
117
|
+
|
|
118
|
+
Version 1 has exact keys and refuses future versions. Existing external-findings
|
|
119
|
+
version 4 and typed version-1 row imports preserve their historical meaning;
|
|
120
|
+
version 5 adds the optional exact infrastructure snapshot. No external discovery
|
|
121
|
+
API, new provider, dependency, collector or automatic request is required.
|
|
122
|
+
Current snapshot and version-5 findings exports use compact UTF-8 JSON with a
|
|
123
|
+
terminal line feed so the emitted representation stays within its parser ceiling.
|
|
124
|
+
An offline comparison input is bounded to two snapshot budgets plus 1 KiB of
|
|
125
|
+
framing. Historical version-4 fixture bytes and reader meaning remain unchanged;
|
|
126
|
+
new typed row conversions emit version 5.
|