@slicedearth/whoisleuth-cli 2.5.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +9 -6
- package/NOTICE +3 -2
- package/README.md +60 -67
- package/cli/artifact-validation/case-response.mjs +6 -814
- package/cli/artifact-validation/lookup-evidence.mjs +30 -3
- package/cli/artifact-validation/signed-review.mjs +2 -51
- package/cli/artifact-verify.mjs +41 -3
- package/cli/bulk-command-runner.mjs +4 -6
- package/cli/case-command.mjs +92 -16
- package/cli/case-pack.mjs +14 -3
- package/cli/ci-report.mjs +17 -5
- package/cli/command-definition.mjs +7 -6
- package/cli/discovery-scan-command-runner.mjs +4 -6
- package/cli/doctor.mjs +37 -3
- package/cli/domain-feed.mjs +61 -0
- package/cli/formatters/html.mjs +3 -0
- package/cli/formatters/json.mjs +9 -14
- package/cli/formatters/terminal-lookup.mjs +24 -1
- package/cli/history-command-definitions.mjs +1 -1
- package/cli/indicator-set-command.mjs +1 -2
- package/cli/intake-command.mjs +43 -3
- package/cli/investigation-artifacts.mjs +2 -3
- package/cli/investigation-package-review.mjs +2 -0
- package/cli/investigation-plan.mjs +15 -0
- package/cli/local-mmdb-review.mjs +27 -4
- package/cli/lookup-brief.mjs +11 -5
- package/cli/lookup-command-runner.mjs +21 -13
- package/cli/offline-evidence-review.mjs +51 -5
- package/cli/review-arguments.mjs +3 -2
- package/cli/review-command-definitions.mjs +23 -17
- package/cli/review-command-runner.mjs +6 -3
- package/cli/risk-calibration.mjs +6 -4
- package/cli/saved-lookup.mjs +22 -7
- package/cli/selected-input-worker.mjs +12 -2
- package/cli/source-reliability.mjs +12 -10
- package/cli/support-arguments.mjs +1 -1
- package/cli/support-command-definitions.mjs +5 -4
- package/cli/support-command-runner.mjs +9 -7
- package/cli/watchlist-review.mjs +43 -0
- package/cli/workflow-arguments.mjs +14 -0
- package/cli/workflow-command-definitions.mjs +27 -1
- package/cli/workflow-command-runner.mjs +32 -0
- package/docs/cli-reference.md +49 -11
- package/docs/cli.md +200 -26
- package/docs/contextual-reviews.md +38 -5
- package/docs/infrastructure-observations.md +135 -0
- package/docs/offline-intake-context.md +213 -0
- package/lib/credential-form-attribution.mjs +136 -0
- package/lib/credential-surface-profile.mjs +6 -2
- package/lib/dmarc-discovery.mjs +4 -2
- package/lib/dns-delegation-health.mjs +23 -7
- package/lib/dns-inheritance-review.mjs +14 -1
- package/lib/dns-intelligence.mjs +18 -4
- package/lib/domain-posture-analysis.mjs +53 -2
- package/lib/domain-posture-parsers.mjs +9 -0
- package/lib/domain-posture.mjs +29 -10
- package/lib/evidence-export-privacy.mjs +5 -2
- package/lib/evidence-export.mjs +10 -4
- package/lib/evidence-report.mjs +3 -1
- package/lib/favicon.mjs +13 -4
- package/lib/generated/cisa-kev-catalog.mjs +17 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +178 -5
- package/lib/html-signals.mjs +6 -0
- package/lib/idn-confusables.mjs +5 -1
- package/lib/lookup-child-profile-contract.mjs +28 -11
- package/lib/lookup-collection-receipt.mjs +52 -0
- package/lib/lookup-diagnostics.mjs +2 -12
- package/lib/lookup-progress.mjs +29 -31
- package/lib/lookup-response-contract.mjs +10 -0
- package/lib/lookup-response.mjs +15 -14
- package/lib/lookup-source-progress.mjs +4 -2
- package/lib/lookup.mjs +33 -2
- package/lib/operation-budget.mjs +4 -0
- package/lib/rdap-capabilities.mjs +43 -9
- package/lib/registry-insights.mjs +17 -6
- package/lib/service-binding-dns.mjs +19 -2
- package/lib/sslbl-certificates.generated.mjs +142 -140
- package/lib/static-html-analysis.mjs +32 -4
- package/lib/technology-evidence-role.mjs +1 -51
- package/lib/tls-intelligence.mjs +30 -5
- package/lib/website-enrichment.mjs +39 -23
- package/lib/whois-authority.mjs +56 -1
- package/lib/whois-chain.mjs +14 -4
- package/lib/whois-common-formats.mjs +3 -1
- package/lib/whois-contacts.mjs +13 -2
- package/lib/whois-contracts.mjs +2 -0
- package/lib/whois-normalization.mjs +4 -4
- package/package.json +9 -7
- package/packages/analysis/bounded-json.mjs +12 -2
- package/packages/analysis/risk-scoring.mjs +12 -9
- package/packages/cases/case-attachment-model.mjs +36 -1
- package/packages/cases/case-checkpoint-receipts.mjs +68 -0
- package/packages/cases/case-containment-handoff.mjs +195 -0
- package/packages/cases/case-evidence-links.mjs +44 -0
- package/packages/cases/case-evidence-model.mjs +51 -7
- package/packages/cases/case-migration-model.mjs +9 -1
- package/packages/cases/case-object-coverage.mjs +119 -0
- package/packages/cases/case-packet-correction.mjs +106 -0
- package/packages/cases/case-recheck-model.mjs +35 -5
- package/packages/cases/case-record-operations.mjs +87 -9
- package/packages/cases/case-record-projection.mjs +43 -5
- package/packages/cases/case-record-version-input.mjs +23 -1
- package/packages/cases/case-report.mjs +35 -4
- package/packages/cases/case-requested-evidence.mjs +1 -1
- package/packages/cases/case-response-actions.mjs +57 -1
- package/packages/cases/case-response-model.mjs +7 -0
- package/packages/cases/case-response-object.mjs +119 -0
- package/packages/cases/case-response-outcomes.mjs +81 -14
- package/packages/cases/case-response-packet-render.mjs +22 -3
- package/packages/cases/case-response-packet-validation.mjs +880 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +12 -1
- package/packages/cases/case-response-packet.mjs +32 -17
- package/packages/cases/case-response-review-inputs.mjs +66 -8
- package/packages/cases/case-type-presentation.mjs +10 -0
- package/packages/cases/case-workflow-metadata.mjs +1 -17
- package/packages/comparison/capture-context.mjs +3 -2
- package/packages/contracts/analyst-interchange.mjs +7 -5
- package/packages/contracts/analyst-review-state-contract.mjs +1 -1
- package/packages/contracts/candidate-watch-review.mjs +54 -0
- package/packages/contracts/case-portability.mjs +292 -43
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/cli-doctor.mjs +4 -0
- package/packages/contracts/cli-lookup.mjs +57 -46
- package/packages/contracts/context-review.mjs +2 -1
- package/packages/contracts/external-observation-interchange.mjs +96 -28
- package/packages/contracts/lookup-evidence.mjs +13 -2
- package/packages/contracts/message-intake.mjs +7 -1
- package/packages/contracts/registry-insights.mjs +6 -0
- package/packages/contracts/risk-calibration.mjs +44 -29
- package/packages/contracts/schema-lifecycle.mjs +3 -1
- package/packages/contracts/web-capture.mjs +1 -1
- package/packages/contracts/workspace-portability.mjs +86 -16
- package/packages/evidence/collection-quality.mjs +1 -1
- package/packages/evidence/credential-form-attribution.mjs +60 -0
- package/packages/evidence/dmarc-policy-context.mjs +80 -0
- package/packages/evidence/image-regions.mjs +15 -0
- package/packages/evidence/lookup-collection-receipt.mjs +140 -0
- package/packages/evidence/lookup-collection-review.mjs +74 -0
- package/packages/evidence/record-identity.mjs +60 -16
- package/packages/evidence/technology-evidence-role.mjs +51 -0
- package/packages/interchange/external-findings-import.mjs +40 -21
- package/packages/interchange/json-output.mjs +14 -0
- package/packages/interchange/managed-indicator-export.mjs +3 -2
- package/packages/interchange/managed-indicator-set.mjs +13 -0
- package/packages/investigation/acquisition-decision-packet.mjs +78 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -2
- package/packages/investigation/context-review.mjs +3 -3
- package/packages/investigation/document-intake.mjs +9 -3
- package/packages/investigation/domain-history-review.mjs +50 -12
- package/packages/investigation/domain-transition-review.mjs +2 -0
- package/packages/investigation/infrastructure-observation.mjs +234 -0
- package/packages/investigation/intake-context.mjs +284 -0
- package/packages/investigation/intake-indicators.mjs +116 -0
- package/packages/investigation/intake-phones.mjs +155 -0
- package/packages/investigation/intake-report.mjs +2 -1
- package/packages/investigation/investigation-manifest.mjs +33 -7
- package/packages/investigation/link-intake.mjs +38 -8
- package/packages/investigation/message-intake.mjs +55 -15
- package/packages/monitoring/domain-feed.mjs +321 -0
- package/packages/workspace/brand-candidate-workflow.mjs +351 -0
- package/packages/workspace/brand-keyword-campaign.mjs +241 -0
- package/packages/workspace/brand-profile-model.mjs +46 -2
- package/packages/workspace/bulk-session-model.mjs +12 -0
- package/packages/workspace/candidate-watch-handoff.mjs +195 -0
- package/packages/workspace/detection-rule-model.mjs +10 -2
- package/packages/workspace/shortlist-model.mjs +10 -2
- package/packages/workspace/watchlist-history.mjs +31 -4
- package/packages/workspace/watchlist-store.mjs +136 -7
- package/third-party-notices.txt +30 -30
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
|
|
2
2
|
import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
|
|
3
3
|
export const RISK_CALIBRATION_DATASET_SCHEMA = 'whoisleuth.risk-calibration-dataset';
|
|
4
|
-
export const RISK_CALIBRATION_DATASET_VERSION =
|
|
5
|
-
export const SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS = Object.freeze([RISK_CALIBRATION_DATASET_VERSION]);
|
|
4
|
+
export const RISK_CALIBRATION_DATASET_VERSION = 3;
|
|
5
|
+
export const SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS = Object.freeze([2, RISK_CALIBRATION_DATASET_VERSION]);
|
|
6
6
|
export const RISK_CALIBRATION_REPORT_SCHEMA = 'whoisleuth.cli.risk-calibration';
|
|
7
7
|
export const RISK_CALIBRATION_REPORT_VERSION = 3;
|
|
8
8
|
export const SUPPORTED_RISK_CALIBRATION_REPORT_VERSIONS = Object.freeze([RISK_CALIBRATION_REPORT_VERSION]);
|
|
@@ -82,7 +82,7 @@ export const RISK_CALIBRATION_EVIDENCE_OPTIONAL_KEYS = Object.freeze([
|
|
|
82
82
|
'activityStatus', 'mutationTypes', 'domainAgeDays', 'faviconMatch',
|
|
83
83
|
'faviconNearMatch', 'reusesOfficialAssets', 'hasPasswordField', 'hasMx',
|
|
84
84
|
'hasSpf', 'hasDmarc', 'privacyProtected', 'phishingLanguageMatch',
|
|
85
|
-
'hasExternalFormAction', 'idnReferenceMatch', 'pageBaselineMatch',
|
|
85
|
+
'hasExternalFormAction', 'hasExternalPasswordForm', 'idnReferenceMatch', 'pageBaselineMatch',
|
|
86
86
|
'hasActiveBrandProfile', 'scanDepth', 'observedAt', 'threatIntelligence',
|
|
87
87
|
]);
|
|
88
88
|
export const RISK_CALIBRATION_THREAT_INTELLIGENCE_KEYS = Object.freeze([
|
|
@@ -179,7 +179,7 @@ export const RISK_CALIBRATION_DATASET_COMPATIBILITY = defineSchemaCompatibility(
|
|
|
179
179
|
writeSemantics: 'read_only',
|
|
180
180
|
byteBudget: MAX_RISK_CALIBRATION_INPUT_BYTES,
|
|
181
181
|
owner: 'packages/contracts/risk-calibration.mts',
|
|
182
|
-
note: 'Offline labelled fixture input
|
|
182
|
+
note: 'Offline labelled fixture input. Version 2 retains bounded review context; version 3 adds explicit form attribution. Historical records remain readable without manufacturing missing associations.',
|
|
183
183
|
});
|
|
184
184
|
export const RISK_CALIBRATION_REPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
185
185
|
id: 'cli.risk-calibration-report',
|
|
@@ -292,22 +292,22 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
292
292
|
RISK_CALIBRATION_REPORT_COMPATIBILITY,
|
|
293
293
|
],
|
|
294
294
|
contracts: [
|
|
295
|
-
{
|
|
295
|
+
...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map((version) => ({
|
|
296
296
|
compatibilityId: RISK_CALIBRATION_DATASET_COMPATIBILITY.id,
|
|
297
297
|
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
298
|
-
version
|
|
298
|
+
version,
|
|
299
299
|
role: 'document',
|
|
300
|
-
lifecycle: 'current',
|
|
300
|
+
lifecycle: version === RISK_CALIBRATION_DATASET_VERSION ? 'current' : 'legacy',
|
|
301
301
|
readable: true,
|
|
302
|
-
emitted:
|
|
302
|
+
emitted: version === RISK_CALIBRATION_DATASET_VERSION,
|
|
303
303
|
exactKeys: false,
|
|
304
304
|
extensionPolicy: 'discard_bounded',
|
|
305
305
|
futureVersionBehaviour: 'reject',
|
|
306
306
|
migrationTarget: null,
|
|
307
307
|
canonicalisation: null,
|
|
308
308
|
byteBudget: MAX_RISK_CALIBRATION_INPUT_BYTES,
|
|
309
|
-
fixtureIds: [
|
|
310
|
-
},
|
|
309
|
+
fixtureIds: [`risk-calibration-dataset-v${version}`],
|
|
310
|
+
})),
|
|
311
311
|
{
|
|
312
312
|
compatibilityId: RISK_CALIBRATION_REPORT_COMPATIBILITY.id,
|
|
313
313
|
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
@@ -329,6 +329,20 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
329
329
|
},
|
|
330
330
|
],
|
|
331
331
|
fixtures: [
|
|
332
|
+
{
|
|
333
|
+
id: 'risk-calibration-dataset-v3',
|
|
334
|
+
path: 'test/fixtures/risk-calibration-dataset-v3.json',
|
|
335
|
+
bytes: 1_096,
|
|
336
|
+
sha256: 'a8e41c1325094aec5ef4731fdf9f487a20999729f6cc704e400c3d83db2a0753',
|
|
337
|
+
contentDigestSha256: null,
|
|
338
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
339
|
+
version: 3,
|
|
340
|
+
role: 'current',
|
|
341
|
+
expectation: 'accepted_exact',
|
|
342
|
+
expectedOutputFixtureId: null,
|
|
343
|
+
scope: 'repository',
|
|
344
|
+
shapeId: 'risk-calibration.dataset.v3',
|
|
345
|
+
},
|
|
332
346
|
{
|
|
333
347
|
id: 'risk-calibration-dataset-v2',
|
|
334
348
|
path: 'test/fixtures/risk-calibration-dataset-v2.json',
|
|
@@ -336,8 +350,8 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
336
350
|
sha256: 'bd650fe84923c61658d451c25d928bd5ff8e54ff585f4f059e6798c681f9a401',
|
|
337
351
|
contentDigestSha256: null,
|
|
338
352
|
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
339
|
-
version:
|
|
340
|
-
role: '
|
|
353
|
+
version: 2,
|
|
354
|
+
role: 'historical',
|
|
341
355
|
expectation: 'accepted_exact',
|
|
342
356
|
expectedOutputFixtureId: null,
|
|
343
357
|
scope: 'repository',
|
|
@@ -375,11 +389,11 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
375
389
|
metadata: {
|
|
376
390
|
enforcement: 'declarative_only',
|
|
377
391
|
shapes: [
|
|
378
|
-
...
|
|
392
|
+
...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map((version) => ({
|
|
379
393
|
id: `risk-calibration.dataset.v${version}`,
|
|
380
394
|
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
381
395
|
versions: [version],
|
|
382
|
-
objects: DATASET_SHAPE_OBJECTS,
|
|
396
|
+
objects: version === 2 ? DATASET_SHAPE_OBJECTS.map(object => ({ ...object, optionalKeys: object.optionalKeys.filter(key => key !== 'hasExternalPasswordForm') })) : DATASET_SHAPE_OBJECTS,
|
|
383
397
|
fixedArrays: [],
|
|
384
398
|
normalisation: 'project_known_fields',
|
|
385
399
|
target: null,
|
|
@@ -609,7 +623,7 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
609
623
|
operation: 'build-dataset-download',
|
|
610
624
|
acceptedContracts: [],
|
|
611
625
|
emittedContract: { schema: RISK_CALIBRATION_DATASET_SCHEMA, version: RISK_CALIBRATION_DATASET_VERSION, discriminator: null },
|
|
612
|
-
shapeIds: [
|
|
626
|
+
shapeIds: [`risk-calibration.dataset.v${RISK_CALIBRATION_DATASET_VERSION}`],
|
|
613
627
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2'],
|
|
614
628
|
hookIds: ['risk-calibration.browser.build-dataset', 'risk-calibration.browser.serialise-dataset'],
|
|
615
629
|
serialisationProfileId: 'risk-calibration.dataset-json.v2',
|
|
@@ -624,9 +638,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
624
638
|
id: 'risk-calibration.cli-detailed-json-stdout',
|
|
625
639
|
plane: 'cli',
|
|
626
640
|
operation: 'build-detailed-json-stdout',
|
|
627
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
641
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
628
642
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
629
|
-
shapeIds: [
|
|
643
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-detailed'],
|
|
630
644
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.report-detailed-json-output.v3'],
|
|
631
645
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.serialise-report'],
|
|
632
646
|
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
@@ -641,9 +655,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
641
655
|
id: 'risk-calibration.cli-dataset-read',
|
|
642
656
|
plane: 'cli',
|
|
643
657
|
operation: 'read-dataset',
|
|
644
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
658
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
645
659
|
emittedContract: null,
|
|
646
|
-
shapeIds:
|
|
660
|
+
shapeIds: SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`),
|
|
647
661
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2'],
|
|
648
662
|
hookIds: ['risk-calibration.cli.parse-dataset'],
|
|
649
663
|
serialisationProfileId: null,
|
|
@@ -658,9 +672,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
658
672
|
id: 'risk-calibration.cli-detailed-terminal-stdout',
|
|
659
673
|
plane: 'cli',
|
|
660
674
|
operation: 'build-detailed-terminal-stdout',
|
|
661
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
675
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
662
676
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
663
|
-
shapeIds: [
|
|
677
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-detailed'],
|
|
664
678
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3'],
|
|
665
679
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.format-terminal'],
|
|
666
680
|
serialisationProfileId: null,
|
|
@@ -675,9 +689,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
675
689
|
id: 'risk-calibration.cli-summary-json-stdout',
|
|
676
690
|
plane: 'cli',
|
|
677
691
|
operation: 'build-summary-json-stdout',
|
|
678
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
692
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
679
693
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'summary' } },
|
|
680
|
-
shapeIds: [
|
|
694
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-summary'],
|
|
681
695
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-summary-output.v3'],
|
|
682
696
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.shared.build-summary', 'risk-calibration.cli.serialise-report'],
|
|
683
697
|
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
@@ -692,9 +706,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
692
706
|
id: 'risk-calibration.cli-detailed-json-file',
|
|
693
707
|
plane: 'cli',
|
|
694
708
|
operation: 'build-detailed-json-file',
|
|
695
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
709
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
696
710
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
697
|
-
shapeIds: [
|
|
711
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-detailed'],
|
|
698
712
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.report-detailed-json-output.v3', 'risk-calibration.cli-private-output.v1'],
|
|
699
713
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.serialise-report', 'risk-calibration.cli.write-private-file'],
|
|
700
714
|
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
@@ -709,9 +723,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
709
723
|
id: 'risk-calibration.cli-detailed-terminal-file',
|
|
710
724
|
plane: 'cli',
|
|
711
725
|
operation: 'build-detailed-terminal-file',
|
|
712
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
726
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
713
727
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
714
|
-
shapeIds: [
|
|
728
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-detailed'],
|
|
715
729
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.cli-private-output.v1'],
|
|
716
730
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.format-terminal', 'risk-calibration.cli.write-private-file'],
|
|
717
731
|
serialisationProfileId: null,
|
|
@@ -726,9 +740,9 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
|
726
740
|
id: 'risk-calibration.cli-summary-json-file',
|
|
727
741
|
plane: 'cli',
|
|
728
742
|
operation: 'build-summary-json-file',
|
|
729
|
-
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [
|
|
743
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS], mode: 'direct', discriminator: null }],
|
|
730
744
|
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'summary' } },
|
|
731
|
-
shapeIds: [
|
|
745
|
+
shapeIds: [...SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS.map(version => `risk-calibration.dataset.v${version}`), 'risk-calibration.report.v3-summary'],
|
|
732
746
|
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-summary-output.v3', 'risk-calibration.cli-private-output.v1'],
|
|
733
747
|
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.shared.build-summary', 'risk-calibration.cli.serialise-report', 'risk-calibration.cli.write-private-file'],
|
|
734
748
|
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
@@ -1176,6 +1190,7 @@ const DATASET_EVIDENCE_SPEC = objectOf([
|
|
|
1176
1190
|
{ key: 'privacyProtected', value: BOOLEAN_SPEC, optional: true },
|
|
1177
1191
|
{ key: 'phishingLanguageMatch', value: boundedText(MAX_RISK_CALIBRATION_STRING_LENGTH), optional: true },
|
|
1178
1192
|
{ key: 'hasExternalFormAction', value: BOOLEAN_SPEC, optional: true },
|
|
1193
|
+
{ key: 'hasExternalPasswordForm', value: BOOLEAN_SPEC, optional: true },
|
|
1179
1194
|
{ key: 'idnReferenceMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1180
1195
|
{ key: 'pageBaselineMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1181
1196
|
{ key: 'hasActiveBrandProfile', value: BOOLEAN_SPEC, optional: true },
|
|
@@ -640,11 +640,12 @@ function copyHook(value, index, budget) {
|
|
|
640
640
|
}
|
|
641
641
|
function copySerialisationProfile(value, index, budget) {
|
|
642
642
|
const label = `Schema lifecycle serialisation profile ${index + 1}`;
|
|
643
|
-
const source = ordinaryRecord(value, SERIALISATION_PROFILE_KEYS, label, budget);
|
|
643
|
+
const source = ordinaryRecord(value, SERIALISATION_PROFILE_KEYS, label, budget, ['compactFallback']);
|
|
644
644
|
if (source.mediaType !== 'application/json'
|
|
645
645
|
|| source.encoding !== 'utf-8'
|
|
646
646
|
|| source.bom !== false
|
|
647
647
|
|| (source.indentSpaces !== 0 && source.indentSpaces !== 2)
|
|
648
|
+
|| (source.compactFallback !== undefined && (source.compactFallback !== true || source.indentSpaces !== 2))
|
|
648
649
|
|| typeof source.terminalLf !== 'boolean'
|
|
649
650
|
|| (source.propertyOrder !== 'normalised_fixed' && source.propertyOrder !== 'source_insertion')
|
|
650
651
|
|| (source.integrity !== 'none'
|
|
@@ -664,6 +665,7 @@ function copySerialisationProfile(value, index, budget) {
|
|
|
664
665
|
encoding: 'utf-8',
|
|
665
666
|
bom: false,
|
|
666
667
|
indentSpaces: source.indentSpaces,
|
|
668
|
+
...(source.compactFallback === true ? { compactFallback: true } : {}),
|
|
667
669
|
terminalLf: source.terminalLf,
|
|
668
670
|
propertyOrder: source.propertyOrder,
|
|
669
671
|
canonicalisation: source.canonicalisation === null
|
|
@@ -2,7 +2,7 @@ export { MAX_WEB_CAPTURE_MANIFEST_BYTES, WEB_CAPTURE_MANIFEST_SCHEMA, WEB_CAPTUR
|
|
|
2
2
|
export const WEB_CAPTURE_DOM_DIGEST_SCHEMA = 'whoisleuth.dom-digest';
|
|
3
3
|
export const WEB_CAPTURE_DOM_DIGEST_VERSION = 1;
|
|
4
4
|
export const WEB_CAPTURE_COMPARISON_SCHEMA = 'whoisleuth.web-capture-comparison';
|
|
5
|
-
export const WEB_CAPTURE_COMPARISON_VERSION =
|
|
5
|
+
export const WEB_CAPTURE_COMPARISON_VERSION = 5;
|
|
6
6
|
export const MAX_WEB_CAPTURE_DOM_DIGEST_BYTES = 1024 * 1024;
|
|
7
7
|
export const MAX_WEB_CAPTURE_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
8
8
|
export const MAX_WEB_CAPTURE_DOM_ELEMENTS = 20_000;
|
|
@@ -7,11 +7,13 @@ export const BRAND_PROFILE_SCHEMA = 'whoisleuth.brand-profiles';
|
|
|
7
7
|
export const PUBLIC_BRAND_PROFILE_SCHEMA_VERSION = 6;
|
|
8
8
|
// Historical readers retain a fixed published identity when the writer moves.
|
|
9
9
|
export const PUBLISHED_V2_2_BRAND_PROFILE_SCHEMA_VERSION = 7;
|
|
10
|
-
export const BRAND_PROFILE_SCHEMA_VERSION =
|
|
10
|
+
export const BRAND_PROFILE_SCHEMA_VERSION = 11;
|
|
11
11
|
export const BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([
|
|
12
12
|
PUBLIC_BRAND_PROFILE_SCHEMA_VERSION,
|
|
13
13
|
PUBLISHED_V2_2_BRAND_PROFILE_SCHEMA_VERSION,
|
|
14
14
|
8,
|
|
15
|
+
9,
|
|
16
|
+
10,
|
|
15
17
|
BRAND_PROFILE_SCHEMA_VERSION,
|
|
16
18
|
]);
|
|
17
19
|
export const SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS = BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS;
|
|
@@ -21,6 +23,17 @@ export const MAX_PROFILE_VALUE_INPUTS = MAX_PROFILE_VALUES * 4;
|
|
|
21
23
|
export const MAX_PROFILE_STORE_BYTES = 4 * 1024 * 1024;
|
|
22
24
|
export const MAX_PROFILE_IMPORT_BYTES = MAX_PROFILE_STORE_BYTES * 8;
|
|
23
25
|
export const MAX_PROFILE_NAME_LENGTH = 100;
|
|
26
|
+
export const MAX_CANDIDATE_OBSERVATIONS = 200;
|
|
27
|
+
export const MAX_CANDIDATE_MATCHES = 20;
|
|
28
|
+
export const MAX_CANDIDATE_SOURCES = 12;
|
|
29
|
+
export const MAX_CANDIDATE_TEXT = 300;
|
|
30
|
+
export const MAX_CANDIDATE_EXCEPTIONS = 200;
|
|
31
|
+
export const MAX_CANDIDATE_EXCEPTION_HISTORY = 8;
|
|
32
|
+
export const MAX_BRAND_KEYWORD_CAMPAIGNS = 20;
|
|
33
|
+
export const MAX_BRAND_KEYWORD_CAMPAIGN_HISTORY = 8;
|
|
34
|
+
export const MAX_BRAND_KEYWORD_TERMS = 20;
|
|
35
|
+
export const MAX_BRAND_KEYWORD_TERM_LENGTH = 80;
|
|
36
|
+
export const MAX_WATCH_CONTEXTS = 20;
|
|
24
37
|
export const MAX_PROFILE_TEXT_LENGTH = 200;
|
|
25
38
|
export const MAX_PROFILE_DOMAIN_LENGTH = 253;
|
|
26
39
|
export const MAX_PROFILE_URL_LENGTH = 2_048;
|
|
@@ -47,14 +60,21 @@ export const MAX_CAMPAIGN_IMPORT_BYTES = 2 * 1024 * 1024;
|
|
|
47
60
|
export const MAX_CAMPAIGN_INPUT_RECORDS = 500;
|
|
48
61
|
export const MAX_CAMPAIGN_STORE_BYTES = 512 * 1024;
|
|
49
62
|
export const WATCHLIST_SCHEMA = 'whoisleuth.watchlists';
|
|
50
|
-
export const WATCHLIST_SCHEMA_VERSION =
|
|
51
|
-
export const WATCHLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([2, 3, WATCHLIST_SCHEMA_VERSION]);
|
|
63
|
+
export const WATCHLIST_SCHEMA_VERSION = 6;
|
|
64
|
+
export const WATCHLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([2, 3, 4, 5, WATCHLIST_SCHEMA_VERSION]);
|
|
52
65
|
export const WATCHLIST_EXPORT_SUPPORTED_VERSIONS = WATCHLIST_BROWSER_SUPPORTED_VERSIONS;
|
|
53
66
|
export const MAX_WATCHLISTS = 100;
|
|
54
67
|
export const MAX_WATCHLIST_INPUTS = MAX_WATCHLISTS * 4;
|
|
55
68
|
export const MAX_WATCHLIST_NAME_LENGTH = 100;
|
|
56
69
|
export const MAX_WATCHLIST_STORE_BYTES = 2 * 1024 * 1024;
|
|
57
70
|
export const MAX_WATCHLIST_IMPORT_BYTES = 2 * 1024 * 1024;
|
|
71
|
+
// Recovery metadata is not new analyst data. Reserve only its exact JSON cost;
|
|
72
|
+
// the store writer still enforces the ordinary budget after subtracting it.
|
|
73
|
+
export const WATCHLIST_RECOVERY_METADATA = Object.freeze({ membershipRecovery: 'legacy_overflow' });
|
|
74
|
+
export const WATCHLIST_RECOVERY_METADATA_BYTES = JSON.stringify(WATCHLIST_RECOVERY_METADATA).length - 1;
|
|
75
|
+
export const MAX_WATCHLIST_READ_BYTES = MAX_WATCHLIST_STORE_BYTES + MAX_WATCHLISTS * WATCHLIST_RECOVERY_METADATA_BYTES;
|
|
76
|
+
// Date.toISOString() produces at most 27 ASCII characters, including extended years.
|
|
77
|
+
export const MAX_WATCHLIST_PORTABLE_BYTES = MAX_WATCHLIST_READ_BYTES + JSON.stringify({ exportedAt: '+999999-12-31T23:59:59.999Z' }).length - 1;
|
|
58
78
|
export const MAX_WATCHLIST_HISTORY_EVENTS = 12;
|
|
59
79
|
export const MAX_WATCHLIST_CHANGES_PER_EVENT = 500;
|
|
60
80
|
export const MAX_WATCHLIST_DOMAINS = 2_000;
|
|
@@ -82,9 +102,9 @@ export const MAX_CT_HISTORY_EVER_SEEN_DOMAINS = 1_000;
|
|
|
82
102
|
export const MAX_CT_HISTORY_QUERY_LENGTH = 200;
|
|
83
103
|
export const MAX_CT_HISTORY_STORE_BYTES = 1024 * 1024;
|
|
84
104
|
export const DETECTION_RULE_SCHEMA = 'whoisleuth.detection-rules';
|
|
85
|
-
export const DETECTION_RULE_SCHEMA_VERSION =
|
|
86
|
-
export const DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([DETECTION_RULE_SCHEMA_VERSION]);
|
|
87
|
-
export const DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS =
|
|
105
|
+
export const DETECTION_RULE_SCHEMA_VERSION = 2;
|
|
106
|
+
export const DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([1, DETECTION_RULE_SCHEMA_VERSION]);
|
|
107
|
+
export const DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS = DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS;
|
|
88
108
|
export const MAX_DETECTION_RULES = 50;
|
|
89
109
|
export const MAX_RULE_CONDITIONS = 8;
|
|
90
110
|
export const MAX_RULE_INPUT_RECORDS = 250;
|
|
@@ -114,8 +134,8 @@ export const MAX_WEBSITE_SNAPSHOTS_PER_DOMAIN = 12;
|
|
|
114
134
|
export const MAX_WEBSITE_SNAPSHOT_STORE_BYTES = 512 * 1024;
|
|
115
135
|
export const MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES = 768 * 1024;
|
|
116
136
|
export const BULK_SESSION_SCHEMA = 'whoisleuth.bulk-sessions';
|
|
117
|
-
export const BULK_SESSION_SCHEMA_VERSION =
|
|
118
|
-
export const BULK_SESSION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([4, 5, BULK_SESSION_SCHEMA_VERSION]);
|
|
137
|
+
export const BULK_SESSION_SCHEMA_VERSION = 7;
|
|
138
|
+
export const BULK_SESSION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([4, 5, 6, BULK_SESSION_SCHEMA_VERSION]);
|
|
119
139
|
export const SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS = BULK_SESSION_BROWSER_SUPPORTED_VERSIONS;
|
|
120
140
|
export const MAX_BULK_SESSIONS = 10;
|
|
121
141
|
export const MAX_BULK_SESSION_ROWS = 2_000;
|
|
@@ -180,12 +200,14 @@ export const WORKSPACE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
|
|
|
180
200
|
export const WORKSPACE_PORTABILITY_BOUND_CONSTANTS = Object.freeze([
|
|
181
201
|
'MAX_PROFILES', 'MAX_PROFILE_VALUES', 'MAX_PROFILE_VALUE_INPUTS', 'MAX_PROFILE_STORE_BYTES', 'MAX_PROFILE_IMPORT_BYTES',
|
|
182
202
|
'MAX_PROFILE_NAME_LENGTH', 'MAX_PROFILE_TEXT_LENGTH', 'MAX_PROFILE_DOMAIN_LENGTH', 'MAX_PROFILE_URL_LENGTH', 'MAX_PROFILE_TLD_LENGTH',
|
|
203
|
+
'MAX_BRAND_KEYWORD_CAMPAIGNS', 'MAX_BRAND_KEYWORD_CAMPAIGN_HISTORY', 'MAX_BRAND_KEYWORD_TERMS', 'MAX_BRAND_KEYWORD_TERM_LENGTH',
|
|
183
204
|
'MAX_OFFICIAL_CHANNELS', 'MAX_RIGHTS_REFERENCES', 'MAX_DKIM_SELECTOR_LENGTH',
|
|
184
205
|
'MAX_DKIM_SELECTORS', 'MAX_PROTECTION_ATTESTATIONS', 'MAX_DESIRED_POSTURE_BASELINES', 'MAX_DESIRED_POSTURE_RECORDS',
|
|
185
206
|
'MAX_DESIRED_POSTURE_SUPPRESSIONS', 'MAX_DESIRED_POSTURE_OBSERVATIONS', 'MAX_DESIRED_POSTURE_CHANGE_WINDOWS',
|
|
186
207
|
'MAX_CAMPAIGNS', 'MAX_CAMPAIGN_DOMAINS', 'MAX_CAMPAIGN_NAME_LENGTH', 'MAX_CAMPAIGN_DESCRIPTION_LENGTH',
|
|
187
208
|
'MAX_CAMPAIGN_IMPORT_BYTES', 'MAX_CAMPAIGN_INPUT_RECORDS', 'MAX_CAMPAIGN_STORE_BYTES',
|
|
188
209
|
'MAX_WATCHLISTS', 'MAX_WATCHLIST_INPUTS', 'MAX_WATCHLIST_NAME_LENGTH', 'MAX_WATCHLIST_STORE_BYTES', 'MAX_WATCHLIST_IMPORT_BYTES',
|
|
210
|
+
'MAX_WATCHLIST_READ_BYTES', 'MAX_WATCHLIST_PORTABLE_BYTES',
|
|
189
211
|
'MAX_WATCHLIST_HISTORY_EVENTS', 'MAX_WATCHLIST_CHANGES_PER_EVENT', 'MAX_WATCHLIST_DOMAINS', 'MAX_WATCHLIST_INPUT_RECORDS',
|
|
190
212
|
'MAX_WATCHLIST_NAMESERVERS', 'MAX_WATCHLIST_MUTATION_TYPES', 'MAX_WATCHLIST_HISTORY_DOMAIN_OPTIONS',
|
|
191
213
|
'MAX_SHORTLIST_ENTRIES', 'MAX_SHORTLIST_INPUTS', 'MAX_SHORTLIST_STORE_BYTES', 'MAX_SHORTLIST_IMPORT_BYTES', 'MAX_SHORTLIST_FACTORS',
|
|
@@ -229,7 +251,7 @@ export const BRAND_PROFILE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
229
251
|
supportedVersions: BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
|
|
230
252
|
futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
|
|
231
253
|
byteBudget: MAX_PROFILE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
232
|
-
note: '
|
|
254
|
+
note: 'Versions 6–10 remain readable without invented keyword campaigns. Version 11 adds bounded, revisioned, time-limited Brand keyword intent without collection authority; candidate provenance and independent page-fingerprint algorithms remain intact.',
|
|
233
255
|
});
|
|
234
256
|
export const CAMPAIGN_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
235
257
|
id: 'browser.campaigns', kind: 'browser_store', schema: null, currentVersion: CAMPAIGN_SCHEMA_VERSION,
|
|
@@ -242,7 +264,7 @@ export const WATCHLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
242
264
|
id: 'browser.watchlists', kind: 'browser_store', schema: null, currentVersion: WATCHLIST_SCHEMA_VERSION,
|
|
243
265
|
supportedVersions: WATCHLIST_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
|
|
244
266
|
futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
|
|
245
|
-
byteBudget:
|
|
267
|
+
byteBudget: MAX_WATCHLIST_READ_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
246
268
|
note: 'Public version 2 remains readable without inferred collection outcomes. Version 3 retains explicit page and favicon collection quality; unversioned maps are outside the compatibility boundary.',
|
|
247
269
|
});
|
|
248
270
|
export const SHORTLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
@@ -262,7 +284,7 @@ export const CT_HISTORY_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
262
284
|
export const DETECTION_RULE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
|
|
263
285
|
id: 'browser.detection-rules', kind: 'browser_store', schema: null, currentVersion: DETECTION_RULE_SCHEMA_VERSION,
|
|
264
286
|
supportedVersions: DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
|
|
265
|
-
futureVersionBehavior: 'preserve_without_write', migration: '
|
|
287
|
+
futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
|
|
266
288
|
byteBudget: MAX_RULE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
267
289
|
note: 'Only allowlisted structured rule fields and operators survive normalisation; the schema string belongs to portable exports.',
|
|
268
290
|
});
|
|
@@ -306,7 +328,7 @@ export const BRAND_PROFILE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
306
328
|
supportedVersions: SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS, acceptsUnversionedLegacy: false,
|
|
307
329
|
futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
|
|
308
330
|
byteBudget: MAX_PROFILE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
309
|
-
note: 'Supported exports merge non-destructively by bounded
|
|
331
|
+
note: 'Supported exports merge non-destructively by bounded profile identity. Candidate decisions cannot be reassigned to a different Brand identifier with the same name.',
|
|
310
332
|
});
|
|
311
333
|
export const CAMPAIGN_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
312
334
|
id: 'export.campaigns', kind: 'export', schema: CAMPAIGN_SCHEMA, currentVersion: CAMPAIGN_SCHEMA_VERSION,
|
|
@@ -319,8 +341,8 @@ export const WATCHLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
319
341
|
id: 'export.watchlists', kind: 'export', schema: WATCHLIST_SCHEMA, currentVersion: WATCHLIST_SCHEMA_VERSION,
|
|
320
342
|
supportedVersions: WATCHLIST_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
|
|
321
343
|
futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
|
|
322
|
-
byteBudget:
|
|
323
|
-
note: '
|
|
344
|
+
byteBudget: MAX_WATCHLIST_PORTABLE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
345
|
+
note: 'Versions 2–5 remain readable. Version 6 preserves older membership overflow as paused, exportable records without activating or pruning domains. New active membership remains bounded. Brand contexts and incomplete-check baselines remain preserved.',
|
|
324
346
|
});
|
|
325
347
|
export const SHORTLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
326
348
|
id: 'export.shortlist', kind: 'export', schema: SHORTLIST_SCHEMA, currentVersion: SHORTLIST_SCHEMA_VERSION,
|
|
@@ -332,7 +354,7 @@ export const SHORTLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
|
332
354
|
export const DETECTION_RULE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
333
355
|
id: 'export.detection-rules', kind: 'export', schema: DETECTION_RULE_SCHEMA, currentVersion: DETECTION_RULE_SCHEMA_VERSION,
|
|
334
356
|
supportedVersions: DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
|
|
335
|
-
futureVersionBehavior: 'reject', migration: '
|
|
357
|
+
futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
|
|
336
358
|
byteBudget: MAX_RULE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
|
|
337
359
|
note: 'Non-destructive rule merge; imported conditions remain allowlisted and non-executable.',
|
|
338
360
|
});
|
|
@@ -421,6 +443,15 @@ const WORKSPACE_LIFECYCLE_DEFINITIONS = Object.freeze([
|
|
|
421
443
|
itemBounds: [
|
|
422
444
|
{ id: 'profiles', path: 'profiles', maximum: MAX_PROFILES, handling: 'truncate' },
|
|
423
445
|
{ id: 'profile-values', path: 'profiles[].values', maximum: MAX_PROFILE_VALUES, handling: 'truncate' },
|
|
446
|
+
{ id: 'candidate-observations', path: 'profiles[].candidateObservations', maximum: MAX_CANDIDATE_OBSERVATIONS, handling: 'truncate' },
|
|
447
|
+
{ id: 'candidate-matches', path: 'profiles[].candidateObservations[].matches', maximum: MAX_CANDIDATE_MATCHES, handling: 'reject' },
|
|
448
|
+
{ id: 'candidate-sources', path: 'profiles[].candidateObservations[].sources', maximum: MAX_CANDIDATE_SOURCES, handling: 'reject' },
|
|
449
|
+
{ id: 'candidate-exceptions', path: 'profiles[].candidateExceptions', maximum: MAX_CANDIDATE_EXCEPTIONS, handling: 'truncate' },
|
|
450
|
+
{ id: 'exception-history', path: 'profiles[].candidateExceptions[].history', maximum: MAX_CANDIDATE_EXCEPTION_HISTORY, handling: 'truncate' },
|
|
451
|
+
{ id: 'keyword-campaigns', path: 'profiles[].keywordCampaigns', maximum: MAX_BRAND_KEYWORD_CAMPAIGNS, handling: 'reject' },
|
|
452
|
+
{ id: 'keyword-campaign-history', path: 'profiles[].keywordCampaigns[].history', maximum: MAX_BRAND_KEYWORD_CAMPAIGN_HISTORY, handling: 'reject' },
|
|
453
|
+
{ id: 'keyword-positive-terms', path: 'profiles[].keywordCampaigns[].positiveTerms', maximum: MAX_BRAND_KEYWORD_TERMS, handling: 'reject' },
|
|
454
|
+
{ id: 'keyword-negative-terms', path: 'profiles[].keywordCampaigns[].negativeTerms', maximum: MAX_BRAND_KEYWORD_TERMS, handling: 'reject' },
|
|
424
455
|
],
|
|
425
456
|
portable: {
|
|
426
457
|
descriptor: BRAND_PROFILE_EXPORT_COMPATIBILITY,
|
|
@@ -466,14 +497,17 @@ const WORKSPACE_LIFECYCLE_DEFINITIONS = Object.freeze([
|
|
|
466
497
|
{ id: 'watchlists', path: 'watchlists', maximum: MAX_WATCHLISTS, handling: 'truncate' },
|
|
467
498
|
{ id: 'domains', path: 'watchlists[].results', maximum: MAX_WATCHLIST_DOMAINS, handling: 'drop_value' },
|
|
468
499
|
{ id: 'history', path: 'watchlists[].history', maximum: MAX_WATCHLIST_HISTORY_EVENTS, handling: 'truncate' },
|
|
500
|
+
{ id: 'domain-contexts', path: 'watchlists[].domainMetadata', maximum: MAX_WATCHLIST_DOMAINS, handling: 'reject' },
|
|
501
|
+
{ id: 'brand-contexts', path: 'watchlists[].domainMetadata[].contexts', maximum: MAX_WATCH_CONTEXTS, handling: 'truncate' },
|
|
469
502
|
],
|
|
470
503
|
portable: {
|
|
471
504
|
descriptor: WATCHLIST_EXPORT_COMPATIBILITY,
|
|
472
505
|
schema: WATCHLIST_SCHEMA,
|
|
473
506
|
builder: 'buildWatchlistExport',
|
|
474
507
|
merger: 'mergeWatchlistStores',
|
|
475
|
-
serializerHookId: 'workspace.portable.json',
|
|
508
|
+
serializerHookId: 'workspace.portable.watchlist-json',
|
|
476
509
|
indentSpaces: 2,
|
|
510
|
+
compactFallback: true,
|
|
477
511
|
terminalLf: false,
|
|
478
512
|
},
|
|
479
513
|
},
|
|
@@ -649,6 +683,34 @@ const WORKSPACE_LIFECYCLE_DEFINITIONS = Object.freeze([
|
|
|
649
683
|
},
|
|
650
684
|
]);
|
|
651
685
|
const WORKSPACE_LIFECYCLE_FIXTURE_SOURCE = Object.freeze([
|
|
686
|
+
{ id: 'workspace.browser.brand.v11', path: 'test/fixtures/workspace-lifecycle/browser-brand-v11.json', bytes: 38, sha256: 'c727cd501774f0f89a39f399a01216065b6ed7048bfed97bbcdaf10e651a7133', schema: 'whoisleuth.browser.brand-profile-store', version: 11 },
|
|
687
|
+
{ id: 'workspace.portable.brand.v11', path: 'test/fixtures/workspace-lifecycle/portable-brand-v11.json', bytes: 123, sha256: 'c5023e1eebe187cf355c4fb53b7d8a755dc0ffb01a3deeb9b9dd7032007933d9', schema: 'whoisleuth.brand-profiles', version: 11 },
|
|
688
|
+
{ id: 'workspace.browser.watchlist.v6', path: 'test/fixtures/workspace-lifecycle/browser-watchlist-v6.json', bytes: 76, sha256: '16df0f813b17f1765862f5c2ff41c2db9c7d6d76e75f24746aa6d75b59d3f02e', schema: 'whoisleuth.browser.watchlist-store', version: 6 },
|
|
689
|
+
{ id: 'workspace.portable.watchlist.v6', path: 'test/fixtures/workspace-lifecycle/portable-watchlist-v6.json', bytes: 120, sha256: 'ced22d0d16e839e0b4625f8799eef30a776908abf3af998de6fad5b549898987', schema: 'whoisleuth.watchlists', version: 6 },
|
|
690
|
+
{ id: 'workspace.browser.brand.v10', path: 'test/fixtures/workspace-lifecycle/browser-brand-v10.json', bytes: 38, sha256: 'bebf3a72ba57c06246e6169ebd9a597eb5b867508db85e1db0e3d763bc0839bc', schema: 'whoisleuth.browser.brand-profile-store', version: 10 },
|
|
691
|
+
{ id: 'workspace.portable.brand.v10', path: 'test/fixtures/workspace-lifecycle/portable-brand-v10.json', bytes: 123, sha256: '85cd7c58696f8219484ac88210ad7ab4a0ad1c9798e8cb02ff6ebb3fd2c82d8a', schema: 'whoisleuth.brand-profiles', version: 10 },
|
|
692
|
+
{ id: 'workspace.browser.watchlist.v5', path: 'test/fixtures/workspace-lifecycle/browser-watchlist-v5.json', bytes: 76, sha256: '11eabd20f952611307a5c84291c7892c15d3748f7ada5549813e1d705967c2c0', schema: 'whoisleuth.browser.watchlist-store', version: 5 },
|
|
693
|
+
{ id: 'workspace.portable.watchlist.v5', path: 'test/fixtures/workspace-lifecycle/portable-watchlist-v5.json', bytes: 120, sha256: '732d4bda46e907bdd4d343ce36999a2787a0057029ee4dba0c4ef462f2822fed', schema: 'whoisleuth.watchlists', version: 5 },
|
|
694
|
+
{
|
|
695
|
+
id: 'workspace.browser.detection.v2', path: 'test/fixtures/workspace-lifecycle/browser-detection-v2.json',
|
|
696
|
+
bytes: 34, sha256: '2db6ae8eb9d24cb86924a7020c6f3eebfa41b5fc5848a53e239465db622c2312',
|
|
697
|
+
schema: 'whoisleuth.browser.detection-rule-store', version: 2,
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
id: 'workspace.portable.detection.v2', path: 'test/fixtures/workspace-lifecycle/portable-detection-v2.json',
|
|
701
|
+
bytes: 293, sha256: 'bf4409e18ef0b721f5a7e2f14a06f2fada88287114ec17775fef42e7ed38072a',
|
|
702
|
+
schema: 'whoisleuth.detection-rules', version: 2,
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: 'workspace.browser.bulk.v7', path: 'test/fixtures/workspace-lifecycle/browser-bulk-v7.json',
|
|
706
|
+
bytes: 77, sha256: '6b02cbd48a939a34d8f292da2b6bc8e8b59f3ed1b9c106a8e9e271ec0e949459',
|
|
707
|
+
schema: 'whoisleuth.browser.bulk-session-store', version: 7,
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
id: 'workspace.portable.bulk.v7', path: 'test/fixtures/workspace-lifecycle/portable-bulk-v7.json',
|
|
711
|
+
bytes: 338, sha256: 'f7e14d866eeb4e94b5390086e278348bfa0610ace72776fee4fbf24871ab3393',
|
|
712
|
+
schema: 'whoisleuth.bulk-sessions', version: 7,
|
|
713
|
+
},
|
|
652
714
|
{
|
|
653
715
|
id: 'workspace.browser.watchlist.v4', path: 'test/fixtures/workspace-lifecycle/browser-watchlist-v4.json',
|
|
654
716
|
bytes: 76, sha256: '059930ee54d1bc65ac68ef6d3a8ac8cd8e53f682fe9a857512e2b6c57e93d5fe',
|
|
@@ -1146,6 +1208,13 @@ const WORKSPACE_LIFECYCLE_HOOKS = Object.freeze([
|
|
|
1146
1208
|
module: WORKSPACE_CONTRACT_OWNER,
|
|
1147
1209
|
exportName: 'serialiseWorkspacePortableJson',
|
|
1148
1210
|
},
|
|
1211
|
+
{
|
|
1212
|
+
id: 'workspace.portable.watchlist-json',
|
|
1213
|
+
role: 'serialiser',
|
|
1214
|
+
runtime: 'shared',
|
|
1215
|
+
module: 'packages/workspace/watchlist-store.mts',
|
|
1216
|
+
exportName: 'serializeWatchlistExport',
|
|
1217
|
+
},
|
|
1149
1218
|
{
|
|
1150
1219
|
id: 'workspace.portable.json-line',
|
|
1151
1220
|
role: 'serialiser',
|
|
@@ -1254,6 +1323,7 @@ function workspacePortableSerialisation(definition) {
|
|
|
1254
1323
|
encoding: 'utf-8',
|
|
1255
1324
|
bom: false,
|
|
1256
1325
|
indentSpaces: definition.portable.indentSpaces,
|
|
1326
|
+
...(definition.portable.compactFallback ? { compactFallback: true } : {}),
|
|
1257
1327
|
terminalLf: definition.portable.terminalLf,
|
|
1258
1328
|
propertyOrder: 'normalised_fixed',
|
|
1259
1329
|
canonicalisation: null,
|
|
@@ -35,7 +35,7 @@ export function webCollectionQualityForCapture(value, depth) {
|
|
|
35
35
|
return capturedWebCollectionQuality('not_collected', 'not_collected');
|
|
36
36
|
return normalizeWebCollectionQuality(value) ?? capturedWebCollectionQuality('unknown', 'unknown');
|
|
37
37
|
}
|
|
38
|
-
const PAGE_FIELDS = new Set(['activityStatus', 'pageTitle', 'hasPasswordField', 'hasExternalFormAction', 'phishingLanguageMatch', 'pageBaselineMatch']);
|
|
38
|
+
const PAGE_FIELDS = new Set(['activityStatus', 'pageTitle', 'hasPasswordField', 'hasExternalFormAction', 'hasExternalPasswordForm', 'phishingLanguageMatch', 'pageBaselineMatch']);
|
|
39
39
|
const FAVICON_FIELDS = new Set(['faviconHash', 'faviconPHash', 'faviconMatch', 'faviconNearMatch']);
|
|
40
40
|
const COMBINED_FIELDS = new Set(['riskScore', 'riskFactors', 'riskModelVersion', 'opportunityScore', 'opportunityFactors', 'reusesOfficialAssets']);
|
|
41
41
|
/** Missing historical quality never proves that a false/null value was observed. */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const CREDENTIAL_CATEGORY_NAMES = ['password', 'email', 'username', 'one_time_code', 'payment'];
|
|
2
|
+
export const MAX_CREDENTIAL_FORMS = 50;
|
|
3
|
+
export const MAX_CREDENTIAL_INPUTS = 500;
|
|
4
|
+
export const MAX_FORM_DESTINATIONS = 10;
|
|
5
|
+
function record(value) {
|
|
6
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
7
|
+
}
|
|
8
|
+
function keys(value, expected) {
|
|
9
|
+
return Object.keys(value).length === expected.length && expected.every(key => Object.hasOwn(value, key));
|
|
10
|
+
}
|
|
11
|
+
const count = (value, max) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= max;
|
|
12
|
+
export function validCredentialFormAttribution(value) {
|
|
13
|
+
if (!record(value) || !keys(value, ['complete', 'unassociatedInputs', 'forms']) || typeof value.complete !== 'boolean'
|
|
14
|
+
|| !count(value.unassociatedInputs, MAX_CREDENTIAL_INPUTS) || !Array.isArray(value.forms) || value.forms.length > MAX_CREDENTIAL_FORMS)
|
|
15
|
+
return false;
|
|
16
|
+
let classified = 0;
|
|
17
|
+
for (const [index, form] of value.forms.entries()) {
|
|
18
|
+
if (!record(form) || !keys(form, ['index', 'categories', 'destinations']) || form.index !== index + 1
|
|
19
|
+
|| !record(form.categories) || !keys(form.categories, CREDENTIAL_CATEGORY_NAMES)
|
|
20
|
+
|| !CREDENTIAL_CATEGORY_NAMES.every(key => count(form.categories[key], MAX_CREDENTIAL_INPUTS))
|
|
21
|
+
|| !Array.isArray(form.destinations) || !form.destinations.length || form.destinations.length > MAX_FORM_DESTINATIONS)
|
|
22
|
+
return false;
|
|
23
|
+
classified += Math.max(...CREDENTIAL_CATEGORY_NAMES.map(key => Number(form.categories[key])));
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const destination of form.destinations) {
|
|
26
|
+
if (!record(destination) || !keys(destination, ['relationship', 'origin']))
|
|
27
|
+
return false;
|
|
28
|
+
if (destination.relationship === 'unknown' || destination.relationship === 'no_submission') {
|
|
29
|
+
if (destination.origin !== null || (destination.relationship === 'unknown' && value.complete))
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
else if (destination.relationship === 'external' || destination.relationship === 'same_origin') {
|
|
33
|
+
if (typeof destination.origin !== 'string' || destination.origin.length > 320)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(destination.origin);
|
|
37
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.origin !== destination.origin || url.username || url.password)
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
return false;
|
|
46
|
+
const identity = `${destination.relationship}:${destination.origin}`;
|
|
47
|
+
if (seen.has(identity))
|
|
48
|
+
return false;
|
|
49
|
+
seen.add(identity);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return classified + value.unassociatedInputs <= MAX_CREDENTIAL_INPUTS && (!value.complete || value.unassociatedInputs === 0);
|
|
53
|
+
}
|
|
54
|
+
export function externalPasswordFormObservation(value) {
|
|
55
|
+
if (!validCredentialFormAttribution(value))
|
|
56
|
+
return null;
|
|
57
|
+
if (value.forms.some(form => form.categories.password > 0 && form.destinations.some(destination => destination.relationship === 'external')))
|
|
58
|
+
return true;
|
|
59
|
+
return value.complete ? false : null;
|
|
60
|
+
}
|