diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d32911..2da54ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +- Scored webhook contract 2026-10-12 requires the 21 source detection flags, including independent os_mismatch and os_mismatch2. Current flags exclude browser_vpn_proxy, datacenter_ip, abuser and suspicious_paid_click; retained older bodies use a separate legacy schema. AI bot and verified AI browser examples come from Core fixture output. Scoring weights are unchanged. + +## Unreleased + - Scored webhook schema 2026-10-09 removes result_version and scoring_version. Legacy versions remain readable. ## Unreleased diff --git a/dist/schemas/identification-scored-event.schema.json b/dist/schemas/identification-scored-event.schema.json index 46b293c..3f4d6be 100644 --- a/dist/schemas/identification-scored-event.schema.json +++ b/dist/schemas/identification-scored-event.schema.json @@ -91,7 +91,8 @@ "enum": [ "2026-10-07", "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, @@ -118,19 +119,6 @@ "scoring_version": {}, "risk_events": {}, "fingerprint": {}, - "detection_flags": { - "type": "object", - "not": { - "type": "object", - "required": [ - "os_mismatch2" - ], - "properties": { - "os_mismatch2": {} - }, - "additionalProperties": true - } - }, "hre": { "type": "object", "properties": { @@ -164,7 +152,8 @@ }, "additionalProperties": true } - } + }, + "additionalProperties": true } }, "additionalProperties": true, @@ -204,13 +193,15 @@ "schema_version": { "enum": [ "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -235,7 +226,8 @@ "const": "browser_vpn_proxy" } } - } + }, + "additionalProperties": true } }, "detection_flags": { @@ -249,7 +241,8 @@ ], "properties": { "browser_vpn_proxy": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -258,7 +251,8 @@ ], "properties": { "datacenter_ip": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -267,7 +261,8 @@ ], "properties": { "abuser": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -276,14 +271,18 @@ ], "properties": { "suspicious_paid_click": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } }, { @@ -323,12 +322,16 @@ "type": "object", "properties": { "schema_version": { - "const": "2026-10-09" + "enum": [ + "2026-10-09", + "2026-10-12" + ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -344,7 +347,8 @@ ], "properties": { "result_version": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -353,12 +357,88 @@ ], "properties": { "scoring_version": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "enum": [ + "2026-10-07", + "2026-10-08", + "2026-10-09" + ] + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "type": "object", + "not": { + "type": "object", + "required": [ + "os_mismatch2" + ], + "properties": { + "os_mismatch2": {} + }, + "additionalProperties": true + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "const": "2026-10-12" + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "$ref": "#/$defs/DetectionFlags" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true } } ], @@ -405,79 +485,87 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "The 21 source detection signals in scored webhook contract 2026-10-12. Groups and owner attribution are separate from this set. The two OS mismatch detectors remain independent.", "required": [ "vpn", - "privacy_relay", - "tor", "proxy", - "os_mismatch", + "privacy_relay", + "check_incomplete", + "stun_not_checked", "os_not_detected", "timezone_mismatch", - "anti_detect_browser", - "browser_automation", + "javascript_disabled", + "os_mismatch", + "os_mismatch2", "ip_mismatch", + "latency_test", + "anti_detect_browser", + "device_spoofing", + "banned_ip", "incognito", "search_bot", - "javascript_disabled", - "stun_not_checked", - "check_incomplete" + "ai_bot", + "browser_automation", + "ai_browser", + "tor" ], "properties": { "vpn": { "type": "boolean", "description": "A VPN or Browser VPN/Proxy was detected." }, + "proxy": { + "type": "boolean", + "description": "A proxy was detected." + }, "privacy_relay": { "type": "boolean", "description": "A privacy relay such as iCloud Private Relay was detected." }, - "browser_vpn_proxy": { + "check_incomplete": { "type": "boolean", - "deprecated": true, - "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." + "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." }, - "tor": { + "stun_not_checked": { "type": "boolean", - "description": "The request came through the Tor network." + "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." }, - "proxy": { + "os_not_detected": { "type": "boolean", - "description": "A proxy was detected." + "description": "The operating system could not be determined from the User-Agent or the network." }, - "datacenter_ip": { + "timezone_mismatch": { "type": "boolean", - "deprecated": true, - "description": "The public IP belongs to a datacenter or hosting range." + "description": "The browser timezone differs from the timezone of the IP location." }, - "abuser": { + "javascript_disabled": { "type": "boolean", - "deprecated": true, - "description": "The public IP has a record of abuse in IP intelligence." + "description": "JavaScript, or the browser APIs the checks need, were unavailable." }, "os_mismatch": { "type": "boolean", - "description": "Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks." + "description": "The first operating system consistency detector found a mismatch. Independent of os_mismatch2." }, - "os_not_detected": { + "os_mismatch2": { "type": "boolean", - "description": "The operating system could not be determined from the User-Agent or the network." + "description": "The second operating system consistency detector (TCP behaviour) found a mismatch. Independent of os_mismatch." }, - "timezone_mismatch": { + "ip_mismatch": { "type": "boolean", - "description": "The browser timezone differs from the timezone of the IP location." + "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + }, + "latency_test": { + "type": "boolean" }, "anti_detect_browser": { "type": "boolean", "description": "An anti-detect browser was detected." }, - "browser_automation": { - "type": "boolean", - "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." + "device_spoofing": { + "type": "boolean" }, - "ip_mismatch": { - "type": "boolean", - "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + "banned_ip": { + "type": "boolean" }, "incognito": { "type": "boolean", @@ -487,46 +575,24 @@ "type": "boolean", "description": "A search-engine crawler. Its Risk Score is always 0." }, - "suspicious_paid_click": { - "type": "boolean", - "deprecated": true, - "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." - }, - "javascript_disabled": { - "type": "boolean", - "description": "JavaScript, or the browser APIs the checks need, were unavailable." - }, - "stun_not_checked": { - "type": "boolean", - "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." - }, - "check_incomplete": { + "ai_bot": { "type": "boolean", - "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." + "description": "An already accepted crawler has purpose training or user_fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. A User-Agent claim alone does not grant an accepted verdict." }, - "device_spoofing": { - "type": "boolean" - }, - "latency_test": { - "type": "boolean" - }, - "banned_ip": { - "type": "boolean" - }, - "ai_bot": { + "browser_automation": { "type": "boolean", - "description": "An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk." + "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." }, "ai_browser": { "type": "boolean", - "description": "A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative." + "description": "A browser has separately verified infrastructure or service attribution. A User-Agent claim or provider-only IP proof is insufficient." }, - "os_mismatch2": { + "tor": { "type": "boolean", - "deprecated": true, - "description": "Legacy 2026-10-06 only; current payloads combine this check into os_mismatch." + "description": "The request came through the Tor network." } - } + }, + "additionalProperties": false }, "DeviceId": { "type": "string", @@ -616,7 +682,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-09 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-12 uses signals for score contributions, detection_flags for 21 source signals, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -701,7 +767,8 @@ } }, "detection_flags": { - "$ref": "#/$defs/DetectionFlags" + "$ref": "#/$defs/LegacyDetectionFlags", + "description": "Contract 2026-10-12 requires DetectionFlags (exactly 21 source signals); this fallback supports retained older versions." }, "observed_at": { "$ref": "#/$defs/Rfc3339Timestamp", @@ -774,6 +841,131 @@ "" ] }, + "LegacyDetectionFlags": { + "type": "object", + "description": "Detection flags in retained webhook and History payloads before 2026-10-12. Missing extension flags are not evaluated negative results.", + "required": [ + "vpn", + "privacy_relay", + "tor", + "proxy", + "os_mismatch", + "os_not_detected", + "timezone_mismatch", + "anti_detect_browser", + "browser_automation", + "ip_mismatch", + "incognito", + "search_bot", + "javascript_disabled", + "stun_not_checked", + "check_incomplete" + ], + "properties": { + "vpn": { + "type": "boolean", + "description": "A VPN or Browser VPN/Proxy was detected." + }, + "privacy_relay": { + "type": "boolean", + "description": "A privacy relay such as iCloud Private Relay was detected." + }, + "browser_vpn_proxy": { + "type": "boolean", + "deprecated": true, + "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." + }, + "tor": { + "type": "boolean", + "description": "The request came through the Tor network." + }, + "proxy": { + "type": "boolean", + "description": "A proxy was detected." + }, + "datacenter_ip": { + "type": "boolean", + "deprecated": true, + "description": "The public IP belongs to a datacenter or hosting range." + }, + "abuser": { + "type": "boolean", + "deprecated": true, + "description": "The public IP has a record of abuse in IP intelligence." + }, + "os_mismatch": { + "type": "boolean", + "description": "Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks." + }, + "os_not_detected": { + "type": "boolean", + "description": "The operating system could not be determined from the User-Agent or the network." + }, + "timezone_mismatch": { + "type": "boolean", + "description": "The browser timezone differs from the timezone of the IP location." + }, + "anti_detect_browser": { + "type": "boolean", + "description": "An anti-detect browser was detected." + }, + "browser_automation": { + "type": "boolean", + "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." + }, + "ip_mismatch": { + "type": "boolean", + "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + }, + "incognito": { + "type": "boolean", + "description": "The browser runs in a private window." + }, + "search_bot": { + "type": "boolean", + "description": "A search-engine crawler. Its Risk Score is always 0." + }, + "suspicious_paid_click": { + "type": "boolean", + "deprecated": true, + "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." + }, + "javascript_disabled": { + "type": "boolean", + "description": "JavaScript, or the browser APIs the checks need, were unavailable." + }, + "stun_not_checked": { + "type": "boolean", + "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." + }, + "check_incomplete": { + "type": "boolean", + "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." + }, + "device_spoofing": { + "type": "boolean" + }, + "latency_test": { + "type": "boolean" + }, + "banned_ip": { + "type": "boolean" + }, + "ai_bot": { + "type": "boolean", + "description": "An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk." + }, + "ai_browser": { + "type": "boolean", + "description": "A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative." + }, + "os_mismatch2": { + "type": "boolean", + "deprecated": true, + "description": "Legacy 2026-10-06 only; current payloads combine this check into os_mismatch." + } + } + }, "OperatingSystem": { "type": "string", "description": "Operating system name, for example `Windows`, `Mac OS X`, `Linux`, `Android`, `IOS (iPhone)`, `IOS (iPad)`, `ChromeOS` or `Unknown`. Open set: display it, do not branch on it.", @@ -850,8 +1042,9 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-12 (21 independent source signals); multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-12", "2026-10-09", "2026-10-08", "2026-10-07", diff --git a/dist/schemas/identification.schema.json b/dist/schemas/identification.schema.json index 11c4995..319a018 100644 --- a/dist/schemas/identification.schema.json +++ b/dist/schemas/identification.schema.json @@ -122,7 +122,7 @@ } }, "detection_flags": { - "$ref": "#/$defs/DetectionFlags" + "$ref": "#/$defs/LegacyDetectionFlags" }, "observed_at": { "type": "string", @@ -380,9 +380,50 @@ "" ] }, - "DetectionFlags": { + "DeviceId": { + "type": "string", + "format": "uuid", + "description": "Server-side device identifier (UUID v5). It survives cleared cookies and private windows. The nil UUID `00000000-0000-0000-0000-000000000000` means that no usable device signals were collected (for example on rate-limit marker rows): never group identifications by it.", + "examples": [ + "d8e0f2a4-b6c8-4d0e-bf2a-4b6c8d0e2f4a", + "00000000-0000-0000-0000-000000000000" + ] + }, + "DeviceType": { + "type": "string", + "description": "Device class from the browser. Known values: `desktop`, `mobile`, `tablet` and `unknown` (the class could not be determined). The set is open: keep values added in later versions and treat them as `unknown`.", + "examples": [ + "desktop" + ] + }, + "IpInfo": { + "type": "object", + "description": "An IPv4 address and its country. Both keys are always present and can be empty strings.", + "required": [ + "ip", + "country" + ], + "properties": { + "ip": { + "$ref": "#/$defs/Ipv4OrEmpty" + }, + "country": { + "$ref": "#/$defs/Country" + } + } + }, + "Ipv4OrEmpty": { + "type": "string", + "pattern": "^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})?$", + "description": "Dotted IPv4 address, or an empty string when no IPv4 address is known (for example for visitors on IPv6).", + "examples": [ + "203.0.113.24", + "" + ] + }, + "LegacyDetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Detection flags in retained webhook and History payloads before 2026-10-12. Missing extension flags are not evaluated negative results.", "required": [ "vpn", "privacy_relay", @@ -505,47 +546,6 @@ } } }, - "DeviceId": { - "type": "string", - "format": "uuid", - "description": "Server-side device identifier (UUID v5). It survives cleared cookies and private windows. The nil UUID `00000000-0000-0000-0000-000000000000` means that no usable device signals were collected (for example on rate-limit marker rows): never group identifications by it.", - "examples": [ - "d8e0f2a4-b6c8-4d0e-bf2a-4b6c8d0e2f4a", - "00000000-0000-0000-0000-000000000000" - ] - }, - "DeviceType": { - "type": "string", - "description": "Device class from the browser. Known values: `desktop`, `mobile`, `tablet` and `unknown` (the class could not be determined). The set is open: keep values added in later versions and treat them as `unknown`.", - "examples": [ - "desktop" - ] - }, - "IpInfo": { - "type": "object", - "description": "An IPv4 address and its country. Both keys are always present and can be empty strings.", - "required": [ - "ip", - "country" - ], - "properties": { - "ip": { - "$ref": "#/$defs/Ipv4OrEmpty" - }, - "country": { - "$ref": "#/$defs/Country" - } - } - }, - "Ipv4OrEmpty": { - "type": "string", - "pattern": "^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})?$", - "description": "Dotted IPv4 address, or an empty string when no IPv4 address is known (for example for visitors on IPv6).", - "examples": [ - "203.0.113.24", - "" - ] - }, "OperatingSystem": { "type": "string", "description": "Operating system name, for example `Windows`, `Mac OS X`, `Linux`, `Android`, `IOS (iPhone)`, `IOS (iPad)`, `ChromeOS` or `Unknown`. Open set: display it, do not branch on it.", diff --git a/dist/schemas/webhook-event.schema.json b/dist/schemas/webhook-event.schema.json index 8145c85..c4899a7 100644 --- a/dist/schemas/webhook-event.schema.json +++ b/dist/schemas/webhook-event.schema.json @@ -115,79 +115,87 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "The 21 source detection signals in scored webhook contract 2026-10-12. Groups and owner attribution are separate from this set. The two OS mismatch detectors remain independent.", "required": [ "vpn", - "privacy_relay", - "tor", "proxy", - "os_mismatch", + "privacy_relay", + "check_incomplete", + "stun_not_checked", "os_not_detected", "timezone_mismatch", - "anti_detect_browser", - "browser_automation", + "javascript_disabled", + "os_mismatch", + "os_mismatch2", "ip_mismatch", + "latency_test", + "anti_detect_browser", + "device_spoofing", + "banned_ip", "incognito", "search_bot", - "javascript_disabled", - "stun_not_checked", - "check_incomplete" + "ai_bot", + "browser_automation", + "ai_browser", + "tor" ], "properties": { "vpn": { "type": "boolean", "description": "A VPN or Browser VPN/Proxy was detected." }, + "proxy": { + "type": "boolean", + "description": "A proxy was detected." + }, "privacy_relay": { "type": "boolean", "description": "A privacy relay such as iCloud Private Relay was detected." }, - "browser_vpn_proxy": { + "check_incomplete": { "type": "boolean", - "deprecated": true, - "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." + "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." }, - "tor": { + "stun_not_checked": { "type": "boolean", - "description": "The request came through the Tor network." + "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." }, - "proxy": { + "os_not_detected": { "type": "boolean", - "description": "A proxy was detected." + "description": "The operating system could not be determined from the User-Agent or the network." }, - "datacenter_ip": { + "timezone_mismatch": { "type": "boolean", - "deprecated": true, - "description": "The public IP belongs to a datacenter or hosting range." + "description": "The browser timezone differs from the timezone of the IP location." }, - "abuser": { + "javascript_disabled": { "type": "boolean", - "deprecated": true, - "description": "The public IP has a record of abuse in IP intelligence." + "description": "JavaScript, or the browser APIs the checks need, were unavailable." }, "os_mismatch": { "type": "boolean", - "description": "Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks." + "description": "The first operating system consistency detector found a mismatch. Independent of os_mismatch2." }, - "os_not_detected": { + "os_mismatch2": { "type": "boolean", - "description": "The operating system could not be determined from the User-Agent or the network." + "description": "The second operating system consistency detector (TCP behaviour) found a mismatch. Independent of os_mismatch." }, - "timezone_mismatch": { + "ip_mismatch": { "type": "boolean", - "description": "The browser timezone differs from the timezone of the IP location." + "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + }, + "latency_test": { + "type": "boolean" }, "anti_detect_browser": { "type": "boolean", "description": "An anti-detect browser was detected." }, - "browser_automation": { - "type": "boolean", - "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." + "device_spoofing": { + "type": "boolean" }, - "ip_mismatch": { - "type": "boolean", - "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + "banned_ip": { + "type": "boolean" }, "incognito": { "type": "boolean", @@ -197,46 +205,24 @@ "type": "boolean", "description": "A search-engine crawler. Its Risk Score is always 0." }, - "suspicious_paid_click": { - "type": "boolean", - "deprecated": true, - "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." - }, - "javascript_disabled": { - "type": "boolean", - "description": "JavaScript, or the browser APIs the checks need, were unavailable." - }, - "stun_not_checked": { - "type": "boolean", - "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." - }, - "check_incomplete": { + "ai_bot": { "type": "boolean", - "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." - }, - "device_spoofing": { - "type": "boolean" - }, - "latency_test": { - "type": "boolean" - }, - "banned_ip": { - "type": "boolean" + "description": "An already accepted crawler has purpose training or user_fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. A User-Agent claim alone does not grant an accepted verdict." }, - "ai_bot": { + "browser_automation": { "type": "boolean", - "description": "An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk." + "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." }, "ai_browser": { "type": "boolean", - "description": "A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative." + "description": "A browser has separately verified infrastructure or service attribution. A User-Agent claim or provider-only IP proof is insufficient." }, - "os_mismatch2": { + "tor": { "type": "boolean", - "deprecated": true, - "description": "Legacy 2026-10-06 only; current payloads combine this check into os_mismatch." + "description": "The request came through the Tor network." } - } + }, + "additionalProperties": false }, "DeviceId": { "type": "string", @@ -326,7 +312,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-09 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-12 uses signals for score contributions, detection_flags for 21 source signals, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -411,7 +397,8 @@ } }, "detection_flags": { - "$ref": "#/$defs/DetectionFlags" + "$ref": "#/$defs/LegacyDetectionFlags", + "description": "Contract 2026-10-12 requires DetectionFlags (exactly 21 source signals); this fallback supports retained older versions." }, "observed_at": { "$ref": "#/$defs/Rfc3339Timestamp", @@ -549,7 +536,8 @@ "enum": [ "2026-10-07", "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, @@ -576,19 +564,6 @@ "scoring_version": {}, "risk_events": {}, "fingerprint": {}, - "detection_flags": { - "type": "object", - "not": { - "type": "object", - "required": [ - "os_mismatch2" - ], - "properties": { - "os_mismatch2": {} - }, - "additionalProperties": true - } - }, "hre": { "type": "object", "properties": { @@ -622,7 +597,8 @@ }, "additionalProperties": true } - } + }, + "additionalProperties": true } }, "additionalProperties": true, @@ -662,13 +638,15 @@ "schema_version": { "enum": [ "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -693,7 +671,8 @@ "const": "browser_vpn_proxy" } } - } + }, + "additionalProperties": true } }, "detection_flags": { @@ -707,7 +686,8 @@ ], "properties": { "browser_vpn_proxy": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -716,7 +696,8 @@ ], "properties": { "datacenter_ip": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -725,7 +706,8 @@ ], "properties": { "abuser": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -734,14 +716,18 @@ ], "properties": { "suspicious_paid_click": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } }, { @@ -781,12 +767,16 @@ "type": "object", "properties": { "schema_version": { - "const": "2026-10-09" + "enum": [ + "2026-10-09", + "2026-10-12" + ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -802,7 +792,8 @@ ], "properties": { "result_version": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -811,12 +802,88 @@ ], "properties": { "scoring_version": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "enum": [ + "2026-10-07", + "2026-10-08", + "2026-10-09" + ] + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "type": "object", + "not": { + "type": "object", + "required": [ + "os_mismatch2" + ], + "properties": { + "os_mismatch2": {} + }, + "additionalProperties": true + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "const": "2026-10-12" + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "$ref": "#/$defs/DetectionFlags" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true } } ] @@ -846,6 +913,131 @@ "" ] }, + "LegacyDetectionFlags": { + "type": "object", + "description": "Detection flags in retained webhook and History payloads before 2026-10-12. Missing extension flags are not evaluated negative results.", + "required": [ + "vpn", + "privacy_relay", + "tor", + "proxy", + "os_mismatch", + "os_not_detected", + "timezone_mismatch", + "anti_detect_browser", + "browser_automation", + "ip_mismatch", + "incognito", + "search_bot", + "javascript_disabled", + "stun_not_checked", + "check_incomplete" + ], + "properties": { + "vpn": { + "type": "boolean", + "description": "A VPN or Browser VPN/Proxy was detected." + }, + "privacy_relay": { + "type": "boolean", + "description": "A privacy relay such as iCloud Private Relay was detected." + }, + "browser_vpn_proxy": { + "type": "boolean", + "deprecated": true, + "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." + }, + "tor": { + "type": "boolean", + "description": "The request came through the Tor network." + }, + "proxy": { + "type": "boolean", + "description": "A proxy was detected." + }, + "datacenter_ip": { + "type": "boolean", + "deprecated": true, + "description": "The public IP belongs to a datacenter or hosting range." + }, + "abuser": { + "type": "boolean", + "deprecated": true, + "description": "The public IP has a record of abuse in IP intelligence." + }, + "os_mismatch": { + "type": "boolean", + "description": "Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks." + }, + "os_not_detected": { + "type": "boolean", + "description": "The operating system could not be determined from the User-Agent or the network." + }, + "timezone_mismatch": { + "type": "boolean", + "description": "The browser timezone differs from the timezone of the IP location." + }, + "anti_detect_browser": { + "type": "boolean", + "description": "An anti-detect browser was detected." + }, + "browser_automation": { + "type": "boolean", + "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." + }, + "ip_mismatch": { + "type": "boolean", + "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + }, + "incognito": { + "type": "boolean", + "description": "The browser runs in a private window." + }, + "search_bot": { + "type": "boolean", + "description": "A search-engine crawler. Its Risk Score is always 0." + }, + "suspicious_paid_click": { + "type": "boolean", + "deprecated": true, + "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." + }, + "javascript_disabled": { + "type": "boolean", + "description": "JavaScript, or the browser APIs the checks need, were unavailable." + }, + "stun_not_checked": { + "type": "boolean", + "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." + }, + "check_incomplete": { + "type": "boolean", + "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." + }, + "device_spoofing": { + "type": "boolean" + }, + "latency_test": { + "type": "boolean" + }, + "banned_ip": { + "type": "boolean" + }, + "ai_bot": { + "type": "boolean", + "description": "An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk." + }, + "ai_browser": { + "type": "boolean", + "description": "A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative." + }, + "os_mismatch2": { + "type": "boolean", + "deprecated": true, + "description": "Legacy 2026-10-06 only; current payloads combine this check into os_mismatch." + } + } + }, "MultiaccountChangedData": { "type": "object", "additionalProperties": false, @@ -1207,8 +1399,9 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-12 (21 independent source signals); multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-12", "2026-10-09", "2026-10-08", "2026-10-07", diff --git a/dist/schemas/webhook-ping-event.schema.json b/dist/schemas/webhook-ping-event.schema.json index aec1ece..8d9fb9a 100644 --- a/dist/schemas/webhook-ping-event.schema.json +++ b/dist/schemas/webhook-ping-event.schema.json @@ -41,8 +41,9 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-12 (21 independent source signals); multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-12", "2026-10-09", "2026-10-08", "2026-10-07", diff --git a/dist/shieldlabs-api.json b/dist/shieldlabs-api.json index 82c94b9..5e5bb27 100644 --- a/dist/shieldlabs-api.json +++ b/dist/shieldlabs-api.json @@ -519,154 +519,19 @@ "$ref": "#/components/examples/IdentificationScoredTestDelivery" }, "current": { - "summary": "Current contract with fp21 and HRE", + "summary": "21 source detection signals", + "description": "Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit.", "value": { - "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-06", + "schema_version": "2026-10-12", "created_at": "2026-10-06T00:00:02Z", "data": { - "result_version": "2026-10-06T00:00:01Z", - "scoring_version": "core:fixture", - "risk_events": [ - { - "code": "vpn", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "proxy", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "privacy_relay", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "check_incomplete", - "detected": false, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "network_check_incomplete", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_not_detected", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "timezone_mismatch", - "detected": false, - "weight": 10, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "javascript_disabled", - "detected": false, - "weight": 90, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_mismatch", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_mismatch2", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "ip_mismatch", - "detected": false, - "weight": 45, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "latency_test", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "anti_detect_browser", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "device_spoofing", - "detected": false, - "weight": 90, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "banned_ip", - "detected": false, - "weight": 999, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "incognito", - "detected": true, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "search_bot", - "detected": false, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "browser_automation", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "tor", - "detected": false, - "weight": 99, - "contribution": 0, - "status": "evaluated" - } - ], "hre": { "rules_version": "fp21-fixture", "account_sharing": { + "cluster_id": "11111111-1111-4111-8111-111111111111", "status": "evaluated", "level": "high", "reason": "", @@ -674,11 +539,13 @@ "min_devices": 4 }, "account_takeover": { + "cluster_id": "22222222-2222-4222-8222-222222222222", "status": "evaluated", "level": "medium", "reason": "fixture" }, "impossible_travel": { + "cluster_id": null, "status": "evaluated", "level": null, "reason": "below_distance" @@ -723,11 +590,8 @@ "banned_ip": false, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -736,7 +600,188 @@ "ip_mismatch": false, "incognito": true, "search_bot": false, - "suspicious_paid_click": false, + "ai_bot": false, + "ai_browser": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } + } + }, + "aiBot": { + "summary": "AI crawler flags", + "description": "Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit.", + "value": { + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-12", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "ai_bot_owner": "OpenAI", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": true, + "ai_browser": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } + } + }, + "aiBrowser": { + "summary": "Verified browser infrastructure flags", + "description": "Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit.", + "value": { + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-12", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "ai_browser_owner": "Browserbase", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": false, + "ai_browser": true, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false @@ -2310,11 +2355,123 @@ } } }, + "DetectionFlags": { + "type": "object", + "description": "The 21 source detection signals in scored webhook contract 2026-10-12. Groups and owner attribution are separate from this set. The two OS mismatch detectors remain independent.", + "required": [ + "vpn", + "proxy", + "privacy_relay", + "check_incomplete", + "stun_not_checked", + "os_not_detected", + "timezone_mismatch", + "javascript_disabled", + "os_mismatch", + "os_mismatch2", + "ip_mismatch", + "latency_test", + "anti_detect_browser", + "device_spoofing", + "banned_ip", + "incognito", + "search_bot", + "ai_bot", + "browser_automation", + "ai_browser", + "tor" + ], + "properties": { + "vpn": { + "type": "boolean", + "description": "A VPN or Browser VPN/Proxy was detected." + }, + "proxy": { + "type": "boolean", + "description": "A proxy was detected." + }, + "privacy_relay": { + "type": "boolean", + "description": "A privacy relay such as iCloud Private Relay was detected." + }, + "check_incomplete": { + "type": "boolean", + "description": "Part of the browser checks timed out, so the verdict rests on partial data. Informational." + }, + "stun_not_checked": { + "type": "boolean", + "description": "The browser network (STUN) check did not complete. Cleared again when a late network result arrives." + }, + "os_not_detected": { + "type": "boolean", + "description": "The operating system could not be determined from the User-Agent or the network." + }, + "timezone_mismatch": { + "type": "boolean", + "description": "The browser timezone differs from the timezone of the IP location." + }, + "javascript_disabled": { + "type": "boolean", + "description": "JavaScript, or the browser APIs the checks need, were unavailable." + }, + "os_mismatch": { + "type": "boolean", + "description": "The first operating system consistency detector found a mismatch. Independent of os_mismatch2." + }, + "os_mismatch2": { + "type": "boolean", + "description": "The second operating system consistency detector (TCP behaviour) found a mismatch. Independent of os_mismatch." + }, + "ip_mismatch": { + "type": "boolean", + "description": "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." + }, + "latency_test": { + "type": "boolean" + }, + "anti_detect_browser": { + "type": "boolean", + "description": "An anti-detect browser was detected." + }, + "device_spoofing": { + "type": "boolean" + }, + "banned_ip": { + "type": "boolean" + }, + "incognito": { + "type": "boolean", + "description": "The browser runs in a private window." + }, + "search_bot": { + "type": "boolean", + "description": "A search-engine crawler. Its Risk Score is always 0." + }, + "ai_bot": { + "type": "boolean", + "description": "An already accepted crawler has purpose training or user_fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. A User-Agent claim alone does not grant an accepted verdict." + }, + "browser_automation": { + "type": "boolean", + "description": "Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60." + }, + "ai_browser": { + "type": "boolean", + "description": "A browser has separately verified infrastructure or service attribution. A User-Agent claim or provider-only IP proof is insufficient." + }, + "tor": { + "type": "boolean", + "description": "The request came through the Tor network." + } + }, + "additionalProperties": false + }, "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-12 (21 independent source signals); multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-12", "2026-10-09", "2026-10-08", "2026-10-07", @@ -2734,9 +2891,9 @@ } } }, - "DetectionFlags": { + "LegacyDetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Detection flags in retained webhook and History payloads before 2026-10-12. Missing extension flags are not evaluated negative results.", "required": [ "vpn", "privacy_relay", @@ -2966,7 +3123,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-09 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-12 uses signals for score contributions, detection_flags for 21 source signals, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -3051,7 +3208,8 @@ } }, "detection_flags": { - "$ref": "#/components/schemas/DetectionFlags" + "$ref": "#/components/schemas/LegacyDetectionFlags", + "description": "Contract 2026-10-12 requires DetectionFlags (exactly 21 source signals); this fallback supports retained older versions." }, "observed_at": { "$ref": "#/components/schemas/Rfc3339Timestamp", @@ -3189,7 +3347,8 @@ "enum": [ "2026-10-07", "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, @@ -3216,19 +3375,6 @@ "scoring_version": {}, "risk_events": {}, "fingerprint": {}, - "detection_flags": { - "type": "object", - "not": { - "type": "object", - "required": [ - "os_mismatch2" - ], - "properties": { - "os_mismatch2": {} - }, - "additionalProperties": true - } - }, "hre": { "type": "object", "properties": { @@ -3262,7 +3408,8 @@ }, "additionalProperties": true } - } + }, + "additionalProperties": true } }, "additionalProperties": true, @@ -3302,13 +3449,15 @@ "schema_version": { "enum": [ "2026-10-08", - "2026-10-09" + "2026-10-09", + "2026-10-12" ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -3333,7 +3482,8 @@ "const": "browser_vpn_proxy" } } - } + }, + "additionalProperties": true } }, "detection_flags": { @@ -3347,7 +3497,8 @@ ], "properties": { "browser_vpn_proxy": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -3356,7 +3507,8 @@ ], "properties": { "datacenter_ip": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -3365,7 +3517,8 @@ ], "properties": { "abuser": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -3374,14 +3527,18 @@ ], "properties": { "suspicious_paid_click": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true } }, { @@ -3421,12 +3578,16 @@ "type": "object", "properties": { "schema_version": { - "const": "2026-10-09" + "enum": [ + "2026-10-09", + "2026-10-12" + ] } }, "required": [ "schema_version" - ] + ], + "additionalProperties": true }, "then": { "type": "object", @@ -3442,7 +3603,8 @@ ], "properties": { "result_version": {} - } + }, + "additionalProperties": true }, { "type": "object", @@ -3451,12 +3613,88 @@ ], "properties": { "scoring_version": {} - } + }, + "additionalProperties": true } ] - } + }, + "additionalProperties": true } - } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "enum": [ + "2026-10-07", + "2026-10-08", + "2026-10-09" + ] + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "type": "object", + "not": { + "type": "object", + "required": [ + "os_mismatch2" + ], + "properties": { + "os_mismatch2": {} + }, + "additionalProperties": true + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + { + "if": { + "type": "object", + "required": [ + "schema_version" + ], + "properties": { + "schema_version": { + "const": "2026-10-12" + } + }, + "additionalProperties": true + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "detection_flags": { + "$ref": "#/components/schemas/DetectionFlags" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true } } ] diff --git a/dist/shieldlabs-api.yaml b/dist/shieldlabs-api.yaml index 29d5aee..f86c86d 100644 --- a/dist/shieldlabs-api.yaml +++ b/dist/shieldlabs-api.yaml @@ -544,125 +544,31 @@ webhooks: testDelivery: $ref: '#/components/examples/IdentificationScoredTestDelivery' current: - summary: Current contract with fp21 and HRE + summary: 21 source detection signals + description: Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit. value: - event_id: ce256c0f66536957adc5f719250f5b10ce331bd1 + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 site_id: 7 event_type: identification.scored - schema_version: '2026-10-06' + schema_version: '2026-10-12' created_at: '2026-10-06T00:00:02Z' data: - result_version: '2026-10-06T00:00:01Z' - scoring_version: core:fixture - risk_events: - - code: vpn - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: proxy - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: privacy_relay - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: check_incomplete - detected: false - weight: 0 - contribution: 0 - status: evaluated - - code: network_check_incomplete - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: os_not_detected - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: timezone_mismatch - detected: false - weight: 10 - contribution: 0 - status: evaluated - - code: javascript_disabled - detected: false - weight: 90 - contribution: 0 - status: evaluated - - code: os_mismatch - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: os_mismatch2 - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: ip_mismatch - detected: false - weight: 45 - contribution: 0 - status: evaluated - - code: latency_test - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: anti_detect_browser - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: device_spoofing - detected: false - weight: 90 - contribution: 0 - status: evaluated - - code: banned_ip - detected: false - weight: 999 - contribution: 0 - status: evaluated - - code: incognito - detected: true - weight: 0 - contribution: 0 - status: evaluated - - code: search_bot - detected: false - weight: 0 - contribution: 0 - status: evaluated - - code: browser_automation - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: tor - detected: false - weight: 99 - contribution: 0 - status: evaluated hre: rules_version: fp21-fixture account_sharing: + cluster_id: 11111111-1111-4111-8111-111111111111 status: evaluated level: high reason: '' devices: 4 min_devices: 4 account_takeover: + cluster_id: 22222222-2222-4222-8222-222222222222 status: evaluated level: medium reason: fixture impossible_travel: + cluster_id: null status: evaluated level: null reason: below_distance @@ -702,11 +608,8 @@ webhooks: banned_ip: false vpn: false privacy_relay: false - browser_vpn_proxy: false tor: false proxy: false - datacenter_ip: false - abuser: false os_mismatch: false os_not_detected: false timezone_mismatch: false @@ -715,7 +618,166 @@ webhooks: ip_mismatch: false incognito: true search_bot: false - suspicious_paid_click: false + ai_bot: false + ai_browser: false + javascript_disabled: false + stun_not_checked: false + check_incomplete: false + observed_at: '2026-10-06T00:00:00Z' + aiBot: + summary: AI crawler flags + description: Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit. + value: + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 + site_id: 7 + event_type: identification.scored + schema_version: '2026-10-12' + created_at: '2026-10-06T00:00:02Z' + data: + ai_bot_owner: OpenAI + hre: + account_sharing: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + account_takeover: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + impossible_travel: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + request_id: 13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11 + visitor_id: 161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d + device_id: 5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f + session_id: 7a1b2c3d-4e5f-4789-abcd-ef0123456789 + cookie_id: 3f2e1d0c-9b8a-4654-b210-fedcba987654 + user_hid: anonymous + domain: example.com + public_ip: + ip: 203.0.113.1 + country: '' + local_ip: + ip: '' + country: '' + connection_type: direct + os: Windows + browser: Chrome + device_type: desktop + traffic_source: + channel: '' + referrer_domain: '' + landing_url: '' + click_id_type: '' + utm_source: '' + utm_medium: '' + utm_campaign: '' + utm_content: '' + utm_term: '' + risk_score: 0 + signals: [] + detection_flags: + os_mismatch2: false + device_spoofing: false + latency_test: false + banned_ip: false + vpn: false + privacy_relay: false + tor: false + proxy: false + os_mismatch: false + os_not_detected: false + timezone_mismatch: false + anti_detect_browser: false + browser_automation: false + ip_mismatch: false + incognito: false + search_bot: false + ai_bot: true + ai_browser: false + javascript_disabled: false + stun_not_checked: false + check_incomplete: false + observed_at: '2026-10-06T00:00:00Z' + aiBrowser: + summary: Verified browser infrastructure flags + description: Emitted by Core synthetic fixture export. This example describes serialization, not a live production visit. + value: + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 + site_id: 7 + event_type: identification.scored + schema_version: '2026-10-12' + created_at: '2026-10-06T00:00:02Z' + data: + ai_browser_owner: Browserbase + hre: + account_sharing: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + account_takeover: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + impossible_travel: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + request_id: 13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11 + visitor_id: 161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d + device_id: 5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f + session_id: 7a1b2c3d-4e5f-4789-abcd-ef0123456789 + cookie_id: 3f2e1d0c-9b8a-4654-b210-fedcba987654 + user_hid: anonymous + domain: example.com + public_ip: + ip: 203.0.113.1 + country: '' + local_ip: + ip: '' + country: '' + connection_type: direct + os: Windows + browser: Chrome + device_type: desktop + traffic_source: + channel: '' + referrer_domain: '' + landing_url: '' + click_id_type: '' + utm_source: '' + utm_medium: '' + utm_campaign: '' + utm_content: '' + utm_term: '' + risk_score: 0 + signals: [] + detection_flags: + os_mismatch2: false + device_spoofing: false + latency_test: false + banned_ip: false + vpn: false + privacy_relay: false + tor: false + proxy: false + os_mismatch: false + os_not_detected: false + timezone_mismatch: false + anti_detect_browser: false + browser_automation: false + ip_mismatch: false + incognito: false + search_bot: false + ai_bot: false + ai_browser: true javascript_disabled: false stun_not_checked: false check_incomplete: false @@ -1988,11 +2050,99 @@ components: $ref: '#/components/schemas/Rfc3339Timestamp' data: $ref: '#/components/schemas/MultiaccountChangedData' + DetectionFlags: + type: object + description: The 21 source detection signals in scored webhook contract 2026-10-12. Groups and owner attribution are separate from this set. The two OS mismatch detectors remain independent. + required: + - vpn + - proxy + - privacy_relay + - check_incomplete + - stun_not_checked + - os_not_detected + - timezone_mismatch + - javascript_disabled + - os_mismatch + - os_mismatch2 + - ip_mismatch + - latency_test + - anti_detect_browser + - device_spoofing + - banned_ip + - incognito + - search_bot + - ai_bot + - browser_automation + - ai_browser + - tor + properties: + vpn: + type: boolean + description: A VPN or Browser VPN/Proxy was detected. + proxy: + type: boolean + description: A proxy was detected. + privacy_relay: + type: boolean + description: A privacy relay such as iCloud Private Relay was detected. + check_incomplete: + type: boolean + description: Part of the browser checks timed out, so the verdict rests on partial data. Informational. + stun_not_checked: + type: boolean + description: The browser network (STUN) check did not complete. Cleared again when a late network result arrives. + os_not_detected: + type: boolean + description: The operating system could not be determined from the User-Agent or the network. + timezone_mismatch: + type: boolean + description: The browser timezone differs from the timezone of the IP location. + javascript_disabled: + type: boolean + description: JavaScript, or the browser APIs the checks need, were unavailable. + os_mismatch: + type: boolean + description: The first operating system consistency detector found a mismatch. Independent of os_mismatch2. + os_mismatch2: + type: boolean + description: The second operating system consistency detector (TCP behaviour) found a mismatch. Independent of os_mismatch. + ip_mismatch: + type: boolean + description: 'The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score.' + latency_test: + type: boolean + anti_detect_browser: + type: boolean + description: An anti-detect browser was detected. + device_spoofing: + type: boolean + banned_ip: + type: boolean + incognito: + type: boolean + description: The browser runs in a private window. + search_bot: + type: boolean + description: A search-engine crawler. Its Risk Score is always 0. + ai_bot: + type: boolean + description: An already accepted crawler has purpose training or user_fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. A User-Agent claim alone does not grant an accepted verdict. + browser_automation: + type: boolean + description: Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60. + ai_browser: + type: boolean + description: A browser has separately verified infrastructure or service attribution. A User-Agent claim or provider-only IP proof is insufficient. + tor: + type: boolean + description: The request came through the Tor network. + additionalProperties: false SchemaVersion: type: string minLength: 1 - description: Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. + description: Webhook contract version. Scored release 2026-10-12 (21 independent source signals); multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. examples: + - '2026-10-12' - '2026-10-09' - '2026-10-08' - '2026-10-07' @@ -2323,14 +2473,9 @@ components: examples: - 10 - -30 - DetectionFlags: + LegacyDetectionFlags: type: object - description: |- - Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on - the Risk Score; signal names are for display and logging. - - When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and - `javascript_disabled` are always `false`. + description: Detection flags in retained webhook and History payloads before 2026-10-12. Missing extension flags are not evaluated negative results. required: - vpn - privacy_relay @@ -2503,7 +2648,7 @@ components: description: Three completed on-demand handler results. Technical errors block emission; no_history/skipped are explicit not_evaluated results. Anonymous checks are not_applicable. Multiaccount is separate. IdentificationScoredData: type: object - description: Final identification. risk_score is this scan only. Version 2026-10-09 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. + description: Final identification. risk_score is this scan only. Version 2026-10-12 uses signals for score contributions, detection_flags for 21 source signals, and hre for account results. Older bodies remain accepted. required: - request_id - visitor_id @@ -2568,7 +2713,8 @@ components: items: $ref: '#/components/schemas/Signal' detection_flags: - $ref: '#/components/schemas/DetectionFlags' + $ref: '#/components/schemas/LegacyDetectionFlags' + description: Contract 2026-10-12 requires DetectionFlags (exactly 21 source signals); this fallback supports retained older versions. observed_at: $ref: '#/components/schemas/Rfc3339Timestamp' description: Original snapshot scan clock, distinct from envelope created_at. RFC 3339 in UTC with up to 9 fractional digits. @@ -2670,6 +2816,7 @@ components: - '2026-10-07' - '2026-10-08' - '2026-10-09' + - '2026-10-12' additionalProperties: true then: type: object @@ -2689,15 +2836,6 @@ components: scoring_version: {} risk_events: {} fingerprint: {} - detection_flags: - type: object - not: - type: object - required: - - os_mismatch2 - properties: - os_mismatch2: {} - additionalProperties: true hre: type: object properties: @@ -2722,6 +2860,7 @@ components: properties: cluster_id: {} additionalProperties: true + additionalProperties: true additionalProperties: true not: anyOf: @@ -2745,8 +2884,10 @@ components: enum: - '2026-10-08' - '2026-10-09' + - '2026-10-12' required: - schema_version + additionalProperties: true then: type: object properties: @@ -2766,6 +2907,7 @@ components: type: string not: const: browser_vpn_proxy + additionalProperties: true detection_flags: type: object not: @@ -2775,21 +2917,28 @@ components: - browser_vpn_proxy properties: browser_vpn_proxy: {} + additionalProperties: true - type: object required: - datacenter_ip properties: datacenter_ip: {} + additionalProperties: true - type: object required: - abuser properties: abuser: {} + additionalProperties: true - type: object required: - suspicious_paid_click properties: suspicious_paid_click: {} + additionalProperties: true + additionalProperties: true + additionalProperties: true + additionalProperties: true - if: type: object properties: @@ -2814,9 +2963,12 @@ components: type: object properties: schema_version: - const: '2026-10-09' + enum: + - '2026-10-09' + - '2026-10-12' required: - schema_version + additionalProperties: true then: type: object properties: @@ -2829,11 +2981,62 @@ components: - result_version properties: result_version: {} + additionalProperties: true - type: object required: - scoring_version properties: scoring_version: {} + additionalProperties: true + additionalProperties: true + additionalProperties: true + - if: + type: object + required: + - schema_version + properties: + schema_version: + enum: + - '2026-10-07' + - '2026-10-08' + - '2026-10-09' + additionalProperties: true + then: + type: object + properties: + data: + type: object + properties: + detection_flags: + type: object + not: + type: object + required: + - os_mismatch2 + properties: + os_mismatch2: {} + additionalProperties: true + additionalProperties: true + additionalProperties: true + additionalProperties: true + - if: + type: object + required: + - schema_version + properties: + schema_version: + const: '2026-10-12' + additionalProperties: true + then: + type: object + properties: + data: + type: object + properties: + detection_flags: + $ref: '#/components/schemas/DetectionFlags' + additionalProperties: true + additionalProperties: true WebhookPingEvent: type: object description: Body of a `webhook.ping` delivery, sent when you verify an endpoint. It has no `data`. The keys arrive sorted alphabetically and `created_at` has second precision. diff --git a/spec/components/schemas/DetectionFlags.yaml b/spec/components/schemas/DetectionFlags.yaml index 7d20e79..7c275e3 100644 --- a/spec/components/schemas/DetectionFlags.yaml +++ b/spec/components/schemas/DetectionFlags.yaml @@ -1,61 +1,47 @@ type: object -description: >- - Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on - these flags and on - - the Risk Score; signal names are for display and logging. - - - When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` - and - - `javascript_disabled` are always `false`. +description: The 21 source detection signals in scored webhook contract + 2026-10-12. Groups and owner attribution are separate from this set. The two + OS mismatch detectors remain independent. required: - vpn - - privacy_relay - - tor - proxy - - os_mismatch + - privacy_relay + - check_incomplete + - stun_not_checked - os_not_detected - timezone_mismatch - - anti_detect_browser - - browser_automation + - javascript_disabled + - os_mismatch + - os_mismatch2 - ip_mismatch + - latency_test + - anti_detect_browser + - device_spoofing + - banned_ip - incognito - search_bot - - javascript_disabled - - stun_not_checked - - check_incomplete + - ai_bot + - browser_automation + - ai_browser + - tor properties: vpn: type: boolean description: A VPN or Browser VPN/Proxy was detected. - privacy_relay: - type: boolean - description: A privacy relay such as iCloud Private Relay was detected. - browser_vpn_proxy: - type: boolean - deprecated: true - description: A VPN or proxy built into the browser or one of its extensions. - `true` exactly when `connection_type` is `browser_vpn_proxy`. - tor: - type: boolean - description: The request came through the Tor network. proxy: type: boolean description: A proxy was detected. - datacenter_ip: + privacy_relay: type: boolean - deprecated: true - description: The public IP belongs to a datacenter or hosting range. - abuser: + description: A privacy relay such as iCloud Private Relay was detected. + check_incomplete: type: boolean - deprecated: true - description: The public IP has a record of abuse in IP intelligence. - os_mismatch: + description: Part of the browser checks timed out, so the verdict rests on + partial data. Informational. + stun_not_checked: type: boolean - description: Either internal OS consistency check detected a mismatch. The public - flag combines browser/network and TCP behaviour checks. + description: The browser network (STUN) check did not complete. Cleared again + when a late network result arrives. os_not_detected: type: boolean description: The operating system could not be determined from the User-Agent or @@ -63,61 +49,51 @@ properties: timezone_mismatch: type: boolean description: The browser timezone differs from the timezone of the IP location. - anti_detect_browser: + javascript_disabled: type: boolean - description: An anti-detect browser was detected. - browser_automation: + description: JavaScript, or the browser APIs the checks need, were unavailable. + os_mismatch: type: boolean - description: Browser automation was detected, for example a WebDriver-controlled - browser. Catalogue weight is 90 for task 204 scoring releases; historical - result versions may retain the earlier weight 60. + description: The first operating system consistency detector found a mismatch. + Independent of os_mismatch2. + os_mismatch2: + type: boolean + description: The second operating system consistency detector (TCP behaviour) + found a mismatch. Independent of os_mismatch. ip_mismatch: type: boolean description: "The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score." - incognito: - type: boolean - description: The browser runs in a private window. - search_bot: - type: boolean - description: A search-engine crawler. Its Risk Score is always 0. - suspicious_paid_click: - type: boolean - deprecated: true - description: The visit came from a paid ad click (Google Ads, Meta, TikTok, - Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more - (the 999 marker included). - javascript_disabled: - type: boolean - description: JavaScript, or the browser APIs the checks need, were unavailable. - stun_not_checked: + latency_test: type: boolean - description: The browser network (STUN) check did not complete. Cleared again - when a late network result arrives. - check_incomplete: + anti_detect_browser: type: boolean - description: Part of the browser checks timed out, so the verdict rests on - partial data. Informational. + description: An anti-detect browser was detected. device_spoofing: type: boolean - latency_test: - type: boolean banned_ip: type: boolean - + incognito: + type: boolean + description: The browser runs in a private window. + search_bot: + type: boolean + description: A search-engine crawler. Its Risk Score is always 0. ai_bot: type: boolean - description: An accepted bot is classified as AI training or user-requested fetch. - ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. - Weight 0. The flag is optional on older payloads; its absence is not an - evaluated negative result. Provider claims alone do not grant zero risk. + description: An already accepted crawler has purpose training or user_fetch. + ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI + browsers. A User-Agent claim alone does not grant an accepted verdict. + browser_automation: + type: boolean + description: Browser automation was detected, for example a WebDriver-controlled + browser. Catalogue weight is 90 for task 204 scoring releases; historical + result versions may retain the earlier weight 60. ai_browser: type: boolean - description: A browser is identified by separate verified browser infrastructure. - A generic browser User-Agent or a verified provider alone is insufficient. - Weight 0. Optional on older payloads; absence is not an evaluated negative. - - os_mismatch2: + description: A browser has separately verified infrastructure or service + attribution. A User-Agent claim or provider-only IP proof is insufficient. + tor: type: boolean - deprecated: true - description: Legacy 2026-10-06 only; current payloads combine this check into os_mismatch. + description: The request came through the Tor network. +additionalProperties: false diff --git a/spec/components/schemas/IdentificationScoredData.yaml b/spec/components/schemas/IdentificationScoredData.yaml index 0a41a1c..99246b9 100644 --- a/spec/components/schemas/IdentificationScoredData.yaml +++ b/spec/components/schemas/IdentificationScoredData.yaml @@ -1,6 +1,6 @@ type: object -description: Final identification. risk_score is this scan only. Version 2026-10-09 - uses signals for score contributions, detection_flags for final decisions, +description: Final identification. risk_score is this scan only. Version 2026-10-12 + uses signals for score contributions, detection_flags for 21 source signals, and hre for account results. Older bodies remain accepted. required: - request_id @@ -72,7 +72,8 @@ properties: items: $ref: ./Signal.yaml detection_flags: - $ref: ./DetectionFlags.yaml + $ref: ./LegacyDetectionFlags.yaml + description: Contract 2026-10-12 requires DetectionFlags (exactly 21 source signals); this fallback supports retained older versions. observed_at: $ref: ./Rfc3339Timestamp.yaml description: Original snapshot scan clock, distinct from envelope created_at. diff --git a/spec/components/schemas/IdentificationScoredEvent.yaml b/spec/components/schemas/IdentificationScoredEvent.yaml index b6c90cc..17363f1 100644 --- a/spec/components/schemas/IdentificationScoredEvent.yaml +++ b/spec/components/schemas/IdentificationScoredEvent.yaml @@ -61,7 +61,11 @@ allOf: - schema_version properties: schema_version: - enum: [2026-10-07, 2026-10-08, 2026-10-09] + enum: + - 2026-10-07 + - 2026-10-08 + - 2026-10-09 + - 2026-10-12 additionalProperties: true then: type: object @@ -78,15 +82,6 @@ allOf: scoring_version: {} risk_events: {} fingerprint: {} - detection_flags: - type: object - not: - type: object - required: - - os_mismatch2 - properties: - os_mismatch2: {} - additionalProperties: true hre: type: object properties: @@ -111,6 +106,7 @@ allOf: properties: cluster_id: {} additionalProperties: true + additionalProperties: true additionalProperties: true not: anyOf: @@ -127,13 +123,17 @@ allOf: fingerprint: {} additionalProperties: true additionalProperties: true - - if: type: object properties: schema_version: - enum: [2026-10-08, 2026-10-09] - required: [schema_version] + enum: + - 2026-10-08 + - 2026-10-09 + - 2026-10-12 + required: + - schema_version + additionalProperties: true then: type: object properties: @@ -153,39 +153,55 @@ allOf: type: string not: const: browser_vpn_proxy + additionalProperties: true detection_flags: type: object not: anyOf: - type: object - required: [browser_vpn_proxy] + required: + - browser_vpn_proxy properties: browser_vpn_proxy: {} + additionalProperties: true - type: object - required: [datacenter_ip] + required: + - datacenter_ip properties: datacenter_ip: {} + additionalProperties: true - type: object - required: [abuser] + required: + - abuser properties: abuser: {} + additionalProperties: true - type: object - required: [suspicious_paid_click] + required: + - suspicious_paid_click properties: suspicious_paid_click: {} - + additionalProperties: true + additionalProperties: true + additionalProperties: true + additionalProperties: true - if: type: object properties: schema_version: - enum: [2026-10-07, 2026-10-08] - required: [schema_version] + enum: + - 2026-10-07 + - 2026-10-08 + required: + - schema_version then: type: object properties: data: type: object - required: [result_version, scoring_version] + required: + - result_version + - scoring_version properties: result_version: {} scoring_version: {} @@ -193,8 +209,12 @@ allOf: type: object properties: schema_version: - const: 2026-10-09 - required: [schema_version] + enum: + - 2026-10-09 + - 2026-10-12 + required: + - schema_version + additionalProperties: true then: type: object properties: @@ -203,10 +223,63 @@ allOf: not: anyOf: - type: object - required: [result_version] + required: + - result_version properties: result_version: {} + additionalProperties: true - type: object - required: [scoring_version] + required: + - scoring_version properties: scoring_version: {} + additionalProperties: true + additionalProperties: true + additionalProperties: true + - if: + type: object + required: + - schema_version + properties: + schema_version: + enum: + - 2026-10-07 + - 2026-10-08 + - 2026-10-09 + additionalProperties: true + then: + type: object + properties: + data: + type: object + properties: + detection_flags: + type: object + not: + type: object + required: + - os_mismatch2 + properties: + os_mismatch2: {} + additionalProperties: true + additionalProperties: true + additionalProperties: true + additionalProperties: true + - if: + type: object + required: + - schema_version + properties: + schema_version: + const: 2026-10-12 + additionalProperties: true + then: + type: object + properties: + data: + type: object + properties: + detection_flags: + $ref: ./DetectionFlags.yaml + additionalProperties: true + additionalProperties: true diff --git a/spec/components/schemas/LegacyDetectionFlags.yaml b/spec/components/schemas/LegacyDetectionFlags.yaml new file mode 100644 index 0000000..ab42029 --- /dev/null +++ b/spec/components/schemas/LegacyDetectionFlags.yaml @@ -0,0 +1,115 @@ +type: object +description: Detection flags in retained webhook and History payloads before + 2026-10-12. Missing extension flags are not evaluated negative results. +required: + - vpn + - privacy_relay + - tor + - proxy + - os_mismatch + - os_not_detected + - timezone_mismatch + - anti_detect_browser + - browser_automation + - ip_mismatch + - incognito + - search_bot + - javascript_disabled + - stun_not_checked + - check_incomplete +properties: + vpn: + type: boolean + description: A VPN or Browser VPN/Proxy was detected. + privacy_relay: + type: boolean + description: A privacy relay such as iCloud Private Relay was detected. + browser_vpn_proxy: + type: boolean + deprecated: true + description: A VPN or proxy built into the browser or one of its extensions. + `true` exactly when `connection_type` is `browser_vpn_proxy`. + tor: + type: boolean + description: The request came through the Tor network. + proxy: + type: boolean + description: A proxy was detected. + datacenter_ip: + type: boolean + deprecated: true + description: The public IP belongs to a datacenter or hosting range. + abuser: + type: boolean + deprecated: true + description: The public IP has a record of abuse in IP intelligence. + os_mismatch: + type: boolean + description: Either internal OS consistency check detected a mismatch. The + public flag combines browser/network and TCP behaviour checks. + os_not_detected: + type: boolean + description: The operating system could not be determined from the User-Agent or + the network. + timezone_mismatch: + type: boolean + description: The browser timezone differs from the timezone of the IP location. + anti_detect_browser: + type: boolean + description: An anti-detect browser was detected. + browser_automation: + type: boolean + description: Browser automation was detected, for example a WebDriver-controlled + browser. Catalogue weight is 90 for task 204 scoring releases; historical + result versions may retain the earlier weight 60. + ip_mismatch: + type: boolean + description: "The public IP differs from the local IP found by the browser + network check. Informational: it does not add to the score." + incognito: + type: boolean + description: The browser runs in a private window. + search_bot: + type: boolean + description: A search-engine crawler. Its Risk Score is always 0. + suspicious_paid_click: + type: boolean + deprecated: true + description: The visit came from a paid ad click (Google Ads, Meta, TikTok, + Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more + (the 999 marker included). + javascript_disabled: + type: boolean + description: JavaScript, or the browser APIs the checks need, were unavailable. + stun_not_checked: + type: boolean + description: The browser network (STUN) check did not complete. Cleared again + when a late network result arrives. + check_incomplete: + type: boolean + description: Part of the browser checks timed out, so the verdict rests on + partial data. Informational. + device_spoofing: + type: boolean + latency_test: + type: boolean + banned_ip: + type: boolean + ai_bot: + type: boolean + description: An accepted bot is classified as AI training or user-requested + fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI + browsers. Weight 0. The flag is optional on older payloads; its absence is + not an evaluated negative result. Provider claims alone do not grant zero + risk. + ai_browser: + type: boolean + description: A browser is identified by separate verified browser + infrastructure. A generic browser User-Agent or a verified provider alone + is insufficient. Weight 0. Optional on older payloads; absence is not an + evaluated negative. + os_mismatch2: + type: boolean + deprecated: true + description: Legacy 2026-10-06 only; current payloads combine this check into + os_mismatch. diff --git a/spec/components/schemas/SchemaVersion.yaml b/spec/components/schemas/SchemaVersion.yaml index 0e337b6..072c82e 100644 --- a/spec/components/schemas/SchemaVersion.yaml +++ b/spec/components/schemas/SchemaVersion.yaml @@ -1,8 +1,10 @@ type: string minLength: 1 -description: Webhook contract version. Scored release 2026-10-09; multi-account group release 2026-10-07; parsers also - accept legacy 2026-06-01. +description: Webhook contract version. Scored release 2026-10-12 (21 independent + source signals); multi-account group release 2026-10-07; parsers also accept + legacy 2026-06-01. examples: + - 2026-10-12 - 2026-10-09 - 2026-10-08 - 2026-10-07 diff --git a/spec/models/Identification.yaml b/spec/models/Identification.yaml index 59a6b1f..b65e27b 100644 --- a/spec/models/Identification.yaml +++ b/spec/models/Identification.yaml @@ -114,7 +114,7 @@ properties: - Is proxy - null detection_flags: - $ref: ../components/schemas/DetectionFlags.yaml + $ref: ../components/schemas/LegacyDetectionFlags.yaml observed_at: type: string format: date-time diff --git a/spec/webhooks/identification-scored.yaml b/spec/webhooks/identification-scored.yaml index e3e1a39..1012fdb 100644 --- a/spec/webhooks/identification-scored.yaml +++ b/spec/webhooks/identification-scored.yaml @@ -41,125 +41,32 @@ post: testDelivery: $ref: ../components/examples/IdentificationScoredTestDelivery.yaml current: - summary: Current contract with fp21 and HRE + summary: 21 source detection signals + description: Emitted by Core synthetic fixture export. This example describes + serialization, not a live production visit. value: - event_id: ce256c0f66536957adc5f719250f5b10ce331bd1 + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 site_id: 7 event_type: identification.scored - schema_version: 2026-10-06 + schema_version: 2026-10-12 created_at: 2026-10-06T00:00:02Z data: - result_version: 2026-10-06T00:00:01Z - scoring_version: core:fixture - risk_events: - - code: vpn - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: proxy - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: privacy_relay - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: check_incomplete - detected: false - weight: 0 - contribution: 0 - status: evaluated - - code: network_check_incomplete - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: os_not_detected - detected: false - weight: 30 - contribution: 0 - status: evaluated - - code: timezone_mismatch - detected: false - weight: 10 - contribution: 0 - status: evaluated - - code: javascript_disabled - detected: false - weight: 90 - contribution: 0 - status: evaluated - - code: os_mismatch - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: os_mismatch2 - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: ip_mismatch - detected: false - weight: 45 - contribution: 0 - status: evaluated - - code: latency_test - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: anti_detect_browser - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: device_spoofing - detected: false - weight: 90 - contribution: 0 - status: evaluated - - code: banned_ip - detected: false - weight: 999 - contribution: 0 - status: evaluated - - code: incognito - detected: true - weight: 0 - contribution: 0 - status: evaluated - - code: search_bot - detected: false - weight: 0 - contribution: 0 - status: evaluated - - code: browser_automation - detected: false - weight: 60 - contribution: 0 - status: evaluated - - code: tor - detected: false - weight: 99 - contribution: 0 - status: evaluated hre: rules_version: fp21-fixture account_sharing: + cluster_id: 11111111-1111-4111-8111-111111111111 status: evaluated level: high reason: "" devices: 4 min_devices: 4 account_takeover: + cluster_id: 22222222-2222-4222-8222-222222222222 status: evaluated level: medium reason: fixture impossible_travel: + cluster_id: null status: evaluated level: null reason: below_distance @@ -199,11 +106,8 @@ post: banned_ip: false vpn: false privacy_relay: false - browser_vpn_proxy: false tor: false proxy: false - datacenter_ip: false - abuser: false os_mismatch: false os_not_detected: false timezone_mismatch: false @@ -212,7 +116,168 @@ post: ip_mismatch: false incognito: true search_bot: false - suspicious_paid_click: false + ai_bot: false + ai_browser: false + javascript_disabled: false + stun_not_checked: false + check_incomplete: false + observed_at: 2026-10-06T00:00:00Z + aiBot: + summary: AI crawler flags + description: Emitted by Core synthetic fixture export. This example describes + serialization, not a live production visit. + value: + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 + site_id: 7 + event_type: identification.scored + schema_version: 2026-10-12 + created_at: 2026-10-06T00:00:02Z + data: + ai_bot_owner: OpenAI + hre: + account_sharing: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + account_takeover: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + impossible_travel: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + request_id: 13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11 + visitor_id: 161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d + device_id: 5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f + session_id: 7a1b2c3d-4e5f-4789-abcd-ef0123456789 + cookie_id: 3f2e1d0c-9b8a-4654-b210-fedcba987654 + user_hid: anonymous + domain: example.com + public_ip: + ip: 203.0.113.1 + country: "" + local_ip: + ip: "" + country: "" + connection_type: direct + os: Windows + browser: Chrome + device_type: desktop + traffic_source: + channel: "" + referrer_domain: "" + landing_url: "" + click_id_type: "" + utm_source: "" + utm_medium: "" + utm_campaign: "" + utm_content: "" + utm_term: "" + risk_score: 0 + signals: [] + detection_flags: + os_mismatch2: false + device_spoofing: false + latency_test: false + banned_ip: false + vpn: false + privacy_relay: false + tor: false + proxy: false + os_mismatch: false + os_not_detected: false + timezone_mismatch: false + anti_detect_browser: false + browser_automation: false + ip_mismatch: false + incognito: false + search_bot: false + ai_bot: true + ai_browser: false + javascript_disabled: false + stun_not_checked: false + check_incomplete: false + observed_at: 2026-10-06T00:00:00Z + aiBrowser: + summary: Verified browser infrastructure flags + description: Emitted by Core synthetic fixture export. This example describes + serialization, not a live production visit. + value: + event_id: 52102900cd52d25890e83e18bb0bdec95dedea02 + site_id: 7 + event_type: identification.scored + schema_version: 2026-10-12 + created_at: 2026-10-06T00:00:02Z + data: + ai_browser_owner: Browserbase + hre: + account_sharing: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + account_takeover: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + impossible_travel: + cluster_id: null + status: not_applicable + level: null + reason: anonymous_user + request_id: 13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11 + visitor_id: 161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d + device_id: 5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f + session_id: 7a1b2c3d-4e5f-4789-abcd-ef0123456789 + cookie_id: 3f2e1d0c-9b8a-4654-b210-fedcba987654 + user_hid: anonymous + domain: example.com + public_ip: + ip: 203.0.113.1 + country: "" + local_ip: + ip: "" + country: "" + connection_type: direct + os: Windows + browser: Chrome + device_type: desktop + traffic_source: + channel: "" + referrer_domain: "" + landing_url: "" + click_id_type: "" + utm_source: "" + utm_medium: "" + utm_campaign: "" + utm_content: "" + utm_term: "" + risk_score: 0 + signals: [] + detection_flags: + os_mismatch2: false + device_spoofing: false + latency_test: false + banned_ip: false + vpn: false + privacy_relay: false + tor: false + proxy: false + os_mismatch: false + os_not_detected: false + timezone_mismatch: false + anti_detect_browser: false + browser_automation: false + ip_mismatch: false + incognito: false + search_bot: false + ai_bot: false + ai_browser: true javascript_disabled: false stun_not_checked: false check_incomplete: false diff --git a/test/204-risk-groups.test.mjs b/test/204-risk-groups.test.mjs index 8fb185c..164a795 100644 --- a/test/204-risk-groups.test.mjs +++ b/test/204-risk-groups.test.mjs @@ -5,7 +5,7 @@ import YAML from 'yaml'; import Ajv from 'ajv'; test('AI flags are additive and old detection payloads remain valid', () => { - const schema = YAML.parse(readFileSync(new URL('../spec/components/schemas/DetectionFlags.yaml', import.meta.url), 'utf8')); + const schema = YAML.parse(readFileSync(new URL('../spec/components/schemas/LegacyDetectionFlags.yaml', import.meta.url), 'utf8')); const validate = new Ajv({strict:false}).compile(schema); const legacy = Object.fromEntries(schema.required.map(key => [key,false])); assert.equal(validate(legacy), true); diff --git a/test/219-source-signals.test.mjs b/test/219-source-signals.test.mjs new file mode 100644 index 0000000..3550504 --- /dev/null +++ b/test/219-source-signals.test.mjs @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {test} from 'node:test'; +import path from 'node:path'; +import {freshBuild,ROOT} from './helpers.mjs'; + +const KEYS=['vpn','proxy','privacy_relay','check_incomplete','stun_not_checked','os_not_detected','timezone_mismatch','javascript_disabled','os_mismatch','os_mismatch2','ip_mismatch','latency_test','anti_detect_browser','device_spoofing','banned_ip','incognito','search_bot','ai_bot','browser_automation','ai_browser','tor']; +for(const name of ['named','ai-bot','ai-browser']) test('Core #219 emitted '+name+' validates with exactly 21 signals',()=>{ + const {doc,validator}=freshBuild(); + const schema=doc.components.schemas.IdentificationScoredEvent; + const body=JSON.parse(readFileSync(path.join(ROOT,'test/contracts/219-'+name+'.json'),'utf8')); + assert.equal(body.schema_version,'2026-10-12'); + assert.deepEqual(Object.keys(body.data.detection_flags).sort(),KEYS.toSorted()); + const valid=validator.validate(schema,body); assert.ok(valid.valid,JSON.stringify(valid.errors)); + for(const key of KEYS) { + const missing=structuredClone(body);delete missing.data.detection_flags[key]; + assert.equal(validator.validate(schema,missing).valid,false,key+' required'); + } + for(const key of ['browser_vpn_proxy','datacenter_ip','abuser','suspicious_paid_click','good_bot','bad_bot','ai_bot_owner']) { + const extra=structuredClone(body);extra.data.detection_flags[key]=false; + assert.equal(validator.validate(schema,extra).valid,false,key+' outside canon'); + } + for(const [first,second] of [[false,true],[true,false]]) { + const distinct=structuredClone(body);distinct.data.detection_flags.os_mismatch=first;distinct.data.detection_flags.os_mismatch2=second; + assert.equal(validator.validate(schema,distinct).valid,true,'independent OS detectors'); + } + assert.equal(body.data.detection_flags.ai_bot,name==='ai-bot'); + assert.equal(body.data.detection_flags.ai_browser,name==='ai-browser'); +}); diff --git a/test/build.test.mjs b/test/build.test.mjs index df8416a..089dd00 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -14,6 +14,7 @@ const FLAGS = [ 'os_mismatch', 'os_not_detected', 'timezone_mismatch', 'anti_detect_browser', 'browser_automation', 'ip_mismatch', 'incognito', 'search_bot', 'javascript_disabled', 'stun_not_checked', 'check_incomplete', + 'os_mismatch2', 'device_spoofing', 'latency_test', 'banned_ip', 'ai_bot', 'ai_browser', ]; const WEBHOOK_DATA_KEYS = [ 'request_id', 'visitor_id', 'device_id', 'session_id', 'cookie_id', 'user_hid', 'domain', @@ -205,11 +206,11 @@ describe('document', () => { }); describe('schemas', () => { - it('require current detection flags and retain legacy properties', () => { + it('require exactly the 21 current source detection flags', () => { const { doc } = freshBuild(); const flags = doc.components.schemas.DetectionFlags; assert.deepEqual([...flags.required].sort(), [...FLAGS].sort()); - assert.deepEqual(Object.keys(flags.properties).sort(), [...FLAGS, "browser_vpn_proxy", "datacenter_ip", "abuser", "suspicious_paid_click", "os_mismatch2", "device_spoofing", "latency_test", "banned_ip", "ai_bot", "ai_browser"].sort()); + assert.deepEqual(Object.keys(flags.properties).sort(), [...FLAGS].sort()); for (const flag of FLAGS) assert.equal(flags.properties[flag].type, 'boolean', flag); }); diff --git a/test/contracts/219-ai-bot.json b/test/contracts/219-ai-bot.json new file mode 100644 index 0000000..d1e8705 --- /dev/null +++ b/test/contracts/219-ai-bot.json @@ -0,0 +1,86 @@ +{ + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-12", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "ai_bot_owner": "OpenAI", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": true, + "ai_browser": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } +} diff --git a/test/contracts/219-ai-browser.json b/test/contracts/219-ai-browser.json new file mode 100644 index 0000000..d19aea8 --- /dev/null +++ b/test/contracts/219-ai-browser.json @@ -0,0 +1,86 @@ +{ + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-12", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "ai_browser_owner": "Browserbase", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": false, + "ai_browser": true, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } +} diff --git a/test/contracts/219-named.json b/test/contracts/219-named.json new file mode 100644 index 0000000..710a6c5 --- /dev/null +++ b/test/contracts/219-named.json @@ -0,0 +1,88 @@ +{ + "event_id": "52102900cd52d25890e83e18bb0bdec95dedea02", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-12", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "hre": { + "rules_version": "fp21-fixture", + "account_sharing": { + "cluster_id": "11111111-1111-4111-8111-111111111111", + "status": "evaluated", + "level": "high", + "reason": "", + "devices": 4, + "min_devices": 4 + }, + "account_takeover": { + "cluster_id": "22222222-2222-4222-8222-222222222222", + "status": "evaluated", + "level": "medium", + "reason": "fixture" + }, + "impossible_travel": { + "cluster_id": null, + "status": "evaluated", + "level": null, + "reason": "below_distance" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "test-user", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": true, + "search_bot": false, + "ai_bot": false, + "ai_browser": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } +}