@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,124 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// families never contribute. Two adapters operated by the same publisher are
|
|
4
|
-
// deliberately one corroboration source, even when their datasets differ.
|
|
5
|
-
export const EXTERNAL_INTELLIGENCE_CALIBRATION_VERSION = 1;
|
|
6
|
-
export const EXTERNAL_INTELLIGENCE_RECENT_DAYS = 90;
|
|
7
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
8
|
-
const MAX_CLOCK_SKEW_MS = DAY_MS;
|
|
9
|
-
const MAX_PROVIDERS = 10;
|
|
10
|
-
const MAX_FINDINGS_PER_PROVIDER = 100;
|
|
11
|
-
const POSITIVE_STATES = new Set(['success', 'partial']);
|
|
12
|
-
const QUALIFYING_CATEGORIES = new Set(['phishing', 'malware']);
|
|
13
|
-
// Only built-in provider IDs can affect the built-in score. The two community
|
|
14
|
-
// malware datasets share one publisher family and therefore cannot corroborate
|
|
15
|
-
// one another by themselves.
|
|
16
|
-
import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
|
|
17
|
-
import { canonicalRegistrableDomain } from "./registrable-domain.mjs";
|
|
18
|
-
import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "./threat-intelligence-types.mjs";
|
|
19
|
-
const PUBLISHER_FAMILIES = Object.freeze({
|
|
20
|
-
urlscan_search: 'archived-scan-publisher',
|
|
21
|
-
urlhaus_host: 'community-malware-publisher',
|
|
22
|
-
threatfox_domain_ioc: 'community-malware-publisher',
|
|
23
|
-
});
|
|
24
|
-
function record(value) {
|
|
25
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
26
|
-
}
|
|
27
|
-
function timestamp(value) {
|
|
28
|
-
const normalized = normalizeExplicitIsoTimestamp(value);
|
|
29
|
-
return normalized ? Date.parse(normalized) : null;
|
|
30
|
-
}
|
|
31
|
-
function providerEvidence(value, expectedDomain) {
|
|
32
|
-
const provider = record(value);
|
|
33
|
-
const identity = record(provider?.provider);
|
|
34
|
-
const observation = record(provider?.observation);
|
|
35
|
-
const target = record(provider?.target);
|
|
36
|
-
const providerId = typeof identity?.id === 'string' ? identity.id : '';
|
|
37
|
-
const publisherFamily = Object.hasOwn(PUBLISHER_FAMILIES, providerId)
|
|
38
|
-
? PUBLISHER_FAMILIES[providerId]
|
|
39
|
-
: null;
|
|
40
|
-
if (provider?.schema !== THREAT_INTELLIGENCE_SCHEMA
|
|
41
|
-
|| provider.version !== THREAT_INTELLIGENCE_CONTRACT_VERSION
|
|
42
|
-
|| target?.type !== 'domain'
|
|
43
|
-
|| target.exposure !== 'registrable_domain'
|
|
44
|
-
|| target.value !== expectedDomain
|
|
45
|
-
|| !publisherFamily
|
|
46
|
-
|| typeof provider?.state !== 'string'
|
|
47
|
-
|| !POSITIVE_STATES.has(provider.state)
|
|
48
|
-
|| !Array.isArray(provider.findings))
|
|
49
|
-
return null;
|
|
50
|
-
const observedAt = timestamp(observation?.observedAt);
|
|
51
|
-
let latestFindingAt = null;
|
|
52
|
-
let qualifyingFindings = 0;
|
|
53
|
-
for (const item of provider.findings.slice(0, MAX_FINDINGS_PER_PROVIDER)) {
|
|
54
|
-
const finding = record(item);
|
|
55
|
-
if (!finding || typeof finding.category !== 'string' || !QUALIFYING_CATEGORIES.has(finding.category))
|
|
56
|
-
continue;
|
|
57
|
-
const firstObservedAt = timestamp(finding.firstObservedAt);
|
|
58
|
-
const lastObservedAt = timestamp(finding.lastObservedAt);
|
|
59
|
-
if (firstObservedAt !== null && lastObservedAt !== null && firstObservedAt > lastObservedAt)
|
|
60
|
-
continue;
|
|
61
|
-
qualifyingFindings += 1;
|
|
62
|
-
const candidate = lastObservedAt ?? firstObservedAt;
|
|
63
|
-
if (candidate !== null && (latestFindingAt === null || candidate > latestFindingAt))
|
|
64
|
-
latestFindingAt = candidate;
|
|
65
|
-
}
|
|
66
|
-
if (!qualifyingFindings)
|
|
67
|
-
return null;
|
|
68
|
-
let ageDays = null;
|
|
69
|
-
if (observedAt !== null && latestFindingAt !== null && latestFindingAt <= observedAt + MAX_CLOCK_SKEW_MS) {
|
|
70
|
-
ageDays = Math.max(0, Math.floor((observedAt - latestFindingAt) / DAY_MS));
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
providerId,
|
|
74
|
-
publisherFamily,
|
|
75
|
-
qualifyingFindings,
|
|
76
|
-
lastObservedAt: latestFindingAt === null ? null : new Date(latestFindingAt).toISOString(),
|
|
77
|
-
ageDays,
|
|
78
|
-
recent: ageDays !== null && ageDays <= EXTERNAL_INTELLIGENCE_RECENT_DAYS,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export function calibrateExternalIntelligenceRisk(value, expectedDomainValue) {
|
|
82
|
-
const envelope = record(value);
|
|
83
|
-
const expectedDomain = canonicalRegistrableDomain(expectedDomainValue);
|
|
84
|
-
const providers = expectedDomain
|
|
85
|
-
&& envelope?.version === THREAT_INTELLIGENCE_ENVELOPE_VERSION
|
|
86
|
-
&& Array.isArray(envelope.providers)
|
|
87
|
-
? envelope.providers.slice(0, MAX_PROVIDERS)
|
|
88
|
-
: [];
|
|
89
|
-
const byProvider = new Map();
|
|
90
|
-
for (const item of providers) {
|
|
91
|
-
const evidence = providerEvidence(item, expectedDomain ?? '');
|
|
92
|
-
if (!evidence || byProvider.has(evidence.providerId))
|
|
93
|
-
continue;
|
|
94
|
-
byProvider.set(evidence.providerId, evidence);
|
|
95
|
-
}
|
|
96
|
-
const sources = [...byProvider.values()].sort((a, b) => a.providerId.localeCompare(b.providerId));
|
|
97
|
-
const publisherFamilies = new Set(sources.map((source) => source.publisherFamily));
|
|
98
|
-
const recentPublisherFamilies = new Set(sources.filter((source) => source.recent).map((source) => source.publisherFamily));
|
|
99
|
-
const knownAges = sources.map((source) => source.ageDays).filter((age) => age !== null);
|
|
100
|
-
const independentPublisherCount = publisherFamilies.size;
|
|
101
|
-
const recentPublisherCount = recentPublisherFamilies.size;
|
|
102
|
-
let contribution = 0;
|
|
103
|
-
let factor = null;
|
|
104
|
-
if (independentPublisherCount >= 2) {
|
|
105
|
-
contribution = recentPublisherCount >= 2 ? 18 : 10;
|
|
106
|
-
factor = {
|
|
107
|
-
label: recentPublisherCount >= 2
|
|
108
|
-
? 'Corroborated recent external phishing/malware records'
|
|
109
|
-
: 'Corroborated external phishing/malware records',
|
|
110
|
-
delta: contribution,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
version: EXTERNAL_INTELLIGENCE_CALIBRATION_VERSION,
|
|
115
|
-
contribution,
|
|
116
|
-
factor,
|
|
117
|
-
eligibleProviderCount: sources.length,
|
|
118
|
-
independentPublisherCount,
|
|
119
|
-
recentPublisherCount,
|
|
120
|
-
freshestAgeDays: knownAges.length ? Math.min(...knownAges) : null,
|
|
121
|
-
unknownAgeProviderCount: sources.filter((source) => source.ageDays === null).length,
|
|
122
|
-
sources,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
1
|
+
// Retained public import; the runtime-neutral implementation lives in the domain layer.
|
|
2
|
+
export * from "../packages/analysis/external-intelligence-risk.mjs";
|
package/lib/favicon.mjs
CHANGED
|
@@ -1,50 +1,30 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Two hashes are produced from the same bytes:
|
|
7
|
-
// - hash: exact SHA-256 of the raw bytes. Zero false positives, but only
|
|
8
|
-
// catches a byte-for-byte copy.
|
|
9
|
-
// - phash: perceptual (fuzzy) dHash (lib/perceptual-hash.mts). Catches a
|
|
10
|
-
// resized/recompressed/re-saved copy that's visually identical but
|
|
11
|
-
// no longer byte-identical - the common case when the favicon
|
|
12
|
-
// passed through a different tool than the original. Null when the
|
|
13
|
-
// image format can't be decoded (GIF/JPEG/SVG), so it strictly
|
|
14
|
-
// adds coverage without weakening the exact tier.
|
|
1
|
+
// Bounded favicon acquisition. Recognised image bytes produce an exact SHA-256;
|
|
2
|
+
// supported decoded pixels can also produce a heuristic dHash. Neither match
|
|
3
|
+
// establishes ownership, shared infrastructure or maliciousness. HTML error
|
|
4
|
+
// bodies are not image evidence and must not end candidate fallback.
|
|
15
5
|
import * as crypto from 'node:crypto';
|
|
16
6
|
import { safeFetch, readBytesCapped } from "./safe-fetch.mjs";
|
|
17
7
|
import { whoisleuthRequestHeaders } from "./outbound-identity.mjs";
|
|
18
8
|
import { faviconPerceptualHash } from "./perceptual-hash.mjs";
|
|
9
|
+
import { analyzeStaticHtml } from "./static-html-analysis.mjs";
|
|
10
|
+
import { isHtmlElement, parseBoundedHtml } from "./bounded-html-document.mjs";
|
|
19
11
|
import { FAVICON_FETCH_TIMEOUT_MS, MAX_FAVICON_BYTES, MAX_FAVICON_CANDIDATES, } from "./outbound-request-bounds.mjs";
|
|
20
12
|
// Extracts favicon URLs declared in the page's own <link rel="...icon..."> tags
|
|
21
13
|
// (resolved to absolute URLs against the page origin), in preference order:
|
|
22
|
-
// standard "icon"/"shortcut icon" first, then "apple-touch-icon".
|
|
23
|
-
// sites (e.g. npm) serve no /favicon.ico at all and only declare a PNG on a
|
|
24
|
-
// CDN this way, so checking only /favicon.ico misses them entirely. data:
|
|
14
|
+
// standard "icon"/"shortcut icon" first, then "apple-touch-icon". data:
|
|
25
15
|
// URIs are passed through verbatim - decodeFaviconCandidate handles them
|
|
26
16
|
// without a network fetch.
|
|
27
|
-
function extractIconUrls(html, baseUrl) {
|
|
28
|
-
const
|
|
29
|
-
for (const tag of html.match(/<link\b[^>]*>/gi) || []) {
|
|
30
|
-
const rel = (tag.match(/\brel\s*=\s*["']([^"']*)["']/i) || [])[1];
|
|
31
|
-
if (!rel || !/icon/i.test(rel))
|
|
32
|
-
continue;
|
|
33
|
-
const href = (tag.match(/\bhref\s*=\s*["']([^"']+)["']/i) || [])[1];
|
|
34
|
-
if (!href)
|
|
35
|
-
continue;
|
|
36
|
-
links.push({ href: href.trim(), priority: /apple-touch/i.test(rel) ? 1 : 0 });
|
|
37
|
-
}
|
|
38
|
-
links.sort((a, b) => a.priority - b.priority);
|
|
17
|
+
function extractIconUrls(html, baseUrl, suppliedAnalysis) {
|
|
18
|
+
const analysis = suppliedAnalysis ?? analyzeStaticHtml(html, { baseUrl });
|
|
39
19
|
const urls = [];
|
|
40
|
-
for (const { href } of
|
|
20
|
+
for (const { href } of analysis.iconLinks) {
|
|
41
21
|
if (/^data:/i.test(href)) {
|
|
42
22
|
urls.push(href);
|
|
43
23
|
continue;
|
|
44
24
|
}
|
|
45
25
|
try {
|
|
46
|
-
const resolved = new URL(href, baseUrl);
|
|
47
|
-
if (
|
|
26
|
+
const resolved = new URL(href, analysis.effectiveBaseUrl ?? baseUrl);
|
|
27
|
+
if (['http:', 'https:'].includes(resolved.protocol) && !resolved.username && !resolved.password)
|
|
48
28
|
urls.push(resolved.toString());
|
|
49
29
|
}
|
|
50
30
|
catch {
|
|
@@ -56,21 +36,114 @@ function extractIconUrls(html, baseUrl) {
|
|
|
56
36
|
// Decodes an inline `data:` favicon (base64 or percent-encoded) into a byte
|
|
57
37
|
// buffer, bounded to MAX_FAVICON_BYTES. Returns null on anything malformed.
|
|
58
38
|
function decodeDataUri(uri) {
|
|
39
|
+
// Percent encoding is at most three input characters per output byte.
|
|
40
|
+
// Refuse oversized encoded input before slicing or decoding it.
|
|
41
|
+
if (uri.length > MAX_FAVICON_BYTES * 3 + 128)
|
|
42
|
+
return null;
|
|
59
43
|
const comma = uri.indexOf(',');
|
|
60
|
-
if (comma === -1)
|
|
44
|
+
if (comma === -1 || comma > 128)
|
|
61
45
|
return null;
|
|
62
46
|
const meta = uri.slice(5, comma);
|
|
63
47
|
const data = uri.slice(comma + 1);
|
|
64
48
|
try {
|
|
65
|
-
const
|
|
49
|
+
const base64 = /;base64$/i.test(meta);
|
|
50
|
+
if (base64 && (data.length > Math.ceil(MAX_FAVICON_BYTES / 3) * 4
|
|
51
|
+
|| !/^[A-Za-z0-9+/]*={0,2}$/u.test(data)))
|
|
52
|
+
return null;
|
|
53
|
+
const bytes = base64
|
|
66
54
|
? Buffer.from(data, 'base64')
|
|
67
|
-
: Buffer.from(decodeURIComponent(data), '
|
|
55
|
+
: Buffer.from(decodeURIComponent(data), 'utf8');
|
|
68
56
|
return bytes.length > 0 && bytes.length <= MAX_FAVICON_BYTES ? bytes : null;
|
|
69
57
|
}
|
|
70
58
|
catch {
|
|
71
59
|
return null;
|
|
72
60
|
}
|
|
73
61
|
}
|
|
62
|
+
// Admission recognises container framing, not every codec feature or rendered
|
|
63
|
+
// meaning. Exact-only formats remain usable; fuzzy decoding has its own pixel
|
|
64
|
+
// and inflate limits. All loops here operate within MAX_FAVICON_BYTES.
|
|
65
|
+
function isFaviconImage(bytes) {
|
|
66
|
+
if (bytes.length < 6 || bytes.length > MAX_FAVICON_BYTES)
|
|
67
|
+
return false;
|
|
68
|
+
if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
69
|
+
let dataSeen = false;
|
|
70
|
+
for (let offset = 8; offset + 12 <= bytes.length;) {
|
|
71
|
+
const size = bytes.readUInt32BE(offset);
|
|
72
|
+
const end = offset + 12 + size;
|
|
73
|
+
if (end > bytes.length)
|
|
74
|
+
return false;
|
|
75
|
+
const type = bytes.toString('ascii', offset + 4, offset + 8);
|
|
76
|
+
if (offset === 8 && (type !== 'IHDR' || size !== 13
|
|
77
|
+
|| bytes.readUInt32BE(offset + 8) === 0 || bytes.readUInt32BE(offset + 12) === 0))
|
|
78
|
+
return false;
|
|
79
|
+
if (type === 'IDAT' && size > 0)
|
|
80
|
+
dataSeen = true;
|
|
81
|
+
if (type === 'IEND')
|
|
82
|
+
return size === 0 && dataSeen && end === bytes.length;
|
|
83
|
+
offset = end;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (bytes.readUInt32LE(0) === 0x00010000) {
|
|
88
|
+
const count = bytes.readUInt16LE(4);
|
|
89
|
+
const directoryEnd = 6 + count * 16;
|
|
90
|
+
if (!count || directoryEnd > bytes.length)
|
|
91
|
+
return false;
|
|
92
|
+
for (let index = 0; index < count; index += 1) {
|
|
93
|
+
const entry = 6 + index * 16;
|
|
94
|
+
const size = bytes.readUInt32LE(entry + 8);
|
|
95
|
+
const offset = bytes.readUInt32LE(entry + 12);
|
|
96
|
+
if (size < 12 || offset < directoryEnd || offset + size > bytes.length)
|
|
97
|
+
continue;
|
|
98
|
+
const payload = bytes.subarray(offset, offset + size);
|
|
99
|
+
if (payload.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
|
|
100
|
+
|| [12, 40, 52, 56, 108, 124].includes(payload.readUInt32LE(0)))
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (/^GIF8[79]a$/u.test(bytes.toString('ascii', 0, 6))) {
|
|
106
|
+
return bytes.length >= 14 && bytes.readUInt16LE(6) > 0 && bytes.readUInt16LE(8) > 0 && bytes.at(-1) === 0x3b;
|
|
107
|
+
}
|
|
108
|
+
if (bytes.length >= 12 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
109
|
+
return bytes.at(-2) === 0xff && bytes.at(-1) === 0xd9;
|
|
110
|
+
}
|
|
111
|
+
if (bytes.length >= 20 && bytes.toString('ascii', 0, 4) === 'RIFF'
|
|
112
|
+
&& bytes.toString('ascii', 8, 12) === 'WEBP') {
|
|
113
|
+
return bytes.readUInt32LE(4) + 8 === bytes.length && ['VP8 ', 'VP8L', 'VP8X'].includes(bytes.toString('ascii', 12, 16));
|
|
114
|
+
}
|
|
115
|
+
// Standalone SVG needs an SVG root, not an SVG embedded in an HTML error
|
|
116
|
+
// page. Native tree construction discards neither an HTML wrapper nor sibling
|
|
117
|
+
// content here. No script, entity, resource or stylesheet is executed.
|
|
118
|
+
const source = bytes.toString('utf8');
|
|
119
|
+
if (!source.includes('<svg'))
|
|
120
|
+
return false;
|
|
121
|
+
const parsed = parseBoundedHtml(source);
|
|
122
|
+
if (parsed.inputLimitReached || parsed.constructionLimitReached)
|
|
123
|
+
return false;
|
|
124
|
+
const html = parsed.document.childNodes.find(isHtmlElement);
|
|
125
|
+
if (html?.sourceCodeLocation?.startTag || parsed.document.childNodes.some((node) => node.nodeName === '#documentType' && 'name' in node && node.name !== 'svg'))
|
|
126
|
+
return false;
|
|
127
|
+
const head = html?.childNodes.find((node) => isHtmlElement(node) && node.tagName === 'head');
|
|
128
|
+
if (head && isHtmlElement(head) && (head.sourceCodeLocation?.startTag
|
|
129
|
+
|| head.childNodes.some((node) => node.nodeName !== '#comment'
|
|
130
|
+
&& !(node.nodeName === '#text' && 'value' in node && !node.value.trim()))))
|
|
131
|
+
return false;
|
|
132
|
+
const body = html?.childNodes.find((node) => isHtmlElement(node) && node.tagName === 'body');
|
|
133
|
+
if (!body || !isHtmlElement(body) || body.sourceCodeLocation?.startTag)
|
|
134
|
+
return false;
|
|
135
|
+
const content = body.childNodes.filter((node) => node.nodeName !== '#comment'
|
|
136
|
+
&& !(node.nodeName === '#text' && 'value' in node && !node.value.trim()));
|
|
137
|
+
const root = content[0];
|
|
138
|
+
if (content.length !== 1 || !root || !('tagName' in root) || root.tagName !== 'svg'
|
|
139
|
+
|| root.namespaceURI !== 'http://www.w3.org/2000/svg'
|
|
140
|
+
|| !root.attrs.some((attribute) => attribute.name === 'xmlns' && attribute.value === 'http://www.w3.org/2000/svg'))
|
|
141
|
+
return false;
|
|
142
|
+
const location = root.sourceCodeLocation;
|
|
143
|
+
if (!location?.startTag)
|
|
144
|
+
return false;
|
|
145
|
+
return Boolean(location.endTag || /\/\s*>$/u.test(source.slice(location.startTag.startOffset, location.startTag.endOffset)));
|
|
146
|
+
}
|
|
74
147
|
// Returns the favicon bytes for one candidate URL (a real fetch, or an inline
|
|
75
148
|
// data: decode), or null if it can't be retrieved/used.
|
|
76
149
|
async function fetchFaviconBytes(url, headers, fetcher, timeoutMs) {
|
|
@@ -107,15 +180,15 @@ async function fetchFaviconBytes(url, headers, fetcher, timeoutMs) {
|
|
|
107
180
|
// provided (the deep-check path already has the homepage in hand), is scanned
|
|
108
181
|
// for declared <link rel="icon"> URLs, which are tried before the bare
|
|
109
182
|
// /favicon.ico fallback.
|
|
110
|
-
function buildFaviconCandidates(domain, html = '') {
|
|
183
|
+
function buildFaviconCandidates(domain, html = '', options = {}) {
|
|
111
184
|
const candidates = [];
|
|
112
185
|
const seen = new Set();
|
|
113
186
|
const add = (url) => { if (url && !seen.has(url)) {
|
|
114
187
|
seen.add(url);
|
|
115
188
|
candidates.push(url);
|
|
116
189
|
} };
|
|
117
|
-
if (html)
|
|
118
|
-
for (const url of extractIconUrls(html, `https://${domain}
|
|
190
|
+
if (html || options.htmlAnalysis)
|
|
191
|
+
for (const url of extractIconUrls(html, options.baseUrl ?? `https://${domain}/`, options.htmlAnalysis))
|
|
119
192
|
add(url);
|
|
120
193
|
add(`https://${domain}/favicon.ico`);
|
|
121
194
|
add(`https://${domain}/favicon.svg`);
|
|
@@ -123,16 +196,19 @@ function buildFaviconCandidates(domain, html = '') {
|
|
|
123
196
|
add(`http://${domain}/favicon.svg`);
|
|
124
197
|
return candidates.slice(0, MAX_FAVICON_CANDIDATES);
|
|
125
198
|
}
|
|
126
|
-
async function fetchFaviconHash(domain, { html = '', fetcher = safeFetch, timeoutMs = FAVICON_FETCH_TIMEOUT_MS } = {}) {
|
|
199
|
+
async function fetchFaviconHash(domain, { html = '', baseUrl, htmlAnalysis, fetcher = safeFetch, timeoutMs = FAVICON_FETCH_TIMEOUT_MS } = {}) {
|
|
127
200
|
const headers = whoisleuthRequestHeaders();
|
|
128
|
-
const candidates = buildFaviconCandidates(domain, html
|
|
201
|
+
const candidates = buildFaviconCandidates(domain, html, {
|
|
202
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
203
|
+
...(htmlAnalysis ? { htmlAnalysis } : {}),
|
|
204
|
+
});
|
|
129
205
|
const requestTimeoutMs = Number.isInteger(timeoutMs) && timeoutMs >= 10 && timeoutMs <= FAVICON_FETCH_TIMEOUT_MS
|
|
130
206
|
? timeoutMs
|
|
131
207
|
: FAVICON_FETCH_TIMEOUT_MS;
|
|
132
208
|
for (const url of candidates) {
|
|
133
209
|
// eslint-disable-next-line no-await-in-loop
|
|
134
210
|
const bytes = await fetchFaviconBytes(url, headers, fetcher, requestTimeoutMs);
|
|
135
|
-
if (bytes) {
|
|
211
|
+
if (bytes && isFaviconImage(bytes)) {
|
|
136
212
|
return {
|
|
137
213
|
hash: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
138
214
|
phash: faviconPerceptualHash(bytes),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by tools/cisa-kev-catalog.mts from the pinned CISA KEV feed.
|
|
2
2
|
// United States Government work; do not edit by hand.
|
|
3
3
|
const CISA_KEV_CATALOG = Object.freeze({
|
|
4
|
-
catalogVersion: "2026.
|
|
5
|
-
releasedAt: "2026-
|
|
6
|
-
sourceSha256: "
|
|
4
|
+
catalogVersion: "2026.10.02",
|
|
5
|
+
releasedAt: "2026-10-02T15:19:38.2945Z",
|
|
6
|
+
sourceSha256: "d2c8c6cb23291b46ff2b086197641a6b1fa35e6b746bba714cc280a1778f9e48",
|
|
7
7
|
sourceUrl: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
|
|
8
8
|
identifiers: Object.freeze([
|
|
9
9
|
"CVE-2002-0367",
|
|
@@ -1439,6 +1439,7 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1439
1439
|
"CVE-2025-24991",
|
|
1440
1440
|
"CVE-2025-24993",
|
|
1441
1441
|
"CVE-2025-25181",
|
|
1442
|
+
"CVE-2025-25249",
|
|
1442
1443
|
"CVE-2025-25257",
|
|
1443
1444
|
"CVE-2025-26399",
|
|
1444
1445
|
"CVE-2025-26633",
|
|
@@ -1484,6 +1485,8 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1484
1485
|
"CVE-2025-38352",
|
|
1485
1486
|
"CVE-2025-3928",
|
|
1486
1487
|
"CVE-2025-3935",
|
|
1488
|
+
"CVE-2025-39682",
|
|
1489
|
+
"CVE-2025-39964",
|
|
1487
1490
|
"CVE-2025-4008",
|
|
1488
1491
|
"CVE-2025-40536",
|
|
1489
1492
|
"CVE-2025-40551",
|
|
@@ -1571,6 +1574,9 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1571
1574
|
"CVE-2026-0257",
|
|
1572
1575
|
"CVE-2026-0300",
|
|
1573
1576
|
"CVE-2026-0770",
|
|
1577
|
+
"CVE-2026-102489",
|
|
1578
|
+
"CVE-2026-102490",
|
|
1579
|
+
"CVE-2026-104286",
|
|
1574
1580
|
"CVE-2026-10520",
|
|
1575
1581
|
"CVE-2026-11645",
|
|
1576
1582
|
"CVE-2026-12569",
|
|
@@ -1584,7 +1590,9 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1584
1590
|
"CVE-2026-1731",
|
|
1585
1591
|
"CVE-2026-18556",
|
|
1586
1592
|
"CVE-2026-18577",
|
|
1593
|
+
"CVE-2026-19490",
|
|
1587
1594
|
"CVE-2026-20045",
|
|
1595
|
+
"CVE-2026-20079",
|
|
1588
1596
|
"CVE-2026-20122",
|
|
1589
1597
|
"CVE-2026-20127",
|
|
1590
1598
|
"CVE-2026-20128",
|
|
@@ -1644,6 +1652,8 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1644
1652
|
"CVE-2026-39987",
|
|
1645
1653
|
"CVE-2026-41091",
|
|
1646
1654
|
"CVE-2026-41940",
|
|
1655
|
+
"CVE-2026-42016",
|
|
1656
|
+
"CVE-2026-42018",
|
|
1647
1657
|
"CVE-2026-42208",
|
|
1648
1658
|
"CVE-2026-42271",
|
|
1649
1659
|
"CVE-2026-42897",
|
|
@@ -1656,13 +1666,17 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1656
1666
|
"CVE-2026-48172",
|
|
1657
1667
|
"CVE-2026-48282",
|
|
1658
1668
|
"CVE-2026-48558",
|
|
1669
|
+
"CVE-2026-48710",
|
|
1659
1670
|
"CVE-2026-48907",
|
|
1660
1671
|
"CVE-2026-48908",
|
|
1661
1672
|
"CVE-2026-48939",
|
|
1673
|
+
"CVE-2026-49869",
|
|
1662
1674
|
"CVE-2026-50522",
|
|
1663
1675
|
"CVE-2026-50751",
|
|
1664
1676
|
"CVE-2026-5281",
|
|
1677
|
+
"CVE-2026-53266",
|
|
1665
1678
|
"CVE-2026-53362",
|
|
1679
|
+
"CVE-2026-5430",
|
|
1666
1680
|
"CVE-2026-54420",
|
|
1667
1681
|
"CVE-2026-55040",
|
|
1668
1682
|
"CVE-2026-55255",
|
|
@@ -1671,28 +1685,60 @@ const CISA_KEV_CATALOG = Object.freeze({
|
|
|
1671
1685
|
"CVE-2026-56290",
|
|
1672
1686
|
"CVE-2026-56291",
|
|
1673
1687
|
"CVE-2026-58644",
|
|
1688
|
+
"CVE-2026-58704",
|
|
1674
1689
|
"CVE-2026-59310",
|
|
1690
|
+
"CVE-2026-59822",
|
|
1675
1691
|
"CVE-2026-60004",
|
|
1676
1692
|
"CVE-2026-60137",
|
|
1677
1693
|
"CVE-2026-63030",
|
|
1678
1694
|
"CVE-2026-63077",
|
|
1679
1695
|
"CVE-2026-64849",
|
|
1680
1696
|
"CVE-2026-65400",
|
|
1697
|
+
"CVE-2026-65660",
|
|
1681
1698
|
"CVE-2026-66384",
|
|
1699
|
+
"CVE-2026-67277",
|
|
1700
|
+
"CVE-2026-67279",
|
|
1682
1701
|
"CVE-2026-68820",
|
|
1683
1702
|
"CVE-2026-6973",
|
|
1703
|
+
"CVE-2026-71362",
|
|
1684
1704
|
"CVE-2026-72529",
|
|
1685
1705
|
"CVE-2026-72530",
|
|
1706
|
+
"CVE-2026-7273",
|
|
1686
1707
|
"CVE-2026-72898",
|
|
1687
1708
|
"CVE-2026-73570",
|
|
1688
1709
|
"CVE-2026-7473",
|
|
1710
|
+
"CVE-2026-75650",
|
|
1711
|
+
"CVE-2026-76460",
|
|
1712
|
+
"CVE-2026-76461",
|
|
1713
|
+
"CVE-2026-76504",
|
|
1689
1714
|
"CVE-2026-8037",
|
|
1690
1715
|
"CVE-2026-81578",
|
|
1716
|
+
"CVE-2026-81963",
|
|
1691
1717
|
"CVE-2026-82078",
|
|
1718
|
+
"CVE-2026-82329",
|
|
1719
|
+
"CVE-2026-83548",
|
|
1720
|
+
"CVE-2026-83549",
|
|
1692
1721
|
"CVE-2026-8398",
|
|
1693
1722
|
"CVE-2026-8452",
|
|
1723
|
+
"CVE-2026-84869",
|
|
1724
|
+
"CVE-2026-85046",
|
|
1725
|
+
"CVE-2026-85102",
|
|
1726
|
+
"CVE-2026-85706",
|
|
1727
|
+
"CVE-2026-85880",
|
|
1728
|
+
"CVE-2026-86060",
|
|
1729
|
+
"CVE-2026-86218",
|
|
1730
|
+
"CVE-2026-86950",
|
|
1731
|
+
"CVE-2026-87491",
|
|
1732
|
+
"CVE-2026-87886",
|
|
1733
|
+
"CVE-2026-87902",
|
|
1734
|
+
"CVE-2026-88771",
|
|
1735
|
+
"CVE-2026-88772",
|
|
1694
1736
|
"CVE-2026-9082",
|
|
1695
|
-
"CVE-2026-9198"
|
|
1737
|
+
"CVE-2026-9198",
|
|
1738
|
+
"CVE-2026-93616",
|
|
1739
|
+
"CVE-2026-93952",
|
|
1740
|
+
"CVE-2026-94127",
|
|
1741
|
+
"CVE-2026-9586"
|
|
1696
1742
|
]),
|
|
1697
1743
|
});
|
|
1698
1744
|
export { CISA_KEV_CATALOG };
|