@slicedearth/whoisleuth-cli 2.3.0 → 2.5.1
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/NOTICE +8 -2
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -408
- package/cli/artifact-validation/case-response.mjs +65 -44
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +15 -9
- package/cli/artifact-validation/signed-review.mjs +136 -68
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +92 -24
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +40 -5
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +4 -2
- package/cli/bulk-output.mjs +29 -3
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +212 -0
- package/cli/case-pack.mjs +50 -32
- 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 -206
- package/cli/command-definition.mjs +329 -0
- package/cli/command-reference.mjs +94 -1357
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +8 -6
- package/cli/ct-event-intake.mjs +16 -16
- package/cli/discover.mjs +5 -4
- package/cli/discovery-observation-snapshot.mjs +46 -18
- package/cli/discovery-scan-command-runner.mjs +5 -3
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +5 -5
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +151 -103
- package/cli/errors.mjs +37 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +18 -8
- package/cli/evidence-signing.mjs +5 -4
- package/cli/external-observation-mapping.mjs +9 -10
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +24 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +33 -0
- package/cli/inline-command-families.mjs +6 -46
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +107 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +8 -10
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +38 -147
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +256 -35
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/local-mmdb-review.mjs +197 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +33 -39
- package/cli/lookup-diff.mjs +47 -12
- package/cli/lookup-plan.mjs +16 -14
- package/cli/lookup-reconcile.mjs +15 -12
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +75 -21
- package/cli/manual.mjs +1 -0
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +45 -31
- package/cli/offline-evidence-review.mjs +39 -6
- package/cli/output-file.mjs +31 -5
- package/cli/posture.mjs +1 -1
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +10 -9
- package/cli/review-arguments.mjs +131 -0
- package/cli/review-command-definitions.mjs +273 -0
- package/cli/review-command-runner.mjs +104 -149
- package/cli/risk-calibration.mjs +8 -8
- package/cli/runner.mjs +74 -37
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +63 -0
- package/cli/sharing-review.mjs +8 -80
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +75 -84
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +2 -2
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-arguments.mjs +86 -0
- package/cli/workflow-command-definitions.mjs +78 -0
- package/cli/workflow-command-runner.mjs +75 -13
- package/docs/cli-reference.md +103 -30
- package/docs/cli.md +510 -25
- package/docs/contextual-reviews.md +155 -0
- package/lib/abort.mjs +4 -1
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +27 -754
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +2 -290
- package/lib/browser-library-profile.mjs +99 -130
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +89 -46
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +36 -22
- package/lib/dns-inheritance-review.mjs +207 -0
- package/lib/dns-intelligence.mjs +44 -29
- package/lib/dns-mx.mjs +1 -1
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +18 -14
- package/lib/domain-change-review.mjs +40 -16
- package/lib/domain-control-flight-recorder.mjs +301 -124
- package/lib/domain-control-manifest.mjs +126 -58
- package/lib/domain-posture-analysis.mjs +41 -11
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +120 -32
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +568 -0
- package/lib/evidence-export.mjs +12 -593
- package/lib/evidence-report.mjs +7 -2
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +50 -4
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +2742 -1034
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +87 -85
- package/lib/http-evidence-bounds.mjs +2 -7
- package/lib/http-intelligence.mjs +52 -27
- 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-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +2 -1
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +79 -13
- package/lib/lookup-response-contract.mjs +1077 -0
- package/lib/lookup-response.mjs +11 -24
- package/lib/lookup-source-progress.mjs +24 -35
- package/lib/lookup.mjs +49 -28
- package/lib/observed-network-context.mjs +43 -27
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +87 -134
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +79 -24
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +26 -14
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +2 -36
- package/lib/registrar-standing-contract.mjs +2 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +7 -3
- package/lib/registry-capability-catalogue.mjs +29 -20
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +2 -338
- package/lib/risk-scoring.mjs +2 -345
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/security-txt.mjs +10 -7
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +35 -0
- package/lib/smtp-transport-review.mjs +75 -34
- package/lib/sslbl-certificates.generated.mjs +141 -138
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +2 -1
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +2 -36
- package/lib/typosquat-generator.mjs +29 -16
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-enrichment.mjs +388 -0
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +55 -5
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-parser.mjs +5 -3
- package/lib/whois-transport.mjs +23 -7
- package/package.json +14 -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 +345 -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-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-links.mjs +135 -0
- package/packages/cases/case-evidence-model.mjs +229 -69
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +55 -0
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +98 -48
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +149 -0
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +4 -3
- package/packages/cases/case-record-operations.mjs +123 -133
- package/packages/cases/case-record-projection.mjs +30 -20
- package/packages/cases/case-record-version-input.mjs +19 -0
- package/packages/cases/case-report.mjs +114 -67
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +756 -0
- package/packages/cases/case-response-model.mjs +49 -1129
- package/packages/cases/case-response-outcomes.mjs +338 -0
- package/packages/cases/case-response-packet-render.mjs +157 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +58 -593
- package/packages/cases/case-response-records.mjs +93 -0
- package/packages/cases/case-response-review-inputs.mjs +382 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +26 -1
- package/packages/cases/case-workflow-metadata.mjs +225 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +3 -2
- package/packages/comparison/relationship-evidence.mjs +103 -14
- package/packages/comparison/relationship-provenance.mjs +99 -0
- package/packages/contracts/analyst-interchange.mjs +234 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +370 -114
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/context-review.mjs +24 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
- package/packages/contracts/domain-control-manifest.mjs +128 -74
- package/packages/contracts/domain-control-monitor.mjs +59 -45
- package/packages/contracts/domain-control-review.mjs +200 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +18 -0
- package/packages/contracts/ip-address.mjs +44 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +31 -0
- package/packages/contracts/offline-comparison.mjs +4 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +219 -238
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +108 -0
- package/packages/evidence/observation.mjs +18 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +93 -61
- package/packages/interchange/managed-indicator-export.mjs +35 -0
- package/packages/interchange/managed-indicator-set.mjs +210 -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 +389 -0
- package/packages/investigation/acquisition-decision-packet.mjs +1 -1
- package/packages/investigation/bulk-mail-exposure.mjs +9 -14
- 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 +81 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +100 -0
- package/packages/investigation/domain-transition-review.mjs +47 -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/intake-report.mjs +19 -0
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/link-intake.mjs +179 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +195 -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/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +160 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +39 -31
- package/packages/workspace/bulk-session-model.mjs +112 -27
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +1 -1
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +45 -6
- package/packages/workspace/page-baseline.mjs +20 -17
- package/packages/workspace/relationship-observation-model.mjs +54 -35
- package/packages/workspace/shortlist-model.mjs +14 -5
- package/packages/workspace/watchlist-history.mjs +30 -14
- package/packages/workspace/watchlist-store.mjs +24 -6
- package/packages/workspace/website-snapshot-model.mjs +98 -33
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +90 -25
- package/third-party-notices.txt +577 -17
- package/lib/hostname.mjs +0 -38
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by tools/unicode-confusable-audit.mts from the pinned Unicode
|
|
2
2
|
// UTS #39 data file. Do not edit this projection by hand.
|
|
3
|
-
export const GENERATED_CONFUSABLE_MAPPING_VERSION = "tr39-
|
|
3
|
+
export const GENERATED_CONFUSABLE_MAPPING_VERSION = "tr39-18.0.0-bounded-ascii-v3";
|
|
4
4
|
export const GENERATED_CONFUSABLE_SOURCE = Object.freeze({
|
|
5
5
|
"standard": "Unicode UTS #39",
|
|
6
|
-
"unicodeVersion": "
|
|
7
|
-
"url": "https://www.unicode.org/Public/
|
|
8
|
-
"sha256": "
|
|
6
|
+
"unicodeVersion": "18.0.0",
|
|
7
|
+
"url": "https://www.unicode.org/Public/18.0.0/security/confusables.txt",
|
|
8
|
+
"sha256": "6ed3ee967c9dfdf6677d563c9985182fbc50a2efb7d6059cd57b2e2ce18f5b92",
|
|
9
9
|
"license": "Unicode-3.0"
|
|
10
10
|
});
|
|
11
11
|
export const GENERATED_CONFUSABLE_POLICY = Object.freeze({
|
|
@@ -24,33 +24,33 @@ export const GENERATED_CONFUSABLE_POLICY = Object.freeze({
|
|
|
24
24
|
"totalMappings": 312
|
|
25
25
|
});
|
|
26
26
|
export const GENERATED_CONFUSABLE_STATS = Object.freeze({
|
|
27
|
-
"sourceBytes":
|
|
28
|
-
"sourceLines":
|
|
29
|
-
"parsedMappings":
|
|
30
|
-
"eligibleMappings":
|
|
31
|
-
"projectedMappings":
|
|
32
|
-
"generationMappings":
|
|
27
|
+
"sourceBytes": 763128,
|
|
28
|
+
"sourceLines": 9863,
|
|
29
|
+
"parsedMappings": 6712,
|
|
30
|
+
"eligibleMappings": 255,
|
|
31
|
+
"projectedMappings": 126,
|
|
32
|
+
"generationMappings": 115,
|
|
33
33
|
"rejectedMalformedLines": 0,
|
|
34
|
-
"rejectedOutsidePolicy":
|
|
34
|
+
"rejectedOutsidePolicy": 5780,
|
|
35
35
|
"skeletonCapHits": 0,
|
|
36
|
-
"generationCapHits":
|
|
36
|
+
"generationCapHits": 4
|
|
37
37
|
});
|
|
38
38
|
export const GENERATED_CONFUSABLE_GROUPS = Object.freeze({
|
|
39
|
-
a: "
|
|
40
|
-
b: "
|
|
41
|
-
c: "
|
|
42
|
-
d: "
|
|
39
|
+
a: "аαɑꭤ",
|
|
40
|
+
b: "ьƄ𝈇",
|
|
41
|
+
c: "сϲⅽᴄⲥ𐐽ᲃ",
|
|
42
|
+
d: "ԁⅾꓒ𐆋",
|
|
43
43
|
e: "еεҽꬲ",
|
|
44
|
-
f: "
|
|
44
|
+
f: "ϝꞙƒʄẝꬵ𐅾ք",
|
|
45
45
|
g: "ɡƍᶃց",
|
|
46
46
|
h: "һհ",
|
|
47
47
|
i: "іιıɪɩⲓꙇւ",
|
|
48
|
-
j: "
|
|
48
|
+
j: "јϳȷյ",
|
|
49
49
|
k: "κк",
|
|
50
|
-
l: "
|
|
51
|
-
m: "
|
|
50
|
+
l: "ӏⅼǀꓲꞁꟾ",
|
|
51
|
+
m: "мⅿՠ",
|
|
52
52
|
n: "ոռ",
|
|
53
|
-
o: "
|
|
53
|
+
o: "оοօᴏⲟᴑꬽᲂσϭ𐐬",
|
|
54
54
|
p: "рρϱⲣþƿϸⳏ",
|
|
55
55
|
q: "ԛգզ",
|
|
56
56
|
r: "гꭇꭈᴦⲅ",
|
|
@@ -58,25 +58,25 @@ export const GENERATED_CONFUSABLE_GROUPS = Object.freeze({
|
|
|
58
58
|
t: "тτ",
|
|
59
59
|
u: "υսᴜꭎʋꞟꭒ",
|
|
60
60
|
v: "ѵνᴠ",
|
|
61
|
-
w: "
|
|
61
|
+
w: "ԝաᴡшɯꟺѡⲽ",
|
|
62
62
|
x: "хχ",
|
|
63
63
|
y: "уүʏγⲩɣᶌỿꭚ",
|
|
64
|
-
z: "
|
|
64
|
+
z: "ᴢⱬⲍ",
|
|
65
65
|
});
|
|
66
66
|
export const GENERATED_GENERATION_CONFUSABLE_GROUPS = Object.freeze({
|
|
67
|
-
a: "
|
|
68
|
-
b: "
|
|
69
|
-
c: "
|
|
70
|
-
d: "
|
|
67
|
+
a: "аαɑꭤ",
|
|
68
|
+
b: "ь𝈇",
|
|
69
|
+
c: "сᴄⲥ𐐽ᲃ",
|
|
70
|
+
d: "ԁꓒ𐆋",
|
|
71
71
|
e: "еεҽꬲ",
|
|
72
|
-
f: "
|
|
72
|
+
f: "ϝꞙƒʄẝꬵ𐅾ք",
|
|
73
73
|
g: "ɡƍᶃց",
|
|
74
74
|
h: "һհ",
|
|
75
75
|
i: "іιıɪɩⲓꙇւ",
|
|
76
|
-
j: "
|
|
76
|
+
j: "јϳȷյ",
|
|
77
77
|
k: "κк",
|
|
78
|
-
l: "
|
|
79
|
-
m: "
|
|
78
|
+
l: "ӏǀꓲꞁꟾ",
|
|
79
|
+
m: "мՠ",
|
|
80
80
|
n: "ոռ",
|
|
81
81
|
o: "оοօᴏⲟᴑꬽσ",
|
|
82
82
|
p: "рρⲣþƿϸⳏ",
|
|
@@ -86,8 +86,8 @@ export const GENERATED_GENERATION_CONFUSABLE_GROUPS = Object.freeze({
|
|
|
86
86
|
t: "тτ",
|
|
87
87
|
u: "υսᴜꭎʋꞟꭒ",
|
|
88
88
|
v: "ѵνᴠ",
|
|
89
|
-
w: "
|
|
89
|
+
w: "ԝաᴡшɯꟺѡⲽ",
|
|
90
90
|
x: "хχ",
|
|
91
91
|
y: "уүʏγⲩɣᶌỿ",
|
|
92
|
-
z: "
|
|
92
|
+
z: "ᴢⱬⲍ",
|
|
93
93
|
});
|
package/lib/html-signals.mjs
CHANGED
|
@@ -43,13 +43,8 @@ const HAS_CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f-\u009f]|\p{Default_Ignora
|
|
|
43
43
|
// scoped to resource tags, not every <a href> - an outbound link to the
|
|
44
44
|
// real site is normal on all sorts of pages; a resource pulled live from it
|
|
45
45
|
// during page load is not.
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const IDENTITY_TAG_RE = /<(html|link|meta|form)\b[^>]{0,4096}>/gi;
|
|
49
|
-
const OVERSIZED_IDENTITY_TAG_RE = /<(?:html|link|meta|form)\b[^>]{4097}/i;
|
|
50
|
-
const RELATIONSHIP_TAG_RE = /<(a|img|script|link|iframe|frame|source|video|audio|object|embed)\b[^>]{0,4096}>/gi;
|
|
51
|
-
const OVERSIZED_RELATIONSHIP_TAG_RE = /<(?:a|img|script|link|iframe|frame|source|video|audio|object|embed)\b[^>]{4097}/i;
|
|
52
|
-
const ATTRIBUTE_RE = /([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
46
|
+
const IDENTITY_TAGS = new Set(['html', 'link', 'meta', 'form']);
|
|
47
|
+
const RELATIONSHIP_TAGS = new Set(['a', 'img', 'script', 'link', 'iframe', 'frame', 'source', 'video', 'audio', 'object', 'embed']);
|
|
53
48
|
const LANGUAGE_TAG_RE = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/i;
|
|
54
49
|
const HOSTNAME_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
55
50
|
const RESOURCE_LINK_RELS = new Set(['stylesheet', 'icon', 'preload', 'prefetch', 'modulepreload', 'manifest']);
|
|
@@ -57,9 +52,6 @@ const RISKY_DOWNLOAD_EXTENSIONS = new Set([
|
|
|
57
52
|
'7z', 'apk', 'bat', 'cmd', 'dmg', 'docm', 'exe', 'img', 'iso', 'jar', 'js',
|
|
58
53
|
'msi', 'pkg', 'ps1', 'rar', 'scr', 'vbs', 'xlsm', 'zip',
|
|
59
54
|
]);
|
|
60
|
-
const HTML_COMMENT_RE = /<!--[\s\S]*?(?:-->|$)/g;
|
|
61
|
-
const RAW_TEXT_BLOCK_RE = /<(script|style|textarea|template)\b([^>]*)>[\s\S]*?<\/\1\s*>/gi;
|
|
62
|
-
const NON_EXECUTING_RAW_TEXT_BLOCK_RE = /<(style|textarea|template)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
|
63
55
|
function stripWwwPrefix(host) {
|
|
64
56
|
return host.toLowerCase().replace(/^www\./, '');
|
|
65
57
|
}
|
|
@@ -72,24 +64,19 @@ function boundedHtmlText(value, maxLength, ellipsis = false) {
|
|
|
72
64
|
return null;
|
|
73
65
|
if (text.length <= maxLength)
|
|
74
66
|
return text;
|
|
75
|
-
return ellipsis ? `${text.slice(0, maxLength)}…` : text.slice(0, maxLength);
|
|
67
|
+
return ellipsis ? `${text.slice(0, Math.max(0, maxLength - 1))}…` : text.slice(0, maxLength);
|
|
76
68
|
}
|
|
77
|
-
function extractExternalAssetHosts(
|
|
69
|
+
function extractExternalAssetHosts(analysis, ownDomain, documentUrl) {
|
|
78
70
|
const ownHost = stripWwwPrefix(ownDomain);
|
|
79
71
|
const hosts = new Set();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
while ((match = ASSET_TAG_RE.exec(html))) {
|
|
83
|
-
const resourceUrl = match[1];
|
|
84
|
-
if (!resourceUrl)
|
|
72
|
+
for (const element of analysis.elements) {
|
|
73
|
+
if (!element.html || !['img', 'script', 'link'].includes(element.name))
|
|
85
74
|
continue;
|
|
86
|
-
const
|
|
87
|
-
|
|
75
|
+
const attribute = element.attributes.find((attribute) => attribute.name === (element.name === 'link' ? 'href' : 'src'));
|
|
76
|
+
const resource = normalizeIdentityUrl(attribute?.value, analysis.effectiveBaseUrl ?? documentUrl);
|
|
77
|
+
if (!resource)
|
|
88
78
|
continue;
|
|
89
|
-
const
|
|
90
|
-
if (!matchedHost || HAS_CONTROL_CHARACTER_RE.test(matchedHost))
|
|
91
|
-
continue;
|
|
92
|
-
const host = stripWwwPrefix(matchedHost);
|
|
79
|
+
const host = stripWwwPrefix(new URL(resource.url).hostname);
|
|
93
80
|
if (host && host !== ownHost)
|
|
94
81
|
hosts.add(host);
|
|
95
82
|
if (hosts.size >= MAX_EXTERNAL_ASSET_HOSTS)
|
|
@@ -97,32 +84,6 @@ function extractExternalAssetHosts(html, ownDomain) {
|
|
|
97
84
|
}
|
|
98
85
|
return [...hosts];
|
|
99
86
|
}
|
|
100
|
-
function parseAttributes(tag) {
|
|
101
|
-
const start = tag.search(/\s/);
|
|
102
|
-
if (start === -1)
|
|
103
|
-
return new Map();
|
|
104
|
-
const attributes = new Map();
|
|
105
|
-
ATTRIBUTE_RE.lastIndex = 0;
|
|
106
|
-
let match;
|
|
107
|
-
while ((match = ATTRIBUTE_RE.exec(tag.slice(start)))) {
|
|
108
|
-
const name = (match[1] ?? '').toLowerCase();
|
|
109
|
-
if (!name)
|
|
110
|
-
continue;
|
|
111
|
-
if (!attributes.has(name))
|
|
112
|
-
attributes.set(name, match[2] ?? match[3] ?? match[4] ?? '');
|
|
113
|
-
}
|
|
114
|
-
return attributes;
|
|
115
|
-
}
|
|
116
|
-
function markupForTagParsing(html) {
|
|
117
|
-
return html
|
|
118
|
-
.replace(HTML_COMMENT_RE, ' ')
|
|
119
|
-
.replace(RAW_TEXT_BLOCK_RE, '<$1$2>');
|
|
120
|
-
}
|
|
121
|
-
function markupForTrackingIdentifiers(html) {
|
|
122
|
-
return html
|
|
123
|
-
.replace(HTML_COMMENT_RE, ' ')
|
|
124
|
-
.replace(NON_EXECUTING_RAW_TEXT_BLOCK_RE, ' ');
|
|
125
|
-
}
|
|
126
87
|
function normalizeLanguage(value) {
|
|
127
88
|
if (typeof value !== 'string' || value.length > 35 || HAS_CONTROL_CHARACTER_RE.test(value))
|
|
128
89
|
return null;
|
|
@@ -295,10 +256,9 @@ function trackingIdentifiers(html) {
|
|
|
295
256
|
truncated,
|
|
296
257
|
};
|
|
297
258
|
}
|
|
298
|
-
function extractPageRelationships(
|
|
259
|
+
function extractPageRelationships(analysis, domain, options = {}) {
|
|
299
260
|
const documentUrl = resolvedBaseUrl(domain, options.baseUrl);
|
|
300
261
|
const baseUrl = resolvedBaseUrl(domain, options.effectiveBaseUrl ?? documentUrl);
|
|
301
|
-
const markup = typeof options.tagMarkup === 'string' ? options.tagMarkup : markupForTagParsing(html);
|
|
302
262
|
const baseOrigin = options.documentOrigin ?? new URL(documentUrl).origin;
|
|
303
263
|
const resourceKeys = new Set();
|
|
304
264
|
const resourceOrigins = new Set();
|
|
@@ -312,25 +272,27 @@ function extractPageRelationships(html, domain, options = {}) {
|
|
|
312
272
|
let riskyDownloadCount = 0;
|
|
313
273
|
let tagsExamined = 0;
|
|
314
274
|
let discardedUrls = 0;
|
|
315
|
-
let tagLimitReached =
|
|
275
|
+
let tagLimitReached = analysis.inputLimitReached || analysis.tagLimitReached;
|
|
316
276
|
let resourceOriginLimitReached = false;
|
|
317
277
|
let embeddedOriginLimitReached = false;
|
|
318
278
|
let contactDomainLimitReached = false;
|
|
319
279
|
let downloadOriginLimitReached = false;
|
|
320
280
|
let downloadFileTypeLimitReached = false;
|
|
321
281
|
let perTagLimitReached = false;
|
|
322
|
-
let
|
|
323
|
-
|
|
324
|
-
|
|
282
|
+
let resourceCountLimitReached = false;
|
|
283
|
+
let resourceIdentityTruncated = false;
|
|
284
|
+
for (const element of analysis.elements) {
|
|
285
|
+
if (!element.html || !RELATIONSHIP_TAGS.has(element.name))
|
|
286
|
+
continue;
|
|
325
287
|
if (tagsExamined >= MAX_RESOURCE_TAGS) {
|
|
326
288
|
tagLimitReached = true;
|
|
327
289
|
break;
|
|
328
290
|
}
|
|
329
291
|
tagsExamined += 1;
|
|
330
|
-
const tagName =
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
292
|
+
const tagName = element.name;
|
|
293
|
+
const attributes = new Map(element.attributes.map(({ name, value }) => [name, value]));
|
|
294
|
+
if (element.attributesTruncated)
|
|
295
|
+
tagLimitReached = true;
|
|
334
296
|
if (tagName === 'a') {
|
|
335
297
|
const href = attributes.get('href');
|
|
336
298
|
const contacts = mailtoDomains(href);
|
|
@@ -376,8 +338,14 @@ function extractPageRelationships(html, domain, options = {}) {
|
|
|
376
338
|
discardedUrls += 1;
|
|
377
339
|
continue;
|
|
378
340
|
}
|
|
341
|
+
if (normalized.pathTruncated)
|
|
342
|
+
resourceIdentityTruncated = true;
|
|
379
343
|
const key = `${reference.type}:${normalized.url}`;
|
|
380
344
|
if (!resourceKeys.has(key)) {
|
|
345
|
+
if (resourceKeys.size >= MAX_RESOURCE_TAGS) {
|
|
346
|
+
resourceCountLimitReached = true;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
381
349
|
resourceKeys.add(key);
|
|
382
350
|
byType[reference.type] += 1;
|
|
383
351
|
}
|
|
@@ -389,10 +357,15 @@ function extractPageRelationships(html, domain, options = {}) {
|
|
|
389
357
|
embeddedOriginLimitReached = true;
|
|
390
358
|
}
|
|
391
359
|
}
|
|
392
|
-
const tracking = trackingIdentifiers(
|
|
360
|
+
const tracking = trackingIdentifiers([
|
|
361
|
+
...analysis.elements.filter((element) => element.html).flatMap((element) => element.attributes.map((attribute) => attribute.value)),
|
|
362
|
+
...analysis.scripts.map((script) => script.inlineContent),
|
|
363
|
+
].join('\n'));
|
|
364
|
+
if (analysis.scriptLimitReached || analysis.inlineLimitReached)
|
|
365
|
+
tracking.truncated = true;
|
|
393
366
|
const truncated = tagLimitReached || resourceOriginLimitReached || embeddedOriginLimitReached
|
|
394
367
|
|| contactDomainLimitReached || downloadOriginLimitReached || downloadFileTypeLimitReached
|
|
395
|
-
|| perTagLimitReached || tracking.truncated;
|
|
368
|
+
|| perTagLimitReached || resourceCountLimitReached || resourceIdentityTruncated || tracking.truncated;
|
|
396
369
|
const limitations = [];
|
|
397
370
|
if (tagLimitReached)
|
|
398
371
|
limitations.push(`Resource parsing reached the ${MAX_RESOURCE_TAGS}-tag or ${MAX_IDENTITY_TAG_LENGTH}-character tag limit.`);
|
|
@@ -408,6 +381,10 @@ function extractPageRelationships(html, domain, options = {}) {
|
|
|
408
381
|
limitations.push(`Only the first ${MAX_DOWNLOAD_FILE_TYPES} risky download file types were retained.`);
|
|
409
382
|
if (perTagLimitReached)
|
|
410
383
|
limitations.push(`Some srcset URL candidates could not be safely enumerated within the ${MAX_URLS_PER_TAG}-candidate per-tag boundary.`);
|
|
384
|
+
if (resourceCountLimitReached)
|
|
385
|
+
limitations.push(`Only the first ${MAX_RESOURCE_TAGS} distinct resource references were examined.`);
|
|
386
|
+
if (resourceIdentityTruncated)
|
|
387
|
+
limitations.push('Overlong resource paths were reduced to origins; distinct resource counts may be incomplete.');
|
|
411
388
|
if (tracking.truncated)
|
|
412
389
|
limitations.push(`Only the first ${MAX_TRACKING_IDENTIFIERS} tracking identifiers were retained.`);
|
|
413
390
|
return {
|
|
@@ -415,7 +392,7 @@ function extractPageRelationships(html, domain, options = {}) {
|
|
|
415
392
|
count: resourceKeys.size,
|
|
416
393
|
byType,
|
|
417
394
|
externalOrigins: [...resourceOrigins].sort(),
|
|
418
|
-
truncated: tagLimitReached || resourceOriginLimitReached || perTagLimitReached,
|
|
395
|
+
truncated: tagLimitReached || resourceOriginLimitReached || perTagLimitReached || resourceCountLimitReached || resourceIdentityTruncated,
|
|
419
396
|
},
|
|
420
397
|
embeddedOrigins: [...embeddedOrigins].sort(),
|
|
421
398
|
contactDomains: [...contactDomains].sort(),
|
|
@@ -445,12 +422,9 @@ function metaRefreshTarget(content) {
|
|
|
445
422
|
}
|
|
446
423
|
function extractPageIdentity(html, domain, options = {}) {
|
|
447
424
|
const documentUrl = resolvedBaseUrl(domain, options.baseUrl);
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const baseUrl = resolvedBaseUrl(domain, options.effectiveBaseUrl ?? baseAnalysis?.effectiveBaseUrl ?? documentUrl);
|
|
452
|
-
const baseHrefState = options.baseHrefState ?? baseAnalysis?.baseHrefState ?? 'absent';
|
|
453
|
-
const tagMarkup = markupForTagParsing(html);
|
|
425
|
+
const analysis = options.htmlAnalysis ?? analyzeStaticHtml(html, { baseUrl: documentUrl, includeVisibleText: true });
|
|
426
|
+
const baseUrl = resolvedBaseUrl(domain, options.effectiveBaseUrl ?? analysis.effectiveBaseUrl ?? documentUrl);
|
|
427
|
+
const baseHrefState = options.baseHrefState ?? analysis.baseHrefState;
|
|
454
428
|
const parsedDocument = new URL(documentUrl);
|
|
455
429
|
const baseOrigin = options.documentOrigin ?? parsedDocument.origin;
|
|
456
430
|
const baseUsesHttps = parsedDocument.protocol === 'https:';
|
|
@@ -469,19 +443,19 @@ function extractPageIdentity(html, domain, options = {}) {
|
|
|
469
443
|
let discardedUrls = 0;
|
|
470
444
|
let formLimitReached = false;
|
|
471
445
|
let originLimitReached = false;
|
|
472
|
-
let tagLimitReached =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
446
|
+
let tagLimitReached = analysis.inputLimitReached || analysis.tagLimitReached;
|
|
447
|
+
for (const element of analysis.elements) {
|
|
448
|
+
if (!element.html || !IDENTITY_TAGS.has(element.name))
|
|
449
|
+
continue;
|
|
476
450
|
if (tagsExamined >= MAX_IDENTITY_TAGS) {
|
|
477
451
|
tagLimitReached = true;
|
|
478
452
|
break;
|
|
479
453
|
}
|
|
480
454
|
tagsExamined += 1;
|
|
481
|
-
const tagName =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
455
|
+
const tagName = element.name;
|
|
456
|
+
const attributes = new Map(element.attributes.map(({ name, value }) => [name, value]));
|
|
457
|
+
if (element.attributesTruncated)
|
|
458
|
+
tagLimitReached = true;
|
|
485
459
|
if (tagName === 'html' && documentLanguage === null) {
|
|
486
460
|
documentLanguage = normalizeLanguage(attributes.get('lang'));
|
|
487
461
|
continue;
|
|
@@ -551,12 +525,10 @@ function extractPageIdentity(html, domain, options = {}) {
|
|
|
551
525
|
const queryOmitted = [canonical, metaRefresh, openGraphUrl].some((item) => item?.queryOmitted);
|
|
552
526
|
const pathTruncated = [canonical, metaRefresh, openGraphUrl].some((item) => item?.pathTruncated);
|
|
553
527
|
const sourceTruncated = options.sourceTruncated === true;
|
|
554
|
-
const relationships = extractPageRelationships(
|
|
528
|
+
const relationships = extractPageRelationships(analysis, domain, { baseUrl: documentUrl, effectiveBaseUrl: baseUrl, documentOrigin: baseOrigin });
|
|
555
529
|
const fingerprints = createPageFingerprints(html, {
|
|
556
|
-
// Retained fingerprint algorithms pre-date document-base handling. Keep
|
|
557
|
-
// their response-URL normalization stable until a versioned fingerprint
|
|
558
|
-
// migration is intentionally introduced.
|
|
559
530
|
baseUrl: documentUrl,
|
|
531
|
+
htmlAnalysis: analysis,
|
|
560
532
|
exactBodyHash: options.exactBodyHash,
|
|
561
533
|
sourceTruncated,
|
|
562
534
|
resources: relationships.resources,
|
|
@@ -696,9 +668,34 @@ function buildPagePublicationMetadata(analysis, sourceTruncated) {
|
|
|
696
668
|
},
|
|
697
669
|
};
|
|
698
670
|
}
|
|
699
|
-
function
|
|
671
|
+
function domainSaleLandingPage(analysis, domain) {
|
|
672
|
+
if (analysis.inputLimitReached || analysis.tagLimitReached || analysis.visibleTextLimitReached)
|
|
673
|
+
return false;
|
|
674
|
+
const title = analysis.title?.trim().toLowerCase() ?? '';
|
|
675
|
+
const content = analysis.visibleText.trim().toLowerCase();
|
|
676
|
+
const saleSuffix = /^\s+(?:(?:is|may be)\s+)?(?:for sale|available for purchase|available for lease)(?:[.!?:\s]|$)/u;
|
|
677
|
+
const explicitSale = (value) => {
|
|
678
|
+
const genericSubject = /^(?:(?:this|the) domain(?: name)?|domain(?: name)?)/u.exec(value)?.[0];
|
|
679
|
+
return (genericSubject !== undefined && saleSuffix.test(value.slice(genericSubject.length)))
|
|
680
|
+
|| (value.startsWith(domain) && saleSuffix.test(value.slice(domain.length)));
|
|
681
|
+
};
|
|
682
|
+
const explicitPurchase = /^(?:buy|purchase|own|bid on|inquire about) (?:this|the) domain(?: name)?(?:[.!?:\s]|$)/u;
|
|
683
|
+
// A short, directly worded landing page is sufficient. Long articles need
|
|
684
|
+
// corroborating page-title and transaction-control evidence; a generic
|
|
685
|
+
// commerce CTA or quoted example cannot classify the domain for sale.
|
|
686
|
+
if (content.length <= 240 && (explicitSale(content) || explicitPurchase.test(content)))
|
|
687
|
+
return true;
|
|
688
|
+
if (!explicitSale(title) && !explicitPurchase.test(title))
|
|
689
|
+
return false;
|
|
690
|
+
if (!/(?:this domain(?: name)? (?:is )?for sale|buy this domain|purchase this domain|domain name for sale)/u.test(content))
|
|
691
|
+
return false;
|
|
692
|
+
return analysis.elements.some((element) => element.html
|
|
693
|
+
&& ['form', 'button', 'a'].includes(element.name));
|
|
694
|
+
}
|
|
695
|
+
async function extractHtmlSignals(html, domain, options = {}) {
|
|
696
|
+
options.signal?.throwIfAborted();
|
|
700
697
|
const documentUrl = resolvedBaseUrl(domain, options.baseUrl);
|
|
701
|
-
const htmlAnalysis = analyzeStaticHtml(html, { baseUrl: documentUrl, includeVisibleText: true });
|
|
698
|
+
const htmlAnalysis = options.htmlAnalysis ?? analyzeStaticHtml(html, { baseUrl: documentUrl, includeVisibleText: true });
|
|
702
699
|
const effectiveBaseUrl = htmlAnalysis.effectiveBaseUrl ?? documentUrl;
|
|
703
700
|
const documentOrigin = new URL(documentUrl).origin;
|
|
704
701
|
const pageIdentity = options.includePageIdentity === false ? null : extractPageIdentity(html, domain, {
|
|
@@ -707,6 +704,7 @@ function extractHtmlSignals(html, domain, options = {}) {
|
|
|
707
704
|
effectiveBaseUrl,
|
|
708
705
|
documentOrigin,
|
|
709
706
|
baseHrefState: htmlAnalysis.baseHrefState,
|
|
707
|
+
htmlAnalysis,
|
|
710
708
|
});
|
|
711
709
|
const includeCredentialSurfaceProfile = pageIdentity && options.includeCredentialSurfaceProfile === true;
|
|
712
710
|
const includeStructuredDataIdentity = pageIdentity && options.includeStructuredDataIdentity !== false;
|
|
@@ -719,14 +717,17 @@ function extractHtmlSignals(html, domain, options = {}) {
|
|
|
719
717
|
}
|
|
720
718
|
: pageIdentity;
|
|
721
719
|
const pageLanguageSignal = detectPageLanguageSignal(html, pageIdentity?.documentLanguage, htmlAnalysis);
|
|
720
|
+
const domainSaleSignal = options.sourceTruncated !== true && domainSaleLandingPage(htmlAnalysis, domain)
|
|
721
|
+
? 'explicit domain-sale landing-page content' : null;
|
|
722
722
|
return {
|
|
723
|
+
domainSaleSignal,
|
|
723
724
|
pageTitle: htmlAnalysis.title,
|
|
724
725
|
hasPasswordField: htmlAnalysis.forms.categories.password > 0,
|
|
725
726
|
phishingLanguageMatch: pageLanguageSignal?.label ?? null,
|
|
726
727
|
hasExternalFormAction: pageIdentity
|
|
727
728
|
? pageIdentity.forms.externalActionOrigins.length > 0
|
|
728
729
|
: null,
|
|
729
|
-
externalAssetHosts: extractExternalAssetHosts(
|
|
730
|
+
externalAssetHosts: extractExternalAssetHosts(htmlAnalysis, domain, documentUrl),
|
|
730
731
|
cspMetaPolicy: analyzeCspMetaPolicies(htmlAnalysis.cspMetaPolicies, htmlAnalysis.cspMetaLimitReached),
|
|
731
732
|
pageIdentity: pageIdentityOutput,
|
|
732
733
|
credentialSurfaceProfile: includeCredentialSurfaceProfile && htmlAnalysis ? analyzeCredentialSurfaceProfile({
|
|
@@ -740,9 +741,10 @@ function extractHtmlSignals(html, domain, options = {}) {
|
|
|
740
741
|
observedAt: options.observedAt,
|
|
741
742
|
sourceTruncated: options.sourceTruncated,
|
|
742
743
|
}) : null,
|
|
743
|
-
technologyProfile: includeTechnologyProfile && htmlAnalysis ? analyzeWebsiteTechnology({
|
|
744
|
+
technologyProfile: includeTechnologyProfile && htmlAnalysis ? await analyzeWebsiteTechnology({
|
|
744
745
|
htmlAnalysis,
|
|
745
746
|
generator: pageIdentity.generator,
|
|
747
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
746
748
|
httpServer: options.httpServer,
|
|
747
749
|
responseHeaders: options.responseHeaders,
|
|
748
750
|
resourceOrigins: pageIdentity.resources.externalOrigins,
|
|
@@ -754,7 +756,7 @@ function extractHtmlSignals(html, domain, options = {}) {
|
|
|
754
756
|
pageRoleProfile: includeDerivedPageProfiles && htmlAnalysis ? analyzePageRole({
|
|
755
757
|
htmlAnalysis,
|
|
756
758
|
pageTitle: htmlAnalysis.title,
|
|
757
|
-
activityStatus: options.activityStatus,
|
|
759
|
+
activityStatus: domainSaleSignal ? 'parked' : options.activityStatus,
|
|
758
760
|
observedAt: options.observedAt,
|
|
759
761
|
sourceTruncated: options.sourceTruncated,
|
|
760
762
|
}) : null,
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// so valid retained evidence is not rejected by a stale projection.
|
|
4
|
-
export const MAX_HTTP_EVIDENCE_REDIRECTS = 5;
|
|
5
|
-
export const MAX_HTTP_ATTEMPTS = 2;
|
|
6
|
-
export const MAX_HTTP_PROVENANCE_URL = 2_048;
|
|
7
|
-
export const MAX_HTTP_ERROR_LENGTH = 180;
|
|
1
|
+
// Retained public import; the runtime-neutral implementation lives in the domain layer.
|
|
2
|
+
export * from "../packages/analysis/http-evidence-bounds.mjs";
|
|
@@ -3,8 +3,21 @@
|
|
|
3
3
|
// converts the shared safe-fetch trace and selected response metadata into an
|
|
4
4
|
// additive evidence object suitable for API responses and exports.
|
|
5
5
|
import { createObservation } from "../packages/evidence/observation.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { MAX_OUTBOUND_HTTP_URL_CHARACTERS } from "../packages/contracts/http-url.mjs";
|
|
7
|
+
import { MAX_HTTP_ATTEMPTS, MAX_HTTP_ERROR_LENGTH, MAX_HTTP_EVIDENCE_REDIRECTS, MAX_HTTP_PROVENANCE_URL, } from "../packages/analysis/http-evidence-bounds.mjs";
|
|
7
8
|
import { HTTP_DELIVERY_LIMITATIONS, HTTP_DELIVERY_METADATA_VERSION, MAX_HTTP_CACHE_SECONDS, MAX_HTTP_DELIVERY_HEADER_BYTES, MAX_HTTP_DELIVERY_TOKENS, } from "./homepage-metadata-contract.mjs";
|
|
9
|
+
/** Publish delivery metadata only at its owning response boundary. */
|
|
10
|
+
export function withoutHttpDeliveryMetadata(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
return value;
|
|
13
|
+
const output = { ...value };
|
|
14
|
+
if (output.response && typeof output.response === 'object' && !Array.isArray(output.response)) {
|
|
15
|
+
const response = { ...output.response };
|
|
16
|
+
delete response.deliveryMetadata;
|
|
17
|
+
output.response = response;
|
|
18
|
+
}
|
|
19
|
+
return output;
|
|
20
|
+
}
|
|
8
21
|
const MAX_HTTP_REDIRECTS = MAX_HTTP_EVIDENCE_REDIRECTS;
|
|
9
22
|
const MAX_HTTP_HEADER_LENGTH = 1024;
|
|
10
23
|
const MAX_HTTP_DELIVERY_HEADER_LENGTH = MAX_HTTP_DELIVERY_HEADER_BYTES;
|
|
@@ -25,7 +38,7 @@ function boundedString(value, maxLength) {
|
|
|
25
38
|
return normalized ? normalized.slice(0, maxLength) : null;
|
|
26
39
|
}
|
|
27
40
|
function normalizeProvenanceUrl(value) {
|
|
28
|
-
if (typeof value !== 'string' || value.length === 0 || value.length >
|
|
41
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_OUTBOUND_HTTP_URL_CHARACTERS)
|
|
29
42
|
return null;
|
|
30
43
|
try {
|
|
31
44
|
const parsed = new URL(value);
|
|
@@ -165,14 +178,14 @@ function headerValidity(header, validator) {
|
|
|
165
178
|
return { present: true, valid: null };
|
|
166
179
|
return { present: true, valid: header.state === 'observed' && Boolean(header.value && validator(header.value)) };
|
|
167
180
|
}
|
|
168
|
-
function validHttpDate(value) {
|
|
181
|
+
function validHttpDate(value, observedAt) {
|
|
169
182
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
170
183
|
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
171
184
|
const longWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
172
|
-
const time = '([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)';
|
|
185
|
+
const time = '([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d|60)';
|
|
173
186
|
const imfFixdate = value.match(new RegExp(`^(${weekdays.join('|')}), (0[1-9]|[12]\\d|3[01]) (${months.join('|')}) (\\d{4}) ${time} GMT$`, 'u'));
|
|
174
187
|
const rfc850 = value.match(new RegExp(`^(${longWeekdays.join('|')}), (0[1-9]|[12]\\d|3[01])-(${months.join('|')})-(\\d{2}) ${time} GMT$`, 'u'));
|
|
175
|
-
const asctime = value.match(new RegExp(`^(${weekdays.join('|')}) (${months.join('|')}) (
|
|
188
|
+
const asctime = value.match(new RegExp(`^(${weekdays.join('|')}) (${months.join('|')}) ( [1-9]|0[1-9]|[12]\\d|3[01]) ${time} (\\d{4})$`, 'u'));
|
|
176
189
|
let weekday;
|
|
177
190
|
let day;
|
|
178
191
|
let month;
|
|
@@ -196,10 +209,19 @@ function validHttpDate(value) {
|
|
|
196
209
|
day = Number(rfc850[2]);
|
|
197
210
|
month = months.indexOf(rfc850[3]);
|
|
198
211
|
const shortYear = Number(rfc850[4]);
|
|
199
|
-
|
|
212
|
+
// RFC 9110's rolling window is relative to receipt, not a later review
|
|
213
|
+
// or the host's timezone. The observation supplies that stable reference.
|
|
214
|
+
const futureBoundary = new Date(observedAt);
|
|
215
|
+
futureBoundary.setUTCFullYear(futureBoundary.getUTCFullYear() + 50);
|
|
216
|
+
year = Math.floor(futureBoundary.getUTCFullYear() / 100) * 100 + shortYear;
|
|
200
217
|
hour = Number(rfc850[5]);
|
|
201
218
|
minute = Number(rfc850[6]);
|
|
202
219
|
second = Number(rfc850[7]);
|
|
220
|
+
const candidate = new Date(0);
|
|
221
|
+
candidate.setUTCFullYear(year, month, day);
|
|
222
|
+
candidate.setUTCHours(hour, minute, Math.min(second, 59), 0);
|
|
223
|
+
if (candidate.getTime() > futureBoundary.getTime())
|
|
224
|
+
year -= 100;
|
|
203
225
|
expectedWeekdays = longWeekdays;
|
|
204
226
|
}
|
|
205
227
|
else if (asctime) {
|
|
@@ -218,13 +240,15 @@ function validHttpDate(value) {
|
|
|
218
240
|
return false;
|
|
219
241
|
const parsed = new Date(0);
|
|
220
242
|
parsed.setUTCFullYear(year, month, day);
|
|
221
|
-
|
|
243
|
+
// A leap-second spelling must not roll into the following calendar day
|
|
244
|
+
// while validating the declared date and weekday.
|
|
245
|
+
parsed.setUTCHours(hour, minute, Math.min(second, 59), 0);
|
|
222
246
|
return parsed.getUTCFullYear() === year
|
|
223
247
|
&& parsed.getUTCMonth() === month
|
|
224
248
|
&& parsed.getUTCDate() === day
|
|
225
249
|
&& expectedWeekdays[parsed.getUTCDay()] === weekday;
|
|
226
250
|
}
|
|
227
|
-
function parseCachePolicy(headers) {
|
|
251
|
+
function parseCachePolicy(headers, observedAt) {
|
|
228
252
|
const cacheControl = deliveryHeader(headers, 'cache-control');
|
|
229
253
|
const age = deliveryHeader(headers, 'age', 64);
|
|
230
254
|
const etagHeader = deliveryHeader(headers, 'etag');
|
|
@@ -329,8 +353,8 @@ function parseCachePolicy(headers) {
|
|
|
329
353
|
if (age.state === 'observed' && ageSeconds === null)
|
|
330
354
|
malformed = true;
|
|
331
355
|
const etag = headerValidity(etagHeader, (value) => /^(?:W\/)?"[\x21\x23-\x7e]*"$/u.test(value));
|
|
332
|
-
const lastModified = headerValidity(lastModifiedHeader, validHttpDate);
|
|
333
|
-
const expires = headerValidity(expiresHeader, validHttpDate);
|
|
356
|
+
const lastModified = headerValidity(lastModifiedHeader, (value) => validHttpDate(value, observedAt));
|
|
357
|
+
const expires = headerValidity(expiresHeader, (value) => validHttpDate(value, observedAt));
|
|
334
358
|
malformed = malformed || [etag, lastModified, expires].some((item) => item.present && item.valid === false);
|
|
335
359
|
const headersObserved = [cacheControl, age, etagHeader, lastModifiedHeader, expiresHeader]
|
|
336
360
|
.some((header) => header.state !== 'not_observed');
|
|
@@ -362,9 +386,9 @@ function parseCachePolicy(headers) {
|
|
|
362
386
|
truncated: partial,
|
|
363
387
|
};
|
|
364
388
|
}
|
|
365
|
-
function buildDeliveryMetadata(headers) {
|
|
389
|
+
function buildDeliveryMetadata(headers, observedAt) {
|
|
366
390
|
const contentEncoding = parseContentEncoding(headers);
|
|
367
|
-
const cacheResult = parseCachePolicy(headers);
|
|
391
|
+
const cacheResult = parseCachePolicy(headers, observedAt);
|
|
368
392
|
const cachePolicy = cacheResult.value;
|
|
369
393
|
const incomplete = [contentEncoding.status, cachePolicy.status].some((status) => ['partial', 'malformed'].includes(status));
|
|
370
394
|
const truncated = contentEncoding.status === 'partial' || cacheResult.truncated;
|
|
@@ -466,21 +490,22 @@ function buildHttpObservation(detail, options = {}) {
|
|
|
466
490
|
...(Array.isArray(options.previousAttempts) ? options.previousAttempts : []),
|
|
467
491
|
{ url: detail && detail.requestedUrl, httpStatus: status },
|
|
468
492
|
]);
|
|
493
|
+
const observation = createObservation({
|
|
494
|
+
status: incomplete ? 'partial' : 'success',
|
|
495
|
+
observedAt: options.observedAt,
|
|
496
|
+
scanMode: 'deep',
|
|
497
|
+
source: 'http',
|
|
498
|
+
durationMs: detail && detail.durationMs,
|
|
499
|
+
complete: !incomplete,
|
|
500
|
+
truncated: incomplete,
|
|
501
|
+
limitations,
|
|
502
|
+
diagnostics: {
|
|
503
|
+
redirectCount: redirects.length,
|
|
504
|
+
httpStatus: status,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
469
507
|
return {
|
|
470
|
-
...
|
|
471
|
-
status: incomplete ? 'partial' : 'success',
|
|
472
|
-
observedAt: options.observedAt,
|
|
473
|
-
scanMode: 'deep',
|
|
474
|
-
source: 'http',
|
|
475
|
-
durationMs: detail && detail.durationMs,
|
|
476
|
-
complete: !incomplete,
|
|
477
|
-
truncated: incomplete,
|
|
478
|
-
limitations,
|
|
479
|
-
diagnostics: {
|
|
480
|
-
redirectCount: redirects.length,
|
|
481
|
-
httpStatus: status,
|
|
482
|
-
},
|
|
483
|
-
}),
|
|
508
|
+
...observation,
|
|
484
509
|
requestUrl: requested ? requested.url : null,
|
|
485
510
|
finalUrl: final ? final.url : null,
|
|
486
511
|
transportSecurity: final ? final.url.split(':', 1)[0] : null,
|
|
@@ -506,7 +531,7 @@ function buildHttpObservation(detail, options = {}) {
|
|
|
506
531
|
bytes: capturedBodyBytes,
|
|
507
532
|
} : null,
|
|
508
533
|
securityHeaders: securityHeaders(response && response.headers),
|
|
509
|
-
deliveryMetadata: buildDeliveryMetadata(response && response.headers),
|
|
534
|
+
deliveryMetadata: buildDeliveryMetadata(response && response.headers, observation.observedAt),
|
|
510
535
|
},
|
|
511
536
|
};
|
|
512
537
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Versioned policy for projecting the Unicode UTS #39 confusables data into
|
|
2
2
|
// WHOISleuth's intentionally small domain-label mapping. The full upstream
|
|
3
3
|
// table is never shipped to browsers or consulted at runtime.
|
|
4
|
-
export const UNICODE_CONFUSABLE_DATA_VERSION = '
|
|
5
|
-
export const UNICODE_CONFUSABLE_SOURCE_URL =
|
|
6
|
-
export const UNICODE_CONFUSABLE_SOURCE_SHA256 = '
|
|
4
|
+
export const UNICODE_CONFUSABLE_DATA_VERSION = '18.0.0';
|
|
5
|
+
export const UNICODE_CONFUSABLE_SOURCE_URL = `https://www.unicode.org/Public/${UNICODE_CONFUSABLE_DATA_VERSION}/security/confusables.txt`;
|
|
6
|
+
export const UNICODE_CONFUSABLE_SOURCE_SHA256 = '6ed3ee967c9dfdf6677d563c9985182fbc50a2efb7d6059cd57b2e2ce18f5b92';
|
|
7
7
|
export const UNICODE_CONFUSABLE_LICENSE = 'Unicode-3.0';
|
|
8
8
|
export const CONFUSABLE_PROJECTION_SCHEMA = 'whoisleuth.unicode-confusable-projection';
|
|
9
9
|
export const CONFUSABLE_PROJECTION_VERSION = 1;
|
|
@@ -71,7 +71,9 @@ export const REVIEWED_GENERATION_CONFUSABLES = Object.freeze({
|
|
|
71
71
|
l: 'ӏǀꓲ',
|
|
72
72
|
m: 'м',
|
|
73
73
|
n: 'ոռ',
|
|
74
|
-
o
|
|
74
|
+
// Keep the complete previously generated o-set when newer source mappings
|
|
75
|
+
// compete for the same bounded generation slots.
|
|
76
|
+
o: 'оοօᴏⲟᴑꬽσ',
|
|
75
77
|
p: 'рρⲣ',
|
|
76
78
|
q: 'ԛգզ',
|
|
77
79
|
r: 'гꭇ',
|
package/lib/idn-confusables.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// bounded projection of Unicode Technical Standard #39 data plus the existing
|
|
5
5
|
// reviewed compatibility set. It focuses on characters that visually collapse
|
|
6
6
|
// to common ASCII domain-label characters.
|
|
7
|
-
import { GENERATED_CONFUSABLE_GROUPS, GENERATED_CONFUSABLE_MAPPING_VERSION, GENERATED_GENERATION_CONFUSABLE_GROUPS, } from "./generated/unicode-confusables
|
|
7
|
+
import { GENERATED_CONFUSABLE_GROUPS, GENERATED_CONFUSABLE_MAPPING_VERSION, GENERATED_GENERATION_CONFUSABLE_GROUPS, } from "./generated/unicode-confusables.mjs";
|
|
8
8
|
import { MAX_GENERATION_CONFUSABLES_PER_ASCII, REVIEWED_GENERATION_CONFUSABLES, } from "./idn-confusable-policy.mjs";
|
|
9
9
|
export const IDN_ANALYSIS_VERSION = 1;
|
|
10
10
|
export const CONFUSABLE_MAPPING_VERSION = GENERATED_CONFUSABLE_MAPPING_VERSION;
|