@slicedearth/whoisleuth-cli 2.4.0 → 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/NOTICE +2 -2
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -470
- package/cli/artifact-validation/case-response.mjs +67 -9
- package/cli/artifact-validation/lookup-evidence.mjs +18 -4
- package/cli/artifact-validation/signed-review.mjs +61 -64
- package/cli/artifact-verify.mjs +11 -2
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +1 -1
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +6 -8
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +99 -6
- package/cli/case-pack.mjs +19 -4
- package/cli/ci-report.mjs +6 -3
- package/cli/collection-arguments.mjs +123 -0
- package/cli/collection-command-definitions.mjs +330 -0
- package/cli/collection-preflight.mjs +8 -1
- package/cli/command-argument-grammar.mjs +8 -210
- package/cli/command-definition.mjs +330 -0
- package/cli/command-reference.mjs +55 -1388
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/config-profile.mjs +1 -1
- package/cli/ct-event-intake.mjs +4 -4
- package/cli/discover.mjs +2 -2
- package/cli/discovery-observation-snapshot.mjs +2 -2
- package/cli/discovery-scan-command-runner.mjs +7 -9
- package/cli/discovery-scan.mjs +1 -1
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +4 -4
- package/cli/domain-control-monitor.mjs +6 -6
- package/cli/domain-control-observations.mjs +2 -1
- package/cli/domain-feed.mjs +61 -0
- package/cli/errors.mjs +5 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +2 -3
- package/cli/evidence-signing.mjs +3 -2
- package/cli/external-observation-mapping.mjs +2 -2
- package/cli/formatters/json.mjs +1 -13
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +6 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +32 -0
- package/cli/inline-command-families.mjs +6 -47
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +147 -0
- package/cli/interchange-report.mjs +4 -2
- package/cli/investigation-artifacts.mjs +3 -4
- package/cli/investigation-package-review.mjs +2 -0
- package/cli/investigation-plan.mjs +32 -154
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +78 -12
- package/cli/local-mmdb-review.mjs +220 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-command-runner.mjs +19 -35
- package/cli/lookup-diff.mjs +10 -9
- package/cli/lookup-plan.mjs +10 -14
- package/cli/lookup-reconcile.mjs +6 -5
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +9 -7
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +43 -31
- package/cli/offline-evidence-review.mjs +85 -6
- package/cli/posture.mjs +1 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +8 -7
- package/cli/review-arguments.mjs +132 -0
- package/cli/review-command-definitions.mjs +279 -0
- package/cli/review-command-runner.mjs +72 -159
- package/cli/risk-calibration.mjs +14 -12
- package/cli/runner.mjs +20 -32
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +73 -0
- package/cli/sharing-review.mjs +5 -64
- package/cli/source-reliability.mjs +2 -2
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +4 -4
- package/cli/watchlist-review.mjs +43 -0
- package/cli/workflow-arguments.mjs +100 -0
- package/cli/workflow-command-definitions.mjs +103 -0
- package/cli/workflow-command-runner.mjs +40 -1
- package/docs/cli-reference.md +27 -0
- package/docs/cli.md +322 -4
- package/docs/contextual-reviews.md +178 -0
- package/docs/infrastructure-observations.md +126 -0
- package/docs/offline-intake-context.md +213 -0
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +13 -789
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-json.mjs +2 -330
- package/lib/browser-library-profile.mjs +62 -84
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/credential-form-attribution.mjs +136 -0
- package/lib/credential-surface-profile.mjs +6 -2
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +83 -47
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +35 -15
- package/lib/dns-inheritance-review.mjs +53 -63
- package/lib/dns-intelligence.mjs +27 -11
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +12 -13
- package/lib/domain-change-review.mjs +32 -14
- package/lib/domain-control-flight-recorder.mjs +103 -27
- package/lib/domain-control-manifest.mjs +25 -17
- package/lib/domain-posture-analysis.mjs +75 -7
- package/lib/domain-posture-parsers.mjs +9 -0
- package/lib/domain-posture.mjs +45 -25
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +27 -17
- package/lib/evidence-export.mjs +3 -3
- package/lib/evidence-report.mjs +5 -1
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +13 -4
- package/lib/generated/cisa-kev-catalog.mjs +41 -3
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +332 -19
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +13 -2
- package/lib/http-evidence-bounds.mjs +2 -8
- package/lib/http-intelligence.mjs +13 -1
- package/lib/idn-confusable-policy.mjs +6 -4
- package/lib/idn-confusables.mjs +1 -1
- package/lib/interchange-fidelity-registry.mjs +10 -0
- package/lib/lookup-child-profile-contract.mjs +28 -11
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +1 -23
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +50 -1
- package/lib/lookup-response-contract.mjs +70 -40
- package/lib/lookup-response.mjs +5 -15
- package/lib/lookup-source-progress.mjs +8 -22
- package/lib/lookup.mjs +1 -1
- package/lib/operation-budget.mjs +4 -0
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/page-fingerprints.mjs +18 -1
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +36 -10
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-client.mjs +11 -7
- package/lib/registrable-domain.mjs +2 -56
- package/lib/registrar-standing-contract.mjs +1 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +2 -2
- package/lib/registry-capability-catalogue.mjs +27 -19
- package/lib/risk-calibration-summary.mjs +2 -352
- package/lib/risk-scoring.mjs +2 -345
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +46 -2
- package/lib/smtp-transport-review.mjs +48 -19
- package/lib/sslbl-certificates.generated.mjs +142 -139
- package/lib/static-html-analysis.mjs +32 -4
- package/lib/structured-data-identity.mjs +1 -1
- package/lib/technology-evidence-role.mjs +1 -51
- package/lib/threat-intelligence-manifests.mjs +1 -1
- package/lib/threat-intelligence-runtime.mjs +1 -1
- package/lib/threat-intelligence-targets.mjs +1 -1
- package/lib/threat-intelligence-types.mjs +2 -75
- package/lib/tls-intelligence.mjs +30 -5
- package/lib/typosquat-generator.mjs +11 -2
- package/lib/website-enrichment.mjs +404 -0
- package/lib/whois-authority.mjs +43 -5
- package/lib/whois-chain.mjs +12 -3
- package/lib/whois-common-formats.mjs +3 -1
- package/lib/whois-contacts.mjs +13 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser.mjs +5 -3
- package/package.json +15 -14
- package/packages/analysis/analyst-taxonomy.mjs +40 -0
- package/packages/analysis/bounded-json.mjs +330 -0
- package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
- package/packages/analysis/cli-argument-grammar.mjs +216 -0
- package/packages/analysis/ct-query.mjs +27 -0
- package/packages/analysis/ct-response-bounds.mjs +9 -0
- package/packages/analysis/external-intelligence-risk.mjs +124 -0
- package/packages/analysis/http-evidence-bounds.mjs +8 -0
- package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
- package/packages/analysis/opportunity-scoring.mjs +107 -0
- package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
- package/packages/analysis/portable-generator.mjs +29 -0
- package/packages/analysis/project-metadata.mjs +8 -0
- package/packages/analysis/registrable-domain.mjs +56 -0
- package/packages/analysis/risk-calibration-summary.mjs +352 -0
- package/packages/analysis/risk-scoring.mjs +348 -0
- package/packages/analysis/scoring-evidence-quality.mjs +107 -0
- package/packages/analysis/semantic-version.mjs +54 -0
- package/packages/analysis/threat-intelligence-types.mjs +75 -0
- package/packages/cases/case-containment-handoff.mjs +195 -0
- package/packages/cases/case-evidence-links.mjs +151 -0
- package/packages/cases/case-evidence-model.mjs +89 -12
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +7 -5
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +40 -24
- 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 +124 -36
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +1 -1
- package/packages/cases/case-record-operations.mjs +111 -118
- package/packages/cases/case-record-projection.mjs +23 -4
- package/packages/cases/case-record-version-input.mjs +41 -0
- package/packages/cases/case-report.mjs +93 -27
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +159 -1
- package/packages/cases/case-response-model.mjs +16 -1
- package/packages/cases/case-response-object.mjs +119 -0
- package/packages/cases/case-response-outcomes.mjs +108 -20
- package/packages/cases/case-response-packet-render.mjs +35 -12
- package/packages/cases/case-response-packet.mjs +55 -13
- package/packages/cases/case-response-records.mjs +0 -1
- package/packages/cases/case-response-review-inputs.mjs +60 -8
- package/packages/cases/case-storage-model.mjs +1 -1
- package/packages/cases/case-workflow-metadata.mjs +217 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +1 -1
- package/packages/comparison/relationship-evidence.mjs +21 -9
- package/packages/comparison/relationship-provenance.mjs +7 -1
- package/packages/contracts/analyst-interchange.mjs +35 -15
- package/packages/contracts/candidate-watch-review.mjs +54 -0
- package/packages/contracts/case-portability.mjs +360 -49
- package/packages/contracts/cli-command-semantics.mjs +5 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/context-review.mjs +25 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
- package/packages/contracts/domain-control-manifest.mjs +1 -1
- package/packages/contracts/domain-control-monitor.mjs +13 -2
- package/packages/contracts/domain-control-review.mjs +36 -15
- package/packages/contracts/external-observation-interchange.mjs +96 -28
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-run.mjs +2 -0
- package/packages/contracts/ip-address.mjs +10 -0
- package/packages/contracts/lookup-evidence.mjs +3 -2
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +37 -0
- package/packages/contracts/offline-comparison.mjs +4 -1
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +44 -29
- package/packages/contracts/schema-lifecycle.mjs +3 -1
- package/packages/contracts/web-capture.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +144 -28
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/credential-form-attribution.mjs +60 -0
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +15 -0
- package/packages/evidence/lookup-target.mjs +5 -1
- package/packages/evidence/observation.mjs +2 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +60 -16
- package/packages/evidence/technology-evidence-role.mjs +51 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +36 -29
- package/packages/interchange/json-output.mjs +14 -0
- package/packages/interchange/managed-indicator-export.mjs +36 -0
- package/packages/interchange/managed-indicator-set.mjs +223 -0
- package/packages/interchange/misp-indicator-export.mjs +93 -0
- package/packages/interchange/sharing-policy.mjs +56 -0
- package/packages/interchange/stix-indicator-export.mjs +129 -0
- package/packages/interchange/web-capture-import.mjs +22 -15
- package/packages/investigation/acquisition-decision-packet.mjs +79 -1
- package/packages/investigation/bulk-mail-exposure.mjs +7 -13
- package/packages/investigation/capture-coverage.mjs +57 -0
- package/packages/investigation/connector-provenance-review.mjs +100 -0
- package/packages/investigation/context-review.mjs +35 -0
- package/packages/investigation/document-intake.mjs +87 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +132 -0
- package/packages/investigation/domain-transition-review.mjs +49 -0
- package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
- package/packages/investigation/har-intake.mjs +68 -0
- package/packages/investigation/identity-event-intake.mjs +131 -0
- package/packages/investigation/identity-incident-review.mjs +39 -0
- package/packages/investigation/incident-sequence-review.mjs +58 -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 +20 -0
- package/packages/investigation/investigation-manifest.mjs +35 -9
- package/packages/investigation/link-intake.mjs +209 -0
- package/packages/investigation/lookup-asset-graph.mjs +1 -1
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +235 -0
- package/packages/investigation/page-behaviour.mjs +113 -0
- package/packages/investigation/pdf-intake.mjs +155 -0
- package/packages/investigation/platform-continuity-review.mjs +60 -0
- package/packages/investigation/qr-intake.mjs +56 -0
- package/packages/investigation/requested-action-hints.mjs +11 -0
- package/packages/investigation/selected-input-review.mjs +17 -0
- package/packages/investigation/storefront-review.mjs +70 -0
- 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 +48 -4
- package/packages/workspace/bulk-review-model.mjs +28 -26
- package/packages/workspace/bulk-session-model.mjs +27 -13
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/candidate-watch-handoff.mjs +195 -0
- package/packages/workspace/detection-rule-model.mjs +11 -3
- package/packages/workspace/hostile-input.mjs +1 -1
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +26 -2
- package/packages/workspace/page-baseline.mjs +2 -2
- package/packages/workspace/relationship-observation-model.mjs +17 -13
- package/packages/workspace/shortlist-model.mjs +24 -7
- package/packages/workspace/watchlist-history.mjs +59 -16
- package/packages/workspace/watchlist-store.mjs +152 -11
- package/packages/workspace/website-snapshot-model.mjs +28 -3
- package/packages/workspace/workspace-archive-crypto.mjs +1 -1
- package/packages/workspace/workspace-archive.mjs +5 -4
- package/third-party-notices.txt +549 -16
- package/lib/hostname.mjs +0 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DOMAIN_CONTROL_MANIFEST_COMPATIBILITY } from "../packages/contracts/domain-control-manifest.mjs";
|
|
2
|
+
import { MANAGED_INDICATOR_SET_COMPATIBILITY } from "../packages/contracts/analyst-interchange.mjs";
|
|
2
3
|
import { CLI_CASE_PACK_COMPATIBILITY, ENCRYPTED_WORKSPACE_ARCHIVE_COMPATIBILITY, WORKSPACE_ARCHIVE_COMPATIBILITY, } from "../packages/contracts/case-portability.mjs";
|
|
3
4
|
import { LOOKUP_CLAIM_PASSPORT_COMPATIBILITY } from "../packages/contracts/investigation-portability.mjs";
|
|
4
5
|
import { BRAND_PROFILE_EXPORT_COMPATIBILITY } from "../packages/contracts/workspace-portability.mjs";
|
|
@@ -16,6 +17,15 @@ function compatibilityReference(descriptor) {
|
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
19
|
export const INTERCHANGE_ARTIFACT_CONTRACTS = Object.freeze([
|
|
20
|
+
Object.freeze({
|
|
21
|
+
id: 'managed_indicators', ...compatibilityReference(MANAGED_INDICATOR_SET_COMPATIBILITY),
|
|
22
|
+
versionField: 'version', nestedSchemaPath: Object.freeze([]),
|
|
23
|
+
browser: Object.freeze({ import: 'supported', export: 'supported' }),
|
|
24
|
+
cli: Object.freeze({ read: 'supported', write: 'supported', verify: 'supported' }),
|
|
25
|
+
fidelity: 'semantic_exact_after_normalisation', requiredAssurance: 'whole_integrity',
|
|
26
|
+
preservedFieldGroups: Object.freeze(['stable_indicator_identities', 'original_observations', 'review_windows', 'withdrawal_records', 'preceding_revision_digest']),
|
|
27
|
+
excludedFieldGroups: Object.freeze(['raw_responses', 'browser_local_records', 'credentials', 'remote_publication', 'author_authentication', 'unavailable_revision_history']),
|
|
28
|
+
}),
|
|
19
29
|
Object.freeze({
|
|
20
30
|
id: 'lookup_claim_passport',
|
|
21
31
|
...compatibilityReference(LOOKUP_CLAIM_PASSPORT_COMPATIBILITY),
|
|
@@ -8,6 +8,7 @@ import { MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOK
|
|
|
8
8
|
import { isJsonObject, normalizedDomain, validBoundedString, validHttpProvenanceUrl, validObservationDiagnostics, validOptionalNullableText, validStringArray, validUint, } from "./lookup-contract-primitives.mjs";
|
|
9
9
|
import { TECHNOLOGY_EVIDENCE_ROLE_ORDER } from "./technology-evidence-role.mjs";
|
|
10
10
|
import { MAX_HOMEPAGE_BYTES } from "./outbound-request-bounds.mjs";
|
|
11
|
+
import { CREDENTIAL_CATEGORY_NAMES, MAX_CREDENTIAL_FORMS, MAX_CREDENTIAL_INPUTS, externalPasswordFormObservation, validCredentialFormAttribution } from "../packages/evidence/credential-form-attribution.mjs";
|
|
11
12
|
export const TECHNOLOGY_PROFILE_VERSION = 12;
|
|
12
13
|
export const SUPPORTED_TECHNOLOGY_PROFILE_VERSIONS = Object.freeze([10, 11, TECHNOLOGY_PROFILE_VERSION]);
|
|
13
14
|
export const MAX_TECHNOLOGY_FINDINGS = 24;
|
|
@@ -17,7 +18,7 @@ export const BROWSER_LIBRARY_PROFILE_VERSION = 2;
|
|
|
17
18
|
export const MAX_LIBRARY_FINDINGS = 16;
|
|
18
19
|
export const WEBSITE_SECURITY_POSTURE_VERSION = 3;
|
|
19
20
|
export const MAX_SECURITY_POSTURE_FINDINGS = 32;
|
|
20
|
-
export const CREDENTIAL_SURFACE_PROFILE_VERSION =
|
|
21
|
+
export const CREDENTIAL_SURFACE_PROFILE_VERSION = 2;
|
|
21
22
|
export const STRUCTURED_DATA_IDENTITY_VERSION = 1;
|
|
22
23
|
export const MAX_STRUCTURED_DATA_ENTITIES = 16;
|
|
23
24
|
export const MAX_STRUCTURED_DATA_SAME_AS_HOSTS = 12;
|
|
@@ -66,7 +67,7 @@ const SIMHASH64_RE = /^[a-f0-9]{16}$/u;
|
|
|
66
67
|
const OID_RE = /^\d{1,10}(?:\.\d{1,10}){1,31}$/u;
|
|
67
68
|
const CREDENTIAL_METHOD_KEYS = Object.freeze(['missing', 'get', 'post', 'dialog', 'other']);
|
|
68
69
|
const CREDENTIAL_ACTION_KEYS = Object.freeze(['sameOrigin', 'external', 'missing', 'cleartext', 'unclassified']);
|
|
69
|
-
const CREDENTIAL_CATEGORY_KEYS =
|
|
70
|
+
const CREDENTIAL_CATEGORY_KEYS = CREDENTIAL_CATEGORY_NAMES;
|
|
70
71
|
const TLS_NAME_KEYS = Object.freeze([
|
|
71
72
|
'commonNames', 'organizations', 'organizationalUnits', 'countries', 'localities', 'states',
|
|
72
73
|
]);
|
|
@@ -683,28 +684,37 @@ function securityPostureContractState(value) {
|
|
|
683
684
|
return 'supported';
|
|
684
685
|
}
|
|
685
686
|
function credentialSurfaceContractState(value) {
|
|
686
|
-
const versionState = childVersionState(value, 'credentialSurfaceVersion', [CREDENTIAL_SURFACE_PROFILE_VERSION]);
|
|
687
|
+
const versionState = childVersionState(value, 'credentialSurfaceVersion', [1, CREDENTIAL_SURFACE_PROFILE_VERSION]);
|
|
687
688
|
if (versionState !== 'supported')
|
|
688
689
|
return versionState;
|
|
689
690
|
const profile = value;
|
|
690
691
|
const forms = profile.forms;
|
|
691
692
|
const inputs = profile.inputs;
|
|
692
|
-
if (!hasExactKeys(profile, [...OBSERVATION_FIELDS, 'credentialSurfaceVersion', 'forms', 'inputs'])
|
|
693
|
+
if (!hasExactKeys(profile, [...OBSERVATION_FIELDS, 'credentialSurfaceVersion', 'forms', 'inputs', ...(profile.credentialSurfaceVersion === 2 ? ['formAttribution'] : [])])
|
|
693
694
|
|| !validProfileObservation(profile, new Set(['success', 'partial']), new Set(['html']))
|
|
694
695
|
|| !isJsonObject(forms)
|
|
695
696
|
|| !hasExactKeys(forms, ['count', 'methods', 'actions'])
|
|
696
|
-
|| !validUint(forms.count,
|
|
697
|
-
|| !validCredentialCountRecord(forms.methods, CREDENTIAL_METHOD_KEYS,
|
|
698
|
-
|| !validCredentialCountRecord(forms.actions, CREDENTIAL_ACTION_KEYS,
|
|
697
|
+
|| !validUint(forms.count, MAX_CREDENTIAL_FORMS)
|
|
698
|
+
|| !validCredentialCountRecord(forms.methods, CREDENTIAL_METHOD_KEYS, MAX_CREDENTIAL_FORMS)
|
|
699
|
+
|| !validCredentialCountRecord(forms.actions, CREDENTIAL_ACTION_KEYS, MAX_CREDENTIAL_FORMS)
|
|
699
700
|
|| CREDENTIAL_METHOD_KEYS.reduce((sum, key) => sum + Number(forms.methods[key]), 0) !== forms.count
|
|
700
701
|
|| CREDENTIAL_ACTION_KEYS.reduce((sum, key) => sum + Number(forms.actions[key]), 0) !== forms.count
|
|
701
702
|
|| !isJsonObject(inputs)
|
|
702
703
|
|| !hasExactKeys(inputs, ['count', 'classifiedCount', 'categories'])
|
|
703
|
-
|| !validUint(inputs.count,
|
|
704
|
-
|| !validUint(inputs.classifiedCount,
|
|
704
|
+
|| !validUint(inputs.count, MAX_CREDENTIAL_INPUTS)
|
|
705
|
+
|| !validUint(inputs.classifiedCount, MAX_CREDENTIAL_INPUTS)
|
|
705
706
|
|| Number(inputs.classifiedCount) > Number(inputs.count)
|
|
706
|
-
|| !validCredentialCountRecord(inputs.categories, CREDENTIAL_CATEGORY_KEYS,
|
|
707
|
+
|| !validCredentialCountRecord(inputs.categories, CREDENTIAL_CATEGORY_KEYS, MAX_CREDENTIAL_INPUTS))
|
|
707
708
|
return 'invalid';
|
|
709
|
+
if (profile.credentialSurfaceVersion === 2 && (!validCredentialFormAttribution(profile.formAttribution)
|
|
710
|
+
|| profile.formAttribution.forms.length !== forms.count
|
|
711
|
+
|| (profile.complete === true && !profile.formAttribution.complete)))
|
|
712
|
+
return 'invalid';
|
|
713
|
+
if (profile.credentialSurfaceVersion === 2 && validCredentialFormAttribution(profile.formAttribution)) {
|
|
714
|
+
const attribution = profile.formAttribution;
|
|
715
|
+
if (CREDENTIAL_CATEGORY_KEYS.some(key => attribution.forms.reduce((sum, form) => sum + form.categories[key], 0) > Number(inputs.categories[key])))
|
|
716
|
+
return 'invalid';
|
|
717
|
+
}
|
|
708
718
|
return 'supported';
|
|
709
719
|
}
|
|
710
720
|
function structuredDataContractState(value) {
|
|
@@ -819,6 +829,13 @@ function sanitizeLookupChildProfiles(value) {
|
|
|
819
829
|
replaceChild('tls', 'TLS profile', 'tls', tlsProfileContractState);
|
|
820
830
|
replaceChild('securityPosture', 'Security-posture profile', 'derived', securityPostureContractState);
|
|
821
831
|
replaceChild('credentialSurfaceProfile', 'Credential-surface profile', 'html', credentialSurfaceContractState);
|
|
832
|
+
if (availability.hasExternalPasswordForm !== undefined && availability.credentialSurfaceProfile != null) {
|
|
833
|
+
const credential = availability.credentialSurfaceProfile;
|
|
834
|
+
if (!isJsonObject(credential) || credential.credentialSurfaceVersion !== 2
|
|
835
|
+
|| availability.hasExternalPasswordForm !== externalPasswordFormObservation(credential.formAttribution)) {
|
|
836
|
+
mutableAvailability().hasExternalPasswordForm = null;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
822
839
|
replaceChild('structuredDataIdentity', 'Structured-data identity profile', 'html', structuredDataContractState);
|
|
823
840
|
replaceChild('pageRoleProfile', 'Page-role profile', 'derived', pageRoleContractState);
|
|
824
841
|
replaceChild('clientBehaviorProfile', 'Client-behaviour profile', 'derived', clientBehaviorContractState);
|
|
@@ -864,4 +881,4 @@ function sanitizeLookupChildProfiles(value) {
|
|
|
864
881
|
}
|
|
865
882
|
return output;
|
|
866
883
|
}
|
|
867
|
-
export { sanitizeLookupChildProfiles, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
|
|
884
|
+
export { sanitizeLookupChildProfiles, credentialSurfaceContractState, technologyProfileContractState, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// child-profile contracts. These checks validate without normalising or
|
|
3
3
|
// copying untrusted values.
|
|
4
4
|
import { MAX_OBSERVATION_DIAGNOSTICS } from "../packages/evidence/observation.mjs";
|
|
5
|
-
import { MAX_HTTP_PROVENANCE_URL } from "
|
|
5
|
+
import { MAX_HTTP_PROVENANCE_URL } from "../packages/analysis/http-evidence-bounds.mjs";
|
|
6
6
|
const LOOKUP_CONTROL_CHAR_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignorable_Code_Point}/u;
|
|
7
7
|
const MAX_LOOKUP_CONTRACT_HOST_LENGTH = 253;
|
|
8
8
|
function isJsonObject(value) {
|
|
@@ -1,31 +1,9 @@
|
|
|
1
1
|
// Lookup diagnostics, bounded source errors and optional deep-lookup timing.
|
|
2
2
|
import { OPERATION_BUDGET_ERROR_CODE } from "./operation-budget.mjs";
|
|
3
3
|
import { FEATURE_DISABLED_ERROR_CODE } from "./feature-policy.mjs";
|
|
4
|
+
import { LOOKUP_SOURCE_STATES, lookupDiagnosticStates } from "./lookup-progress.mjs";
|
|
4
5
|
const LOOKUP_DIAGNOSTICS_VERSION = 8;
|
|
5
6
|
const LOOKUP_LEGACY_DIAGNOSTICS_VERSION = 7;
|
|
6
|
-
const LOOKUP_SOURCE_STATES = Object.freeze([
|
|
7
|
-
'success', 'partial', 'not_found', 'skipped', 'error', 'unsupported',
|
|
8
|
-
'not_applicable', 'unavailable', 'rate_limited', 'complete', 'disabled', 'stale',
|
|
9
|
-
]);
|
|
10
|
-
const SOURCE_STATES = new Set(LOOKUP_SOURCE_STATES);
|
|
11
|
-
function lookupDiagnosticStates(value) {
|
|
12
|
-
const record = (input) => input && typeof input === 'object' && !Array.isArray(input)
|
|
13
|
-
? input : {};
|
|
14
|
-
const diagnostics = record(value);
|
|
15
|
-
const candidates = {
|
|
16
|
-
rdap: record(diagnostics.rdap).status,
|
|
17
|
-
registrar_rdap: record(record(diagnostics.rdap).registrar).status,
|
|
18
|
-
whois: record(diagnostics.whois).status,
|
|
19
|
-
availability: record(diagnostics.availability).status,
|
|
20
|
-
reverse_dns: record(diagnostics.reverseDns).status,
|
|
21
|
-
network_context: record(diagnostics.network).status,
|
|
22
|
-
security_txt: record(diagnostics.securityTxt).status,
|
|
23
|
-
sslbl: record(diagnostics.sslbl).status,
|
|
24
|
-
};
|
|
25
|
-
return Object.freeze(Object.fromEntries(Object.entries(candidates).map(([source, state]) => [
|
|
26
|
-
source, typeof state === 'string' && SOURCE_STATES.has(state) ? state : null,
|
|
27
|
-
])));
|
|
28
|
-
}
|
|
29
7
|
const LOOKUP_TIMING_VERSION = 1;
|
|
30
8
|
const MAX_LOOKUP_TIMING_MS = 120_000;
|
|
31
9
|
const LOOKUP_TIMING_SOURCE_ORDER = Object.freeze([
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// the Lookup response trust boundary, and the display projection.
|
|
3
|
-
export const MAX_LOOKUP_DNS_RECORDS_PER_TYPE = 16;
|
|
4
|
-
export const MAX_LOOKUP_REVERSE_DNS_PTR_RECORDS = 8;
|
|
5
|
-
export const TLS_PROFILE_VERSION = 3;
|
|
6
|
-
export const MAX_LOOKUP_TLS_CHAIN_CERTIFICATES = 8;
|
|
7
|
-
export const MAX_LOOKUP_TLS_ALT_NAMES = 50;
|
|
8
|
-
export const MAX_LOOKUP_TLS_NAME_VALUES = 4;
|
|
9
|
-
export const MAX_LOOKUP_TLS_FINDINGS = 5;
|
|
10
|
-
export const MAX_LOOKUP_TLS_CERTIFICATE_POLICIES = 16;
|
|
1
|
+
export * from "../packages/analysis/lookup-network-evidence-bounds.mjs";
|
package/lib/lookup-progress.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Portable bounded NDJSON Lookup progress contract. Partial events are
|
|
2
2
|
// presentation-only; only a validated final ordinary Lookup result may cross
|
|
3
3
|
// the persistence boundary.
|
|
4
|
-
import { scanBoundedJson, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES } from "
|
|
4
|
+
import { scanBoundedJson, MAX_BOUNDED_JSON_DEPTH, MAX_BOUNDED_JSON_KEYS, MAX_BOUNDED_JSON_VALUES } from "../packages/analysis/bounded-json.mjs";
|
|
5
5
|
export const LOOKUP_PROGRESS_SCHEMA = 'whoisleuth.lookup-progress';
|
|
6
6
|
export const LOOKUP_PROGRESS_VERSION = 1;
|
|
7
7
|
export const MAX_LOOKUP_PROGRESS_SOURCES = 16;
|
|
@@ -32,7 +32,56 @@ const SOURCE_STATES = new Set([
|
|
|
32
32
|
'unavailable',
|
|
33
33
|
'rate_limited',
|
|
34
34
|
]);
|
|
35
|
+
// Final diagnostic states are distinct from the provisional progress subset.
|
|
36
|
+
export const LOOKUP_SOURCE_STATES = Object.freeze([
|
|
37
|
+
'success', 'partial', 'not_found', 'skipped', 'error', 'unsupported',
|
|
38
|
+
'not_applicable', 'unavailable', 'rate_limited', 'complete', 'disabled', 'stale',
|
|
39
|
+
]);
|
|
40
|
+
const FINAL_SOURCE_STATES = new Set(LOOKUP_SOURCE_STATES);
|
|
41
|
+
export function lookupDiagnosticStates(value) {
|
|
42
|
+
const record = (input) => input && typeof input === 'object' && !Array.isArray(input)
|
|
43
|
+
? input : {};
|
|
44
|
+
const diagnostics = record(value);
|
|
45
|
+
const candidates = {
|
|
46
|
+
rdap: record(diagnostics.rdap).status,
|
|
47
|
+
registrar_rdap: record(record(diagnostics.rdap).registrar).status,
|
|
48
|
+
whois: record(diagnostics.whois).status,
|
|
49
|
+
availability: record(diagnostics.availability).status,
|
|
50
|
+
reverse_dns: record(diagnostics.reverseDns).status,
|
|
51
|
+
network_context: record(diagnostics.network).status,
|
|
52
|
+
security_txt: record(diagnostics.securityTxt).status,
|
|
53
|
+
sslbl: record(diagnostics.sslbl).status,
|
|
54
|
+
};
|
|
55
|
+
return Object.freeze(Object.fromEntries(Object.entries(candidates).map(([source, state]) => [
|
|
56
|
+
source, typeof state === 'string' && FINAL_SOURCE_STATES.has(state) ? state : null,
|
|
57
|
+
])));
|
|
58
|
+
}
|
|
35
59
|
const SOURCE_ID_RE = /^[a-z][a-z0-9_]{0,39}$/u;
|
|
60
|
+
/** The existing Lookup recipe, shared by offline plans and source progress.
|
|
61
|
+
* This describes admitted families, not provider health or request counts. */
|
|
62
|
+
export function plannedLookupSources(targetType, mode, options = {}) {
|
|
63
|
+
const sources = ['rdap'];
|
|
64
|
+
if (mode === 'fast') {
|
|
65
|
+
if (targetType === 'domain')
|
|
66
|
+
sources.push('domain_evidence');
|
|
67
|
+
return Object.freeze(sources);
|
|
68
|
+
}
|
|
69
|
+
sources.push('whois');
|
|
70
|
+
if (targetType === 'domain') {
|
|
71
|
+
sources.push('domain_evidence', 'registrar_rdap', 'network_context');
|
|
72
|
+
if (options.securityTxt)
|
|
73
|
+
sources.push('security_txt');
|
|
74
|
+
if (options.externalIntelligence)
|
|
75
|
+
sources.push('external_intelligence');
|
|
76
|
+
if (options.malwareHostIntelligence)
|
|
77
|
+
sources.push('malware_host_intelligence');
|
|
78
|
+
if (options.malwareIocIntelligence)
|
|
79
|
+
sources.push('malware_ioc_intelligence');
|
|
80
|
+
}
|
|
81
|
+
else if (targetType === 'ipv4' || targetType === 'ipv6')
|
|
82
|
+
sources.push('reverse_dns');
|
|
83
|
+
return Object.freeze(sources);
|
|
84
|
+
}
|
|
36
85
|
const TEXT_ENCODER = new TextEncoder();
|
|
37
86
|
function byteLength(value) {
|
|
38
87
|
return TEXT_ENCODER.encode(value).byteLength;
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
// relies on before any evidence is derived or rendered. Nested source records
|
|
7
7
|
// stay separately attributed and additive; their own normalizers remain the
|
|
8
8
|
// authority for source-specific fields.
|
|
9
|
-
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_CATEGORIES, THREAT_INTELLIGENCE_CONFIDENCES, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_SEVERITIES, } from "
|
|
10
|
-
import { MAX_HTTP_ATTEMPTS, MAX_HTTP_ERROR_LENGTH, MAX_HTTP_EVIDENCE_REDIRECTS, } from "
|
|
9
|
+
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_CATEGORIES, THREAT_INTELLIGENCE_CONFIDENCES, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_SEVERITIES, } from "../packages/analysis/threat-intelligence-types.mjs";
|
|
10
|
+
import { MAX_HTTP_ATTEMPTS, MAX_HTTP_ERROR_LENGTH, MAX_HTTP_EVIDENCE_REDIRECTS, } from "../packages/analysis/http-evidence-bounds.mjs";
|
|
11
11
|
import { MAX_OBSERVATION_LIMITATIONS, MAX_OBSERVATION_LIMITATION_LENGTH, normalizeExplicitIsoTimestamp, } from "../packages/evidence/observation.mjs";
|
|
12
|
-
import { assertBoundedJsonStructure } from "
|
|
13
|
-
import { canonicalRegistrableDomain } from "
|
|
12
|
+
import { assertBoundedJsonStructure } from "../packages/analysis/bounded-json.mjs";
|
|
13
|
+
import { canonicalRegistrableDomain } from "../packages/analysis/registrable-domain.mjs";
|
|
14
14
|
import { validLookupObservationScope } from "../packages/evidence/lookup-target.mjs";
|
|
15
|
+
import { isWebCollectionQuality } from "../packages/evidence/collection-quality.mjs";
|
|
15
16
|
import { MAX_LOOKUP_DNS_RECORDS_PER_TYPE, MAX_LOOKUP_REVERSE_DNS_PTR_RECORDS, MAX_LOOKUP_TLS_ALT_NAMES, MAX_LOOKUP_TLS_CERTIFICATE_POLICIES, MAX_LOOKUP_TLS_CHAIN_CERTIFICATES, MAX_LOOKUP_TLS_FINDINGS, MAX_LOOKUP_TLS_NAME_VALUES, } from "./lookup-network-evidence-bounds.mjs";
|
|
16
17
|
import { MAX_SECURITY_POSTURE_FINDINGS, sanitizeLookupChildProfiles, validSecurityPostureFinding, validTlsChainCertificate, validTlsFinding, } from "./lookup-child-profile-contract.mjs";
|
|
17
18
|
import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "./homepage-metadata-contract.mjs";
|
|
@@ -136,22 +137,28 @@ function normalizeThreatFinding(value, providerId) {
|
|
|
136
137
|
}
|
|
137
138
|
function normalizeThreatProvider(value, expectedDomain) {
|
|
138
139
|
const input = record(value);
|
|
140
|
+
const identity = record(input.provider);
|
|
141
|
+
const providerId = boundedThreatText(identity.id, 80);
|
|
142
|
+
const providerDefinition = providerId && Object.hasOwn(THREAT_INTELLIGENCE_PROVIDERS, providerId)
|
|
143
|
+
? THREAT_INTELLIGENCE_PROVIDERS[providerId] : null;
|
|
144
|
+
const withheld = (reason) => ({ provider: null, withheld: {
|
|
145
|
+
providerId: providerDefinition ? providerId : null,
|
|
146
|
+
label: providerDefinition ? `${providerDefinition.label} record` : 'Unrecognised provider record',
|
|
147
|
+
count: 1, reason,
|
|
148
|
+
} });
|
|
139
149
|
if (input.schema !== THREAT_INTELLIGENCE_SCHEMA
|
|
140
150
|
|| input.version !== THREAT_INTELLIGENCE_CONTRACT_VERSION)
|
|
141
|
-
return
|
|
151
|
+
return withheld('Unsupported provider format; evidence was withheld.');
|
|
142
152
|
const target = record(input.target);
|
|
143
153
|
if (target.type !== 'domain'
|
|
144
154
|
|| target.exposure !== 'registrable_domain'
|
|
145
155
|
|| target.value !== expectedDomain)
|
|
146
|
-
return
|
|
147
|
-
const identity = record(input.provider);
|
|
148
|
-
const providerId = boundedThreatText(identity.id, 80);
|
|
149
|
-
const providerDefinition = providerId ? THREAT_INTELLIGENCE_PROVIDERS[providerId] : null;
|
|
156
|
+
return withheld('The provider target did not match this Lookup; evidence was withheld.');
|
|
150
157
|
if (!providerId || !providerDefinition)
|
|
151
|
-
return
|
|
158
|
+
return withheld('The provider identity was not recognised; evidence was withheld.');
|
|
152
159
|
const state = boundedThreatText(input.state, 32);
|
|
153
160
|
if (!state || !THREAT_INTELLIGENCE_STATES.has(state))
|
|
154
|
-
return
|
|
161
|
+
return withheld('The provider result state was not supported; evidence was withheld.');
|
|
155
162
|
const observationInput = record(input.observation);
|
|
156
163
|
const limitations = Array.isArray(observationInput.limitations)
|
|
157
164
|
? observationInput.limitations
|
|
@@ -177,35 +184,40 @@ function normalizeThreatProvider(value, expectedDomain) {
|
|
|
177
184
|
? `${omitted} invalid or over-limit provider finding${omitted === 1 ? ' was' : 's were'} omitted from this view.`
|
|
178
185
|
: 'Provider findings were not a supported array and were withheld from this view.');
|
|
179
186
|
}
|
|
180
|
-
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
187
|
+
return { withheld: null, provider: {
|
|
188
|
+
schema: THREAT_INTELLIGENCE_SCHEMA,
|
|
189
|
+
version: THREAT_INTELLIGENCE_CONTRACT_VERSION,
|
|
190
|
+
provider: {
|
|
191
|
+
id: providerId,
|
|
192
|
+
label: providerDefinition.label,
|
|
193
|
+
},
|
|
194
|
+
target: {
|
|
195
|
+
type: 'domain',
|
|
196
|
+
value: expectedDomain,
|
|
197
|
+
exposure: 'registrable_domain',
|
|
198
|
+
},
|
|
199
|
+
state: projectionIncomplete && ['success', 'not_found'].includes(state) ? 'partial' : state,
|
|
200
|
+
detail: boundedThreatText(input.detail),
|
|
201
|
+
findings,
|
|
202
|
+
observation: {
|
|
203
|
+
observedAt: threatTimestamp(observationInput.observedAt),
|
|
204
|
+
limitations: limitations.slice(0, MAX_THREAT_INTELLIGENCE_LIMITATIONS),
|
|
205
|
+
complete: projectionIncomplete ? false : typeof observationInput.complete === 'boolean' ? observationInput.complete : null,
|
|
206
|
+
truncated: projectionTruncated ? true : typeof observationInput.truncated === 'boolean' ? observationInput.truncated : null,
|
|
207
|
+
},
|
|
208
|
+
} };
|
|
202
209
|
}
|
|
203
210
|
function projectThreatProviders(values, expectedDomain) {
|
|
204
211
|
const grouped = new Map();
|
|
212
|
+
const withheld = [];
|
|
213
|
+
const unexamined = Math.max(0, values.length - MAX_THREAT_INTELLIGENCE_PROVIDERS * 2);
|
|
205
214
|
for (const value of values.slice(0, MAX_THREAT_INTELLIGENCE_PROVIDERS * 2)) {
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
215
|
+
const projection = normalizeThreatProvider(value, expectedDomain);
|
|
216
|
+
if (projection.withheld) {
|
|
217
|
+
withheld.push(projection.withheld);
|
|
208
218
|
continue;
|
|
219
|
+
}
|
|
220
|
+
const provider = projection.provider;
|
|
209
221
|
const providerId = String(record(provider.provider).id || '');
|
|
210
222
|
if (!providerId)
|
|
211
223
|
continue;
|
|
@@ -218,20 +230,25 @@ function projectThreatProviders(values, expectedDomain) {
|
|
|
218
230
|
grouped.set(providerId, { first: provider, signatures: new Set([signature]) });
|
|
219
231
|
}
|
|
220
232
|
}
|
|
221
|
-
|
|
233
|
+
if (unexamined)
|
|
234
|
+
withheld.push({ providerId: null, label: 'Additional provider records', count: unexamined,
|
|
235
|
+
reason: 'The provider-record review bound was exceeded; these records were not examined.' });
|
|
236
|
+
const withheldIds = new Set(withheld.map((item) => item.providerId).filter(Boolean));
|
|
237
|
+
const providers = [...grouped.values()].slice(0, MAX_THREAT_INTELLIGENCE_PROVIDERS).map(({ first, signatures }) => (signatures.size === 1 && !withheldIds.has(String(record(first.provider).id)) && unexamined === 0
|
|
222
238
|
? first
|
|
223
239
|
: {
|
|
224
240
|
...first,
|
|
225
241
|
state: 'partial',
|
|
226
|
-
detail: 'Conflicting
|
|
242
|
+
detail: 'Conflicting or unvalidated provider records prevent a conclusive provider projection.',
|
|
227
243
|
findings: [],
|
|
228
244
|
observation: {
|
|
229
245
|
observedAt: null,
|
|
230
|
-
limitations: ['Conflicting
|
|
246
|
+
limitations: ['Conflicting or unvalidated records were withheld rather than resolved by array order.'],
|
|
231
247
|
complete: false,
|
|
232
248
|
truncated: null,
|
|
233
249
|
},
|
|
234
250
|
}));
|
|
251
|
+
return { providers, withheld };
|
|
235
252
|
}
|
|
236
253
|
function optionalBoundedText(value, maxLength) {
|
|
237
254
|
return value === undefined || (typeof value === 'string'
|
|
@@ -624,10 +641,12 @@ function validAvailabilityScalars(value) {
|
|
|
624
641
|
&& validOptionalNullableText(value.pageTitle, MAX_LOOKUP_RESPONSE_PAGE_TITLE_LENGTH)
|
|
625
642
|
&& validOptionalNullableText(value.dnssec, 40)
|
|
626
643
|
&& (value.deepScanComplete === undefined || typeof value.deepScanComplete === 'boolean')
|
|
644
|
+
&& (value.webCollectionQuality === undefined || isWebCollectionQuality(value.webCollectionQuality))
|
|
627
645
|
&& (value.hasMx === undefined || value.hasMx === null || typeof value.hasMx === 'boolean')
|
|
628
646
|
&& (value.hasNullMx === undefined || value.hasNullMx === null || typeof value.hasNullMx === 'boolean')
|
|
629
647
|
&& (value.hasSpf === undefined || value.hasSpf === null || typeof value.hasSpf === 'boolean')
|
|
630
648
|
&& (value.hasDmarc === undefined || value.hasDmarc === null || typeof value.hasDmarc === 'boolean')
|
|
649
|
+
&& (value.hasExternalPasswordForm === undefined || value.hasExternalPasswordForm === null || typeof value.hasExternalPasswordForm === 'boolean')
|
|
631
650
|
&& validOptionalStringArray(value.mxHosts, MAX_LOOKUP_DNS_RECORDS_PER_TYPE, 253);
|
|
632
651
|
}
|
|
633
652
|
function validLookupDomainIdentity(value) {
|
|
@@ -860,6 +879,7 @@ function parseCompactLookupHttpResponse(value, expectedDomain) {
|
|
|
860
879
|
|| typeof availability.confidence !== 'string'
|
|
861
880
|
|| !COMPACT_CONFIDENCE_LEVELS.has(availability.confidence)
|
|
862
881
|
|| (availability.deepScanComplete !== undefined && typeof availability.deepScanComplete !== 'boolean')
|
|
882
|
+
|| (availability.webCollectionQuality !== undefined && !isWebCollectionQuality(availability.webCollectionQuality))
|
|
863
883
|
|| (availability.bulkComparison !== undefined
|
|
864
884
|
&& !validCompactBulkComparison(availability.bulkComparison))
|
|
865
885
|
|| compactPageIdentity !== null && Object.hasOwn(compactPageIdentity, 'publicationMetadata')
|
|
@@ -968,11 +988,20 @@ function createLookupViewModel(response) {
|
|
|
968
988
|
? canonicalRegistrableDomain(response.registrableDomain)
|
|
969
989
|
?? canonicalRegistrableDomain(availability.domain)
|
|
970
990
|
: null;
|
|
971
|
-
const
|
|
991
|
+
const threatProjection = expectedThreatDomain
|
|
972
992
|
&& rawThreatIntelligence.version === THREAT_INTELLIGENCE_ENVELOPE_VERSION
|
|
973
993
|
&& Array.isArray(rawThreatIntelligence.providers)
|
|
974
994
|
? projectThreatProviders(rawThreatIntelligence.providers, expectedThreatDomain)
|
|
975
|
-
: []
|
|
995
|
+
: { providers: [], withheld: Object.keys(rawThreatIntelligence).length ? [{
|
|
996
|
+
providerId: null,
|
|
997
|
+
label: 'External-intelligence data',
|
|
998
|
+
count: Array.isArray(rawThreatIntelligence.providers) ? rawThreatIntelligence.providers.length : null,
|
|
999
|
+
reason: !expectedThreatDomain ? 'External intelligence was not bound to a domain Lookup and was withheld.'
|
|
1000
|
+
: rawThreatIntelligence.version !== THREAT_INTELLIGENCE_ENVELOPE_VERSION
|
|
1001
|
+
? 'The external-intelligence envelope uses an unsupported version; its records were withheld.'
|
|
1002
|
+
: 'External-intelligence records were not a supported array and were withheld.',
|
|
1003
|
+
}] : [] };
|
|
1004
|
+
const { providers, withheld } = threatProjection;
|
|
976
1005
|
const threatIntelligence = providers.length
|
|
977
1006
|
? { version: THREAT_INTELLIGENCE_ENVELOPE_VERSION, providers }
|
|
978
1007
|
: {};
|
|
@@ -1008,6 +1037,7 @@ function createLookupViewModel(response) {
|
|
|
1008
1037
|
sslbl,
|
|
1009
1038
|
threatIntelligence,
|
|
1010
1039
|
threatIntelligenceProviders: providers,
|
|
1040
|
+
threatIntelligenceWithheld: withheld,
|
|
1011
1041
|
dnsEvidence,
|
|
1012
1042
|
dnsRecords: record(dnsEvidence.records),
|
|
1013
1043
|
httpEvidence,
|
package/lib/lookup-response.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Lookup result shaping after the source plan has settled. This module starts no
|
|
2
2
|
// collection and preserves compact, diagnostics and enrichment response shapes.
|
|
3
|
+
import { withoutHttpDeliveryMetadata } from "./http-intelligence.mjs";
|
|
3
4
|
import { buildBulkComparisonEvidence } from "./bulk-comparison-evidence.mjs";
|
|
4
5
|
import { failedReverseDnsIntelligence, skippedReverseDnsIntelligence, } from "./dns-intelligence.mjs";
|
|
5
6
|
import { LOOKUP_DIAGNOSTICS_VERSION, LOOKUP_ERROR_CODES, LOOKUP_LEGACY_DIAGNOSTICS_VERSION, boundedSourceDetail, } from "./lookup-diagnostics.mjs";
|
|
6
7
|
import { createThreatIntelligenceResult } from "./threat-intelligence-contract.mjs";
|
|
7
8
|
import { THREATFOX_PROVIDER, URLHAUS_PROVIDER, URLSCAN_PROVIDER, } from "./lookup-threat-provider-inventory.mjs";
|
|
8
|
-
import { THREAT_INTELLIGENCE_ENVELOPE_VERSION } from "
|
|
9
|
+
import { THREAT_INTELLIGENCE_ENVELOPE_VERSION } from "../packages/analysis/threat-intelligence-types.mjs";
|
|
9
10
|
import { registryAccessDiagnosticFor } from "./registry-capabilities.mjs";
|
|
10
11
|
import { buildRegistryInsights } from "./registry-insights.mjs";
|
|
11
12
|
import { buildRegistrarStanding } from "./registrar-standing.mjs";
|
|
@@ -20,17 +21,6 @@ function withoutNestedPublicationMetadata(value) {
|
|
|
20
21
|
delete output.publicationMetadata;
|
|
21
22
|
return output;
|
|
22
23
|
}
|
|
23
|
-
function withoutNestedDeliveryMetadata(value) {
|
|
24
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
25
|
-
return value;
|
|
26
|
-
const output = { ...value };
|
|
27
|
-
if (output.response && typeof output.response === 'object' && !Array.isArray(output.response)) {
|
|
28
|
-
const response = { ...output.response };
|
|
29
|
-
delete response.deliveryMetadata;
|
|
30
|
-
output.response = response;
|
|
31
|
-
}
|
|
32
|
-
return output;
|
|
33
|
-
}
|
|
34
24
|
async function buildUnifiedLookupResponse(context) {
|
|
35
25
|
const { availabilityEnabled, availabilityPromise, classified, compact, dnsIntelligenceEnabled, fast, inspectCertificateWarningData, networkContextPromise, options, rdapEnabled, rdapPromise, registrarRdapPromise, reverseDnsEligible, reverseDnsPromise, securityTxtPromise, skipWhois, threatfoxIntelligencePromise, timing, urlhausIntelligencePromise, urlscanIntelligencePromise, whoisEnabled, whoisPromise, } = context;
|
|
36
26
|
const [rdapResult, whoisResult, availabilityResult, reverseDnsResult, registrarRdapResult, networkContextResult, securityTxtResult, urlscanIntelligenceResult, urlhausIntelligenceResult, threatfoxIntelligenceResult] = await Promise.allSettled([
|
|
@@ -100,7 +90,7 @@ async function buildUnifiedLookupResponse(context) {
|
|
|
100
90
|
whois = { skipped: true, detail: 'WHOIS is omitted in fast RDAP-only mode.' };
|
|
101
91
|
}
|
|
102
92
|
else if (Array.isArray(whoisChain)) {
|
|
103
|
-
whois = { chain: whoisChain, parsed: parseWhoisChain(whoisChain) };
|
|
93
|
+
whois = { chain: whoisChain, parsed: parseWhoisChain(whoisChain, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined) };
|
|
104
94
|
}
|
|
105
95
|
else {
|
|
106
96
|
whois = {
|
|
@@ -154,7 +144,7 @@ async function buildUnifiedLookupResponse(context) {
|
|
|
154
144
|
? 'skipped'
|
|
155
145
|
: whoisResult.status === 'rejected'
|
|
156
146
|
? 'error'
|
|
157
|
-
: whoisCollectionStatus(whoisChain);
|
|
147
|
+
: whoisCollectionStatus(whoisChain, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined);
|
|
158
148
|
const availabilityStatus = classified.type !== 'domain'
|
|
159
149
|
? 'not_applicable'
|
|
160
150
|
: !availabilityEnabled ? 'disabled'
|
|
@@ -298,7 +288,7 @@ async function buildUnifiedLookupResponse(context) {
|
|
|
298
288
|
availability: {
|
|
299
289
|
...compactAvailability,
|
|
300
290
|
...(richPageIdentity !== undefined ? { pageIdentity: withoutNestedPublicationMetadata(richPageIdentity) } : {}),
|
|
301
|
-
...(richHttp !== undefined ? { http:
|
|
291
|
+
...(richHttp !== undefined ? { http: withoutHttpDeliveryMetadata(richHttp) } : {}),
|
|
302
292
|
...(bulkComparison ? { bulkComparison } : {}),
|
|
303
293
|
},
|
|
304
294
|
diagnostics,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// provider payloads, and request errors. The ordinary final Lookup response
|
|
4
4
|
// remains the only authoritative and persistable result.
|
|
5
5
|
import { whoisCollectionStatus } from "./whois-authority.mjs";
|
|
6
|
-
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, } from "
|
|
6
|
+
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_RESULT_STATES, THREAT_INTELLIGENCE_SCHEMA, } from "../packages/analysis/threat-intelligence-types.mjs";
|
|
7
|
+
import { plannedLookupSources } from "./lookup-progress.mjs";
|
|
7
8
|
const RESULT_STATES = new Set([
|
|
8
9
|
'available',
|
|
9
10
|
'expiring',
|
|
@@ -38,28 +39,13 @@ function sourceTruncated(value) {
|
|
|
38
39
|
.some(([key, field]) => field === true && (key === 'truncated' || key.endsWith('Truncated'))));
|
|
39
40
|
}
|
|
40
41
|
function plannedLookupProgressSources(classified, options = {}) {
|
|
41
|
-
|
|
42
|
-
if (classified.type === 'domain') {
|
|
43
|
-
sources.push('domain_evidence', 'registrar_rdap', 'network_context');
|
|
44
|
-
if (options.securityTxt)
|
|
45
|
-
sources.push('security_txt');
|
|
46
|
-
if (options.externalIntelligence)
|
|
47
|
-
sources.push('external_intelligence');
|
|
48
|
-
if (options.malwareHostIntelligence)
|
|
49
|
-
sources.push('malware_host_intelligence');
|
|
50
|
-
if (options.malwareIocIntelligence)
|
|
51
|
-
sources.push('malware_ioc_intelligence');
|
|
52
|
-
}
|
|
53
|
-
else if (classified.type === 'ipv4' || classified.type === 'ipv6') {
|
|
54
|
-
sources.push('reverse_dns');
|
|
55
|
-
}
|
|
56
|
-
return Object.freeze(sources);
|
|
42
|
+
return plannedLookupSources(classified.type, 'deep', options);
|
|
57
43
|
}
|
|
58
|
-
function normalizedState(source, outcome, value) {
|
|
44
|
+
function normalizedState(source, outcome, value, requestedDomain, requested = true) {
|
|
59
45
|
if (outcome === 'rejected')
|
|
60
46
|
return 'error';
|
|
61
47
|
if (value === null || value === undefined)
|
|
62
|
-
return 'skipped';
|
|
48
|
+
return source === 'rdap' && requested ? 'unsupported' : 'skipped';
|
|
63
49
|
if (source === 'rdap') {
|
|
64
50
|
const rdap = record(value);
|
|
65
51
|
if (rdap.upstreamStatus === 404)
|
|
@@ -69,7 +55,7 @@ function normalizedState(source, outcome, value) {
|
|
|
69
55
|
return sourceTruncated(rdap) ? 'partial' : 'success';
|
|
70
56
|
}
|
|
71
57
|
if (source === 'whois') {
|
|
72
|
-
const status = whoisCollectionStatus(value);
|
|
58
|
+
const status = whoisCollectionStatus(value, requestedDomain);
|
|
73
59
|
return status === 'complete' ? 'success' : status;
|
|
74
60
|
}
|
|
75
61
|
if (source === 'domain_evidence') {
|
|
@@ -94,8 +80,8 @@ function normalizedState(source, outcome, value) {
|
|
|
94
80
|
return 'skipped';
|
|
95
81
|
return 'error';
|
|
96
82
|
}
|
|
97
|
-
function normalizeLookupSourceSettlement(source, outcome, value) {
|
|
98
|
-
const state = normalizedState(source, outcome, value);
|
|
83
|
+
function normalizeLookupSourceSettlement(source, outcome, value, requestedDomain, requested = true) {
|
|
84
|
+
const state = normalizedState(source, outcome, value, requestedDomain, requested);
|
|
99
85
|
const sourceRecord = record(value);
|
|
100
86
|
const threatObservation = THREAT_INTELLIGENCE_SOURCES.has(source)
|
|
101
87
|
? record(sourceRecord.observation)
|
package/lib/lookup.mjs
CHANGED
|
@@ -170,7 +170,7 @@ async function runUnifiedLookup(classified, options = {}) {
|
|
|
170
170
|
]);
|
|
171
171
|
const notify = (source, outcome, value) => {
|
|
172
172
|
try {
|
|
173
|
-
options.onSourceSettled?.(normalizeLookupSourceSettlement(source, outcome, value));
|
|
173
|
+
options.onSourceSettled?.(normalizeLookupSourceSettlement(source, outcome, value, classified.type === 'domain' ? classified.registrableDomain ?? classified.value : undefined, source !== 'rdap' || rdapEnabled));
|
|
174
174
|
}
|
|
175
175
|
catch {
|
|
176
176
|
// Presentation callbacks must never change evidence collection.
|
package/lib/operation-budget.mjs
CHANGED
|
@@ -34,6 +34,7 @@ const OPERATION_FEATURES = Object.freeze({
|
|
|
34
34
|
AVAILABILITY_FAST: 'availability_fast',
|
|
35
35
|
AVAILABILITY_DEEP: 'availability_deep',
|
|
36
36
|
CERTIFICATE_TRANSPARENCY: 'certificate_transparency',
|
|
37
|
+
DOMAIN_FEED_SEARCH: 'domain_feed_search',
|
|
37
38
|
DOMAIN_POSTURE: 'domain_posture',
|
|
38
39
|
});
|
|
39
40
|
const OPERATION_FEATURE_CLASSES = Object.freeze({
|
|
@@ -47,6 +48,7 @@ const OPERATION_FEATURE_CLASSES = Object.freeze({
|
|
|
47
48
|
[OPERATION_FEATURES.AVAILABILITY_FAST]: OPERATION_CLASSES.REGISTRY_LIGHT,
|
|
48
49
|
[OPERATION_FEATURES.AVAILABILITY_DEEP]: OPERATION_CLASSES.REGISTRY_DEEP,
|
|
49
50
|
[OPERATION_FEATURES.CERTIFICATE_TRANSPARENCY]: OPERATION_CLASSES.CERTIFICATE_SEARCH,
|
|
51
|
+
[OPERATION_FEATURES.DOMAIN_FEED_SEARCH]: OPERATION_CLASSES.CERTIFICATE_SEARCH,
|
|
50
52
|
[OPERATION_FEATURES.DOMAIN_POSTURE]: OPERATION_CLASSES.POSTURE_AUDIT,
|
|
51
53
|
});
|
|
52
54
|
// The browser currently runs at most 12 fast Bulk workers, 4 deep Bulk
|
|
@@ -87,6 +89,8 @@ function operationFeatureFor(feature, { fast = false, compact = false } = {}) {
|
|
|
87
89
|
return OPERATION_FEATURES.WHOIS;
|
|
88
90
|
if (feature === 'certificate_transparency')
|
|
89
91
|
return OPERATION_FEATURES.CERTIFICATE_TRANSPARENCY;
|
|
92
|
+
if (feature === 'domain_feed_search')
|
|
93
|
+
return OPERATION_FEATURES.DOMAIN_FEED_SEARCH;
|
|
90
94
|
if (feature === 'domain_posture')
|
|
91
95
|
return OPERATION_FEATURES.DOMAIN_POSTURE;
|
|
92
96
|
return null;
|