@slicedearth/whoisleuth-cli 1.47.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +12 -4
- package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +70 -47
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
- package/third-party-notices.txt +14 -14
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -255
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// Compact provenance envelope for network-derived evidence. Type-specific
|
|
2
|
+
// payloads remain beside this object; the envelope only standardises source
|
|
3
|
+
// health, timing, completeness, truncation, and bounded limitations.
|
|
4
|
+
const OBSERVATION_VERSION = 1;
|
|
5
|
+
const STATUSES = new Set([
|
|
6
|
+
'success',
|
|
7
|
+
'partial',
|
|
8
|
+
'not_found',
|
|
9
|
+
'skipped',
|
|
10
|
+
'error',
|
|
11
|
+
'unsupported',
|
|
12
|
+
'not_applicable',
|
|
13
|
+
]);
|
|
14
|
+
const SCAN_MODES = new Set(['fast', 'deep']);
|
|
15
|
+
const MAX_OBSERVATION_LIMITATIONS = 10;
|
|
16
|
+
const MAX_OBSERVATION_LIMITATION_LENGTH = 300;
|
|
17
|
+
const MAX_OBSERVATION_DIAGNOSTICS = 20;
|
|
18
|
+
const MAX_DIAGNOSTICS = MAX_OBSERVATION_DIAGNOSTICS;
|
|
19
|
+
const MAX_DIAGNOSTIC_KEY = 40;
|
|
20
|
+
const MAX_DIAGNOSTIC_STRING = 240;
|
|
21
|
+
const MAX_DURATION_MS = 120_000;
|
|
22
|
+
const MAX_OBSERVATION_INPUT_LIMITATIONS = MAX_OBSERVATION_LIMITATIONS * 4;
|
|
23
|
+
const MAX_DIAGNOSTIC_DEPTH = 4;
|
|
24
|
+
const MAX_DIAGNOSTIC_VALUES_PER_ENTRY = 32;
|
|
25
|
+
const OBSERVATION_INPUT_FIELDS = [
|
|
26
|
+
'version',
|
|
27
|
+
'status',
|
|
28
|
+
'observedAt',
|
|
29
|
+
'scanMode',
|
|
30
|
+
'source',
|
|
31
|
+
'durationMs',
|
|
32
|
+
'complete',
|
|
33
|
+
'truncated',
|
|
34
|
+
'limitations',
|
|
35
|
+
'diagnostics',
|
|
36
|
+
];
|
|
37
|
+
// Observation v1 diagnostics use a closed, source-owned vocabulary. Reading
|
|
38
|
+
// only these descriptors keeps normalisation independent of an untrusted
|
|
39
|
+
// object's total key count and preserves the deployed producer fields.
|
|
40
|
+
const REGISTERED_DIAGNOSTIC_KEYS = [
|
|
41
|
+
'a',
|
|
42
|
+
'aaaa',
|
|
43
|
+
'addressSource',
|
|
44
|
+
'advisoryMatches',
|
|
45
|
+
'arrayItemsExamined',
|
|
46
|
+
'attemptCount',
|
|
47
|
+
'authorityCount',
|
|
48
|
+
'caa',
|
|
49
|
+
'caa_policy',
|
|
50
|
+
'catalogComponents',
|
|
51
|
+
'certificateGroups',
|
|
52
|
+
'certificateGroupsTruncated',
|
|
53
|
+
'certificateRows',
|
|
54
|
+
'charactersExamined',
|
|
55
|
+
'cidrCount',
|
|
56
|
+
'classifiedInputs',
|
|
57
|
+
'cname',
|
|
58
|
+
'connectionAttempts',
|
|
59
|
+
'delegation',
|
|
60
|
+
'discarded',
|
|
61
|
+
'discardedAuthorityRecordValueCount',
|
|
62
|
+
'discardedFields',
|
|
63
|
+
'discardedProperties',
|
|
64
|
+
'discardedUrls',
|
|
65
|
+
'discarded_attributes',
|
|
66
|
+
'discarded_entities',
|
|
67
|
+
'discarded_metadata',
|
|
68
|
+
'discarded_relationships',
|
|
69
|
+
'dmarc',
|
|
70
|
+
'documentsParsed',
|
|
71
|
+
'entities',
|
|
72
|
+
'error',
|
|
73
|
+
'externalScriptsSkipped',
|
|
74
|
+
'findings',
|
|
75
|
+
'formsObserved',
|
|
76
|
+
'generatorEvaluated',
|
|
77
|
+
'htmlEvaluated',
|
|
78
|
+
'httpStatus',
|
|
79
|
+
'https',
|
|
80
|
+
'ignoredCount',
|
|
81
|
+
'indicatorsObserved',
|
|
82
|
+
'inlineCharactersExamined',
|
|
83
|
+
'inlineScriptsExamined',
|
|
84
|
+
'inlineSignatureCharactersExamined',
|
|
85
|
+
'inlineSignatureTimedOut',
|
|
86
|
+
'inlineSignatureUnavailable',
|
|
87
|
+
'inputsObserved',
|
|
88
|
+
'knownExploitedMatches',
|
|
89
|
+
'lameAuthorityCount',
|
|
90
|
+
'malformedCount',
|
|
91
|
+
'malformedScripts',
|
|
92
|
+
'matches',
|
|
93
|
+
'mx',
|
|
94
|
+
'namesExamined',
|
|
95
|
+
'ns',
|
|
96
|
+
'objectsExamined',
|
|
97
|
+
'observed',
|
|
98
|
+
'observedAbsence',
|
|
99
|
+
'partialAuthorityCount',
|
|
100
|
+
'passiveHeadersEvaluated',
|
|
101
|
+
'potentialExposure',
|
|
102
|
+
'ptr',
|
|
103
|
+
'queriedAddressCount',
|
|
104
|
+
'redirectCount',
|
|
105
|
+
'referencesExamined',
|
|
106
|
+
'rejectedRows',
|
|
107
|
+
'relationshipTagsExamined',
|
|
108
|
+
'relationshipUrlsDiscarded',
|
|
109
|
+
'relationships',
|
|
110
|
+
'requestCount',
|
|
111
|
+
'resolvedAddressCount',
|
|
112
|
+
'resourceOriginsEvaluated',
|
|
113
|
+
'rolesObserved',
|
|
114
|
+
'scriptElementsExamined',
|
|
115
|
+
'scriptsExamined',
|
|
116
|
+
'scriptsObserved',
|
|
117
|
+
'serverEvaluated',
|
|
118
|
+
'soa',
|
|
119
|
+
'spf',
|
|
120
|
+
'successfulAuthorityCount',
|
|
121
|
+
'tagLimitReached',
|
|
122
|
+
'tagsExamined',
|
|
123
|
+
'trackingIdentifiersTruncated',
|
|
124
|
+
'tree',
|
|
125
|
+
'truncatedAuthorityRecordSetCount',
|
|
126
|
+
'truncatedNames',
|
|
127
|
+
'truncatedSameAsLists',
|
|
128
|
+
'truncatedTypeLists',
|
|
129
|
+
'unavailable',
|
|
130
|
+
'unclassifiedActions',
|
|
131
|
+
'unreachableAuthorityCount',
|
|
132
|
+
'workTruncated',
|
|
133
|
+
];
|
|
134
|
+
const ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,43})?(Z|[+-]\d{2}:\d{2})$/iu;
|
|
135
|
+
const LEGACY_ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,43})?(Z|[+-]\d{2}:\d{2})?$/iu;
|
|
136
|
+
const CT_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(\.\d{1,43})?(Z|[+-]\d{2}:\d{2})?$/iu;
|
|
137
|
+
function isObservationStatus(value) {
|
|
138
|
+
return typeof value === 'string' && STATUSES.has(value);
|
|
139
|
+
}
|
|
140
|
+
function isScanMode(value) {
|
|
141
|
+
return typeof value === 'string' && SCAN_MODES.has(value);
|
|
142
|
+
}
|
|
143
|
+
function safeString(value, maxLength) {
|
|
144
|
+
if (typeof value !== 'string' || value.length > maxLength * 4)
|
|
145
|
+
return null;
|
|
146
|
+
const normalised = value.trim().replace(/[\u0000-\u001f\u007f]/g, ' ');
|
|
147
|
+
return normalised ? normalised.slice(0, maxLength) : null;
|
|
148
|
+
}
|
|
149
|
+
function ownDataValue(value, key) {
|
|
150
|
+
try {
|
|
151
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
152
|
+
return descriptor && 'value' in descriptor ? descriptor.value : undefined;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function snapshotObservationInput(value) {
|
|
159
|
+
const snapshot = {};
|
|
160
|
+
for (const field of OBSERVATION_INPUT_FIELDS) {
|
|
161
|
+
const fieldValue = ownDataValue(value, field);
|
|
162
|
+
if (fieldValue === undefined)
|
|
163
|
+
continue;
|
|
164
|
+
Object.defineProperty(snapshot, field, {
|
|
165
|
+
configurable: true,
|
|
166
|
+
enumerable: true,
|
|
167
|
+
value: fieldValue,
|
|
168
|
+
writable: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return snapshot;
|
|
172
|
+
}
|
|
173
|
+
function safelyIsArray(value) {
|
|
174
|
+
try {
|
|
175
|
+
return Array.isArray(value);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function boundedArrayDataValues(value, maximum) {
|
|
182
|
+
if (!safelyIsArray(value))
|
|
183
|
+
return [];
|
|
184
|
+
const output = [];
|
|
185
|
+
const rawLength = ownDataValue(value, 'length');
|
|
186
|
+
const length = typeof rawLength === 'number' && Number.isSafeInteger(rawLength) && rawLength >= 0
|
|
187
|
+
? Math.min(rawLength, maximum)
|
|
188
|
+
: 0;
|
|
189
|
+
for (let index = 0; index < length; index += 1) {
|
|
190
|
+
const item = ownDataValue(value, String(index));
|
|
191
|
+
if (item !== undefined)
|
|
192
|
+
output.push(item);
|
|
193
|
+
}
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
function validCalendarParts(parts) {
|
|
197
|
+
const year = Number(parts[1]);
|
|
198
|
+
const month = Number(parts[2]);
|
|
199
|
+
const day = Number(parts[3]);
|
|
200
|
+
const hour = Number(parts[4]);
|
|
201
|
+
const minute = Number(parts[5]);
|
|
202
|
+
const second = Number(parts[6]);
|
|
203
|
+
if (year < 1 || month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59)
|
|
204
|
+
return false;
|
|
205
|
+
const date = new Date(0);
|
|
206
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
207
|
+
date.setUTCHours(hour, minute, second, 0);
|
|
208
|
+
return date.getUTCFullYear() === year
|
|
209
|
+
&& date.getUTCMonth() === month - 1
|
|
210
|
+
&& date.getUTCDate() === day
|
|
211
|
+
&& date.getUTCHours() === hour
|
|
212
|
+
&& date.getUTCMinutes() === minute
|
|
213
|
+
&& date.getUTCSeconds() === second;
|
|
214
|
+
}
|
|
215
|
+
function canonicalTimestamp(value, expression, assignUtcWhenMissing) {
|
|
216
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 64 || /[\u0000-\u001f\u007f]/u.test(value))
|
|
217
|
+
return null;
|
|
218
|
+
const match = expression.exec(value);
|
|
219
|
+
if (!match || !validCalendarParts(match))
|
|
220
|
+
return null;
|
|
221
|
+
const zone = match[8] || (assignUtcWhenMissing ? 'Z' : '');
|
|
222
|
+
if (!zone)
|
|
223
|
+
return null;
|
|
224
|
+
if (zone !== 'Z' && zone.toUpperCase() !== 'Z') {
|
|
225
|
+
const hours = Number(zone.slice(1, 3));
|
|
226
|
+
const minutes = Number(zone.slice(4, 6));
|
|
227
|
+
if (hours > 23 || minutes > 59)
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const normalised = `${match[1]}-${match[2]}-${match[3]}T${match[4]}:${match[5]}:${match[6]}${match[7] || ''}${zone.toUpperCase() === 'Z' ? 'Z' : zone}`;
|
|
231
|
+
const timestamp = Date.parse(normalised);
|
|
232
|
+
if (!Number.isFinite(timestamp))
|
|
233
|
+
return null;
|
|
234
|
+
const date = new Date(timestamp);
|
|
235
|
+
const utcYear = date.getUTCFullYear();
|
|
236
|
+
return utcYear >= 1 && utcYear <= 9_999 ? date.toISOString() : null;
|
|
237
|
+
}
|
|
238
|
+
function normalizeExplicitIsoTimestamp(value) {
|
|
239
|
+
return canonicalTimestamp(value, ISO_DATE_TIME_RE, false);
|
|
240
|
+
}
|
|
241
|
+
// Frozen legacy schemas accepted zone-less ISO date-times. Preserve that
|
|
242
|
+
// migration path deterministically by assigning UTC instead of consulting the
|
|
243
|
+
// host timezone. Current schemas must use normalizeExplicitIsoTimestamp.
|
|
244
|
+
function normalizeLegacyIsoTimestamp(value) {
|
|
245
|
+
return canonicalTimestamp(value, LEGACY_ISO_DATE_TIME_RE, true);
|
|
246
|
+
}
|
|
247
|
+
function normalizeCtTimestamp(value) {
|
|
248
|
+
return canonicalTimestamp(value, CT_DATE_TIME_RE, true);
|
|
249
|
+
}
|
|
250
|
+
function isoTimestamp(value) {
|
|
251
|
+
return normalizeExplicitIsoTimestamp(value);
|
|
252
|
+
}
|
|
253
|
+
function normalizeDiagnosticValue(value, budget, depth = 0) {
|
|
254
|
+
budget.remaining -= 1;
|
|
255
|
+
if (budget.remaining < 0) {
|
|
256
|
+
budget.exhausted = true;
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
if (depth > MAX_DIAGNOSTIC_DEPTH)
|
|
260
|
+
return null;
|
|
261
|
+
if (typeof value === 'boolean')
|
|
262
|
+
return value;
|
|
263
|
+
if (typeof value === 'number')
|
|
264
|
+
return Number.isFinite(value) ? value : null;
|
|
265
|
+
if (typeof value === 'string')
|
|
266
|
+
return safeString(value, MAX_DIAGNOSTIC_STRING);
|
|
267
|
+
if (!value || typeof value !== 'object' || safelyIsArray(value))
|
|
268
|
+
return null;
|
|
269
|
+
const objectValue = value;
|
|
270
|
+
if (budget.active.has(objectValue))
|
|
271
|
+
return null;
|
|
272
|
+
const record = value;
|
|
273
|
+
const output = {};
|
|
274
|
+
budget.active.add(objectValue);
|
|
275
|
+
try {
|
|
276
|
+
for (const field of ['status', 'error', 'detail', 'truncated', 'discarded', 'count']) {
|
|
277
|
+
const fieldValue = ownDataValue(record, field);
|
|
278
|
+
if (fieldValue === undefined)
|
|
279
|
+
continue;
|
|
280
|
+
const normalised = normalizeDiagnosticValue(fieldValue, budget, depth + 1);
|
|
281
|
+
if (normalised !== null) {
|
|
282
|
+
Object.defineProperty(output, field, {
|
|
283
|
+
configurable: true,
|
|
284
|
+
enumerable: true,
|
|
285
|
+
value: normalised,
|
|
286
|
+
writable: true,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
budget.active.delete(objectValue);
|
|
293
|
+
}
|
|
294
|
+
return Object.keys(output).length ? output : null;
|
|
295
|
+
}
|
|
296
|
+
function normalizeDiagnostics(value) {
|
|
297
|
+
if (!value || typeof value !== 'object' || safelyIsArray(value))
|
|
298
|
+
return {};
|
|
299
|
+
const record = value;
|
|
300
|
+
const output = {};
|
|
301
|
+
let retained = 0;
|
|
302
|
+
for (const key of REGISTERED_DIAGNOSTIC_KEYS) {
|
|
303
|
+
if (retained >= MAX_DIAGNOSTICS)
|
|
304
|
+
break;
|
|
305
|
+
if (key.length > MAX_DIAGNOSTIC_KEY)
|
|
306
|
+
continue;
|
|
307
|
+
const entryValue = ownDataValue(record, key);
|
|
308
|
+
if (entryValue === undefined)
|
|
309
|
+
continue;
|
|
310
|
+
const budget = {
|
|
311
|
+
remaining: MAX_DIAGNOSTIC_VALUES_PER_ENTRY,
|
|
312
|
+
active: new Set(),
|
|
313
|
+
exhausted: false,
|
|
314
|
+
};
|
|
315
|
+
const normalised = normalizeDiagnosticValue(entryValue, budget);
|
|
316
|
+
if (normalised !== null && !budget.exhausted) {
|
|
317
|
+
Object.defineProperty(output, key, {
|
|
318
|
+
configurable: true,
|
|
319
|
+
enumerable: true,
|
|
320
|
+
value: normalised,
|
|
321
|
+
writable: true,
|
|
322
|
+
});
|
|
323
|
+
retained += 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return output;
|
|
327
|
+
}
|
|
328
|
+
function createObservationFromSnapshot(record) {
|
|
329
|
+
const rawStatus = record.status;
|
|
330
|
+
const rawSource = record.source;
|
|
331
|
+
const rawObservedAt = record.observedAt;
|
|
332
|
+
const rawDuration = record.durationMs;
|
|
333
|
+
const rawScanMode = record.scanMode;
|
|
334
|
+
const status = isObservationStatus(rawStatus) ? rawStatus : 'error';
|
|
335
|
+
const source = safeString(rawSource, 40) || 'unknown';
|
|
336
|
+
const observedAt = isoTimestamp(rawObservedAt) || new Date().toISOString();
|
|
337
|
+
const duration = typeof rawDuration === 'number' ? rawDuration : Number.NaN;
|
|
338
|
+
const limitations = [...new Set(boundedArrayDataValues(record.limitations, MAX_OBSERVATION_INPUT_LIMITATIONS)
|
|
339
|
+
.map((item) => safeString(item, MAX_OBSERVATION_LIMITATION_LENGTH))
|
|
340
|
+
.filter((item) => item !== null))]
|
|
341
|
+
.slice(0, MAX_OBSERVATION_LIMITATIONS);
|
|
342
|
+
return {
|
|
343
|
+
version: OBSERVATION_VERSION,
|
|
344
|
+
status,
|
|
345
|
+
observedAt,
|
|
346
|
+
scanMode: isScanMode(rawScanMode) ? rawScanMode : null,
|
|
347
|
+
source,
|
|
348
|
+
durationMs: Number.isFinite(duration) ? Math.max(0, Math.min(MAX_DURATION_MS, Math.round(duration))) : null,
|
|
349
|
+
complete: record.complete === true,
|
|
350
|
+
truncated: record.truncated === true,
|
|
351
|
+
limitations,
|
|
352
|
+
diagnostics: normalizeDiagnostics(record.diagnostics),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function createObservation(input = {}) {
|
|
356
|
+
const record = input && typeof input === 'object' && !safelyIsArray(input)
|
|
357
|
+
? snapshotObservationInput(input)
|
|
358
|
+
: {};
|
|
359
|
+
return createObservationFromSnapshot(record);
|
|
360
|
+
}
|
|
361
|
+
function readObservationEnvelope(value) {
|
|
362
|
+
if (value === undefined || value === null)
|
|
363
|
+
return { state: 'absent', observation: null };
|
|
364
|
+
if (!value || typeof value !== 'object' || safelyIsArray(value))
|
|
365
|
+
return { state: 'invalid', observation: null };
|
|
366
|
+
const record = snapshotObservationInput(value);
|
|
367
|
+
const version = record.version;
|
|
368
|
+
const status = record.status;
|
|
369
|
+
const source = record.source;
|
|
370
|
+
const observedAt = record.observedAt;
|
|
371
|
+
if (typeof version === 'number'
|
|
372
|
+
&& Number.isInteger(version)
|
|
373
|
+
&& version > OBSERVATION_VERSION) {
|
|
374
|
+
return { state: 'unsupported', observation: null };
|
|
375
|
+
}
|
|
376
|
+
if (version !== OBSERVATION_VERSION
|
|
377
|
+
|| !isObservationStatus(status)
|
|
378
|
+
|| !safeString(source, 40)
|
|
379
|
+
|| !isoTimestamp(observedAt)) {
|
|
380
|
+
return { state: 'invalid', observation: null };
|
|
381
|
+
}
|
|
382
|
+
return { state: 'supported', observation: createObservationFromSnapshot(record) };
|
|
383
|
+
}
|
|
384
|
+
export { MAX_OBSERVATION_DIAGNOSTICS, MAX_OBSERVATION_LIMITATIONS, MAX_OBSERVATION_LIMITATION_LENGTH, OBSERVATION_VERSION, createObservation, normalizeCtTimestamp, normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp, readObservationEnvelope, };
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { normalizeDomain, openOrCreateCase, updateCase, } from "
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export const MAX_EXTERNAL_FINDINGS_PER_DOMAIN = 20;
|
|
7
|
-
export const MAX_EXTERNAL_FINDING_DOMAINS = 25;
|
|
1
|
+
import { normalizeDomain, openOrCreateCase, updateCase, } from "../cases/case-model.mjs";
|
|
2
|
+
import { normalizeExplicitIsoTimestamp } from "../evidence/observation.mjs";
|
|
3
|
+
import { EXTERNAL_FINDINGS_SCHEMA, EXTERNAL_FINDINGS_VERSION, MAX_EXTERNAL_FINDING_DOMAINS, MAX_EXTERNAL_FINDINGS, MAX_EXTERNAL_FINDINGS_IMPORT_BYTES, MAX_EXTERNAL_FINDINGS_PER_DOMAIN, } from "../contracts/external-observation-interchange.mjs";
|
|
4
|
+
import { hasUnsafeRetainedText } from "./retained-text.mjs";
|
|
5
|
+
export { EXTERNAL_FINDINGS_SCHEMA, EXTERNAL_FINDINGS_VERSION, MAX_EXTERNAL_FINDING_DOMAINS, MAX_EXTERNAL_FINDINGS, MAX_EXTERNAL_FINDINGS_IMPORT_BYTES, MAX_EXTERNAL_FINDINGS_PER_DOMAIN, };
|
|
8
6
|
export const EXTERNAL_FINDING_CATEGORIES = [
|
|
9
7
|
'certificate',
|
|
10
8
|
'dns',
|
|
@@ -17,7 +15,6 @@ export const EXTERNAL_FINDING_CATEGORIES = [
|
|
|
17
15
|
];
|
|
18
16
|
const CATEGORIES = new Set(EXTERNAL_FINDING_CATEGORIES);
|
|
19
17
|
const COMPLETENESS = new Set(['complete', 'inconclusive', 'partial', 'unknown']);
|
|
20
|
-
const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
21
18
|
const ROOT_KEYS = new Set(['schema', 'schemaVersion', 'source', 'findings']);
|
|
22
19
|
const SOURCE_KEYS = new Set(['name', 'reference', 'collectedAt']);
|
|
23
20
|
const FINDING_KEYS = new Set([
|
|
@@ -62,8 +59,8 @@ function requiredText(value, maximum, label) {
|
|
|
62
59
|
if (typeof value !== 'string'
|
|
63
60
|
|| !value.trim()
|
|
64
61
|
|| value.length > maximum
|
|
65
|
-
||
|
|
66
|
-
throw new Error(`${label} must be non-empty, bounded text without control characters.`);
|
|
62
|
+
|| hasUnsafeRetainedText(value)) {
|
|
63
|
+
throw new Error(`${label} must be non-empty, bounded text without unsafe control characters or formatting controls.`);
|
|
67
64
|
}
|
|
68
65
|
return value.trim();
|
|
69
66
|
}
|
|
@@ -76,10 +73,10 @@ function iso(value, label, optional = false) {
|
|
|
76
73
|
if (optional && (value === undefined || value === null || value === ''))
|
|
77
74
|
return null;
|
|
78
75
|
const text = requiredText(value, 64, label);
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
81
|
-
throw new Error(`${label} must be a valid date and time.`);
|
|
82
|
-
return
|
|
76
|
+
const normalized = normalizeExplicitIsoTimestamp(text);
|
|
77
|
+
if (!normalized)
|
|
78
|
+
throw new Error(`${label} must be a valid date and time with an explicit timezone.`);
|
|
79
|
+
return normalized;
|
|
83
80
|
}
|
|
84
81
|
function limitations(value, index) {
|
|
85
82
|
if (value === undefined)
|
|
@@ -92,7 +89,7 @@ function limitations(value, index) {
|
|
|
92
89
|
unique.add(requiredText(item, 240, `Finding ${index + 1} limitation`));
|
|
93
90
|
return [...unique];
|
|
94
91
|
}
|
|
95
|
-
function structuredObservation(value, index
|
|
92
|
+
function structuredObservation(value, index) {
|
|
96
93
|
if (value === undefined || value === null)
|
|
97
94
|
return null;
|
|
98
95
|
const item = record(value);
|
|
@@ -124,7 +121,7 @@ function structuredObservation(value, index, documentVersion) {
|
|
|
124
121
|
let dnsNameCount = null;
|
|
125
122
|
let namesComplete = null;
|
|
126
123
|
if (declaresEventMetadata) {
|
|
127
|
-
if (!certificateSchema
|
|
124
|
+
if (!certificateSchema) {
|
|
128
125
|
throw new Error(`Finding ${index + 1} certificate-event metadata requires external-findings schema version 4.`);
|
|
129
126
|
}
|
|
130
127
|
eventId = requiredText(item.eventId, 64, `Finding ${index + 1} certificate event id`);
|
|
@@ -180,8 +177,8 @@ export function parseExternalFindingsDocument(value) {
|
|
|
180
177
|
throw new Error('External findings must use the documented object shape without additional top-level fields.');
|
|
181
178
|
}
|
|
182
179
|
if (root.schema !== EXTERNAL_FINDINGS_SCHEMA
|
|
183
|
-
||
|
|
184
|
-
throw new Error(`External findings must use ${EXTERNAL_FINDINGS_SCHEMA} schema version
|
|
180
|
+
|| root.schemaVersion !== EXTERNAL_FINDINGS_VERSION) {
|
|
181
|
+
throw new Error(`External findings must use ${EXTERNAL_FINDINGS_SCHEMA} schema version ${EXTERNAL_FINDINGS_VERSION}.`);
|
|
185
182
|
}
|
|
186
183
|
const sourceValue = record(root.source);
|
|
187
184
|
if (!sourceValue || !hasOnlyKeys(sourceValue, SOURCE_KEYS)) {
|
|
@@ -213,9 +210,7 @@ export function parseExternalFindingsDocument(value) {
|
|
|
213
210
|
if (typeof item.completeness !== 'string' || !COMPLETENESS.has(item.completeness)) {
|
|
214
211
|
throw new Error(`Finding ${index + 1} completeness is unsupported.`);
|
|
215
212
|
}
|
|
216
|
-
const evidenceClass = item.evidenceClass
|
|
217
|
-
? 'provider_report'
|
|
218
|
-
: item.evidenceClass;
|
|
213
|
+
const evidenceClass = item.evidenceClass;
|
|
219
214
|
if (evidenceClass !== 'deployment_observation' && evidenceClass !== 'provider_report') {
|
|
220
215
|
throw new Error(`Finding ${index + 1} evidence class is unsupported.`);
|
|
221
216
|
}
|
|
@@ -228,7 +223,7 @@ export function parseExternalFindingsDocument(value) {
|
|
|
228
223
|
completeness: item.completeness,
|
|
229
224
|
limitations: limitations(item.limitations, index),
|
|
230
225
|
reference: optionalText(item.reference, 500, `Finding ${index + 1} reference`),
|
|
231
|
-
structuredObservation: structuredObservation(item.structuredObservation, index
|
|
226
|
+
structuredObservation: structuredObservation(item.structuredObservation, index),
|
|
232
227
|
};
|
|
233
228
|
const key = findingKey(finding, source.name);
|
|
234
229
|
if (seen.has(key))
|
|
@@ -253,6 +248,13 @@ export function parseExternalFindingsDocument(value) {
|
|
|
253
248
|
findings: normalized,
|
|
254
249
|
};
|
|
255
250
|
}
|
|
251
|
+
export function serializeExternalFindingsDocument(value) {
|
|
252
|
+
const serialised = JSON.stringify(value, null, 2);
|
|
253
|
+
if (typeof serialised !== 'string') {
|
|
254
|
+
throw new TypeError('External findings must be JSON-serialisable.');
|
|
255
|
+
}
|
|
256
|
+
return `${serialised}\n`;
|
|
257
|
+
}
|
|
256
258
|
function pinValue(finding) {
|
|
257
259
|
return finding.reference
|
|
258
260
|
? `${finding.summary} Reference: ${finding.reference}`
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const UNSAFE_RETAINED_TEXT_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignorable_Code_Point}/u;
|
|
2
|
+
const UNSAFE_RETAINED_TEXT_GLOBAL_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignorable_Code_Point}/gu;
|
|
3
|
+
export function hasUnsafeRetainedText(value) {
|
|
4
|
+
return UNSAFE_RETAINED_TEXT_RE.test(value);
|
|
5
|
+
}
|
|
6
|
+
export function neutralizeUnsafeRetainedText(value) {
|
|
7
|
+
return value.replace(UNSAFE_RETAINED_TEXT_GLOBAL_RE, ' ');
|
|
8
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../evidence/artifact-integrity.mjs";
|
|
2
|
+
import { ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION, } from "../contracts/investigation-portability.mjs";
|
|
3
|
+
import { isValidAsciiDomainName } from "../../lib/hostname.mjs";
|
|
4
|
+
export { ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION };
|
|
4
5
|
export const ACQUISITION_DECISIONS = [
|
|
5
6
|
'unresolved',
|
|
6
7
|
'continue_manual_review',
|
|
@@ -28,11 +29,13 @@ function timestamp(value) {
|
|
|
28
29
|
return new Date(value).toISOString();
|
|
29
30
|
}
|
|
30
31
|
function target(value) {
|
|
31
|
-
const
|
|
32
|
-
|
|
32
|
+
const submitted = typeof value === 'string'
|
|
33
|
+
? value.trim().replace(/\.$/u, '')
|
|
34
|
+
: '';
|
|
35
|
+
if (!isValidAsciiDomainName(submitted, { requireDot: true })) {
|
|
33
36
|
throw new Error('A canonical domain is required for an acquisition decision packet.');
|
|
34
37
|
}
|
|
35
|
-
return
|
|
38
|
+
return submitted.toLowerCase();
|
|
36
39
|
}
|
|
37
40
|
function decision(value) {
|
|
38
41
|
return typeof value === 'string' && DECISION_SET.has(value)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { normalizeBulkSessionResult, } from "
|
|
2
|
-
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "
|
|
3
|
-
import { BULK_REVIEW_STALE_AFTER_DAYS } from "./bulk-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export const BULK_DOMAIN_COMPARISON_EXPORT_VERSION = 4;
|
|
1
|
+
import { normalizeBulkSessionResult, } from "../workspace/bulk-session-model.mjs";
|
|
2
|
+
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../evidence/artifact-integrity.mjs";
|
|
3
|
+
import { BULK_REVIEW_STALE_AFTER_DAYS } from "./bulk-review-policy.mjs";
|
|
4
|
+
import { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, BULK_DOMAIN_COMPARISON_VERSION, } from "../contracts/investigation-portability.mjs";
|
|
5
|
+
export { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, BULK_DOMAIN_COMPARISON_VERSION };
|
|
7
6
|
function text(value, maximum = 500) {
|
|
8
7
|
return typeof value === 'string'
|
|
9
8
|
? value.replace(/[\u0000-\u001f\u007f]/gu, ' ').replace(/\s+/gu, ' ').trim().slice(0, maximum)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "
|
|
2
|
-
import { BULK_PROFILE_CONTEXT_MISMATCH_LIMITATION, normalizeBulkProfileContext, normalizeBulkSessionResult, } from "
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export const BULK_MAIL_EXPOSURE_EXPORT_VERSION = 2;
|
|
6
|
-
export const MAX_BULK_MAIL_EXPOSURE_ROWS = 2_000;
|
|
1
|
+
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../evidence/artifact-integrity.mjs";
|
|
2
|
+
import { BULK_PROFILE_CONTEXT_MISMATCH_LIMITATION, normalizeBulkProfileContext, normalizeBulkSessionResult, } from "../workspace/bulk-session-model.mjs";
|
|
3
|
+
import { BULK_MAIL_EXPOSURE_EXPORT_VERSION, BULK_MAIL_EXPOSURE_SCHEMA, BULK_MAIL_EXPOSURE_VERSION, MAX_BULK_MAIL_EXPOSURE_ROWS, } from "../contracts/investigation-portability.mjs";
|
|
4
|
+
export { BULK_MAIL_EXPOSURE_EXPORT_VERSION, BULK_MAIL_EXPOSURE_SCHEMA, BULK_MAIL_EXPOSURE_VERSION, MAX_BULK_MAIL_EXPOSURE_ROWS };
|
|
7
5
|
const STATE_LABELS = {
|
|
8
6
|
authenticated_mail: 'Receiving mail with SPF and DMARC',
|
|
9
7
|
evidence_incomplete: 'Mail evidence incomplete',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { normalizeBulkSessionResult, } from "
|
|
2
|
-
import { BULK_REVIEW_STATES, } from "
|
|
3
|
-
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "
|
|
4
|
-
|
|
5
|
-
export
|
|
1
|
+
import { normalizeBulkSessionResult, } from "../workspace/bulk-session-model.mjs";
|
|
2
|
+
import { BULK_REVIEW_STATES, } from "../workspace/bulk-review-model.mjs";
|
|
3
|
+
import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../evidence/artifact-integrity.mjs";
|
|
4
|
+
import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, } from "../contracts/investigation-portability.mjs";
|
|
5
|
+
export { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION };
|
|
6
6
|
const MAX_MANIFEST_ROWS = 2_000;
|
|
7
7
|
const REVIEW_STATE_SET = new Set(BULK_REVIEW_STATES);
|
|
8
8
|
function timestamp(value, fallback) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const BULK_REVIEW_STALE_AFTER_DAYS = 7;
|