THREAT-C2-DNSOverHTTPSCovertChannel Google Chronicle · YARA-L

Detect Command and Control via DNS-over-HTTPS Covert Channel in Google Chronicle

Adversaries increasingly transport command-and-control traffic inside DNS-over-HTTPS (DoH) requests issued directly to public resolver endpoints such as cloudflare-dns.com, dns.google, and doh.opendns.com, deliberately bypassing the enterprise's own recursive resolvers. This defeats the single most valuable DNS control most organisations own: because the query never reaches the corporate resolver, it never appears in DNS server analytical logs, never hits an RPZ/DNS-firewall blocklist, and never gets sinkholed, so a domain-based blocklist that would have stopped classic DNS tunnelling has no visibility at all. The traffic that remains on the wire is an ordinary-looking TLS session to a well-known, high-reputation cloud resolver on port 443 — which reputation-based proxy and firewall controls will pass. Frameworks such as godoh, Merlin, Sliver's DoH transport, and the Godlua backdoor implement this pattern, encoding tasking and exfiltrated data into DoH wire-format requests (RFC 8484) issued as HTTP POST bodies with content-type application/dns-message, which — unlike the GET/dns= variant — leaves no query content in URL strings or proxy access logs. Detection therefore has to pivot off behaviour rather than content: an endpoint process that is not an approved browser opening sustained sessions to a public DoH endpoint, at a machine-regular cadence with little or no jitter, while the same host's queries stop appearing in corporate resolver telemetry. This differs from the T1071.004 base record and from THREAT-DNSTunnel-Exfil (which key on high-entropy subdomain labels and TXT/NULL record volume in resolver logs) in that no resolver log entry is produced at all — the fingerprint here is the combination of resolver-bypass, DoH endpoint identity, non-browser initiating process, and low-jitter beacon pacing.

MITRE ATT&CK

Tactic
Command and Control

YARA-L Detection Query

Google Chronicle (YARA-L)
yaral
rule doh_covert_channel_c2_beacon {
  meta:
    author = "Detection Engineering"
    description = "Detects command-and-control beaconing over DNS-over-HTTPS to public resolver endpoints from non-browser processes, bypassing corporate recursive resolvers."
    mitre_attack_tactic = "Command and Control"
    mitre_attack_technique = "T1071.004"
    severity = "HIGH"
    priority = "HIGH"

  events:
    $e.metadata.event_type = "NETWORK_HTTP"
    $e.target.port = 443
    (
      $e.target.hostname = /^(cloudflare-dns\.com|mozilla\.cloudflare-dns\.com|dns\.google|doh\.opendns\.com|dns\.quad9\.net|doh\.cleanbrowsing\.org|dns\.nextdns\.io)$/ nocase or
      net.ip_in_range_cidr($e.target.ip, "1.1.1.1/32") or
      net.ip_in_range_cidr($e.target.ip, "8.8.8.8/32") or
      net.ip_in_range_cidr($e.target.ip, "208.67.222.222/32")
    )
    not net.ip_in_range_cidr($e.principal.ip, "10.50.53.0/24")
    not $e.principal.process.file.full_path = /(chrome|msedge|firefox|brave|opera|safari)(\.exe)?$/ nocase
    $host = $e.principal.hostname
    $process = $e.principal.process.file.full_path
    $resolver = $e.target.hostname

  match:
    $host, $process, $resolver over 1h

  outcome:
    $beacon_count = count($e.metadata.id)
    $total_response_bytes = sum($e.network.received_bytes)

  condition:
    $e and $beacon_count >= 30
}
high severity medium confidence

Chronicle YARA-L 2.0 rule matching NETWORK_HTTP UDM events destined for public DoH resolver endpoints on port 443, keyed on target hostname or on resolver address using net.ip_in_range_cidr, Chronicle's genuine CIDR predicate, for both the resolver match and the exclusion of the sanctioned internal forwarder range. Approved browser processes are excluded on the principal process path so that only non-browser DoH clients survive, and match/outcome aggregation over a one-hour window by host, process and resolver enforces the sustained-callback arm. The jitter arm is not expressible in YARA-L outcome functions and is assessed in triage; the total_response_bytes outcome is provided so that abnormally large aggregate DoH responses, indicative of data transport rather than resolution, are visible on the alert itself.

Data Sources

Google Chronicle SIEMChronicle UDM NETWORK_HTTP events (proxy and secure-web-gateway forwarders)Chronicle EDR connectors for principal process attribution

Required Tables

NETWORK_HTTP (UDM event type)

False Positives & Tuning

  • Browser Secure DNS traffic where the browser binary is renamed or installed to a path the exclusion regex does not match
  • Endpoint agents, VPN clients and content filters with a bundled DoH stub resolver
  • CI runners and uptime probes health-checking a public DoH endpoint on a fixed schedule
  • Sanctioned internal DoH forwarders outside the excluded address range

Other platforms for THREAT-C2-DNSOverHTTPSCovertChannel


Testing Methodology

Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Low-Jitter DoH POST Beacon to Cloudflare from a Non-Browser Process

    Expected signal: DeviceNetworkEvents (or Sysmon Event ID 3): approximately 40 ConnectionSuccess records from curl to cloudflare-dns.com / 1.1.1.1 on port 443 spaced 10 seconds apart. Proxy access logs with TLS inspection additionally show POST /dns-query with content-type application/dns-message. Corporate DNS resolver logs show no corresponding query for example.com, which is the resolver-bypass half of the signature.

  2. Test 2PowerShell DoH Resolver-Bypass Beacon to Google Public DNS

    Expected signal: DeviceNetworkEvents: approximately 40 ConnectionSuccess records with InitiatingProcessFileName powershell.exe to dns.google / 8.8.8.8 on port 443 at a 10-second cadence. Sysmon Event ID 3 records the same connections with Image set to the PowerShell binary. The corporate DNS server logs contain no example.com query from this host.

  3. Test 3OpenDNS DoH Beacon from a Scripted macOS Client

    Expected signal: DeviceNetworkEvents on the macOS device: approximately 40 ConnectionSuccess records from curl to doh.opendns.com / 208.67.222.222 on port 443 at a 10-second cadence. Proxy logs show GET /dns-query with a dns= parameter rather than a POST body, which is the GET variant of RFC 8484.

  4. Test 4Jittered DoH Beacon Evasion Baseline

    Expected signal: DeviceNetworkEvents: approximately 40 ConnectionSuccess records from curl to cloudflare-dns.com / 1.1.1.1 on port 443 with irregular spacing across roughly 30 minutes.


Response Playbook

Triage

  1. Identify the initiating process and its full path from the DeviceNetworkEvents result. A browser is expected to speak DoH; svchost.exe running from a non-System32 path, a signed-but-unusual LOLBin, an interpreter (powershell.exe, python.exe, node.exe), or an unsigned binary in %APPDATA%/%TEMP%/%ProgramData% resolving DNS over HTTPS is not, and is the single strongest discriminator in this detection.
  2. Confirm the resolver-bypass half of the fingerprint: query your corporate DNS server analytical logs (or Sentinel DnsEvents) for the same host over the same window. A host generating sustained network activity while producing near-zero corporate resolver queries is actively bypassing the resolver, which is the condition that makes this technique worth alerting on.
  3. Assess the jitter figure directly. Human-driven browsing produces a wide interval spread; a standard deviation at or below a few seconds against a fixed mean is a scheduler, not a person. Plot the raw gap series before dismissing the alert, because a single long idle gap can inflate the standard deviation and mask an otherwise metronomic beacon.
  4. If proxy or TLS-inspection telemetry is available, confirm the RFC 8484 request shape: HTTP POST with content-type application/dns-message. Genuine browser DoH traffic uses the same shape, so treat this as corroboration of the channel, not of maliciousness — it is the combination with a non-browser process that matters.
  5. Compare response sizes against a DNS baseline. DoH responses carrying C2 tasking or exfiltrated content are typically much larger and far more uniform in size than the mixed distribution of ordinary A/AAAA answers; a tight cluster of large responses indicates data transport rather than name resolution.
  6. Check the host for a recently written DoH client configuration or a browser policy change enabling Secure DNS, and check whether the process was launched by a parent consistent with legitimate software installation or by a suspicious execution chain (Office, script host, scheduled task, service creation).
  7. Determine whether the same process name, hash or path is beaconing to a public DoH endpoint on any other host in the estate — a single implant deployed at scale will show the identical process/interval signature across multiple devices.

Containment

  1. Block outbound access to public DoH endpoints at the perimeter by hostname and by resolver address range, and enforce it for all clients except the sanctioned internal forwarders — this is the control that converts the technique back into ordinary resolver-visible DNS.
  2. Deploy the canary domain use-application-dns.net as an NXDOMAIN on the corporate resolver, which instructs Firefox to disable its default DoH; pair it with the Chrome/Edge DnsOverHttpsMode group policy set to 'off' or 'secure' with an enterprise template, so that browser DoH stops competing with the signal.
  3. Isolate the endpoint via EDR network isolation once a non-browser initiating process is confirmed, before killing the process — DoH implants commonly re-establish immediately and an uncontained kill only costs you the running process's memory.
  4. Rotate credentials for any account that was interactively logged on to the host during the beaconing window, since a DoH channel is a fully bidirectional transport capable of moving credential material out.
  5. Add the specific process hash and path to an EDR block rule and sweep the estate for the same indicator, as DoH-capable loaders are typically deployed to multiple hosts in one campaign.

Evidence Collection

  1. Full DeviceNetworkEvents (or proxy access log) history for the device/process/destination tuple across the entire activity window rather than just the alerting window, since beacons commonly run for days before crossing a volume threshold.
  2. Corporate DNS resolver logs for the same host and window — the absence of expected queries is itself the evidence of resolver bypass and should be captured explicitly rather than inferred later.
  3. A process memory dump of the beaconing process while it is still running; DoH implants hold the resolver endpoint URL, the encoding scheme and often the decoded tasking buffer in memory.
  4. The on-disk binary or script implementing the DoH client, its parent process chain, and any dropped configuration file naming the resolver endpoint, together with prefetch and Amcache/Shimcache entries establishing first execution time.
  5. TLS inspection or full packet capture of the DoH sessions if available, which yields the actual dns-message bodies and is the only artifact that can establish what was queried and what was returned.
  6. Any browser or OS policy artefact (registry values under the Chrome/Edge DnsOverHttps policy keys, Firefox network.trr preferences) showing when and by what mechanism DoH was enabled on the host.

Escalation Criteria

  • !The initiating process is not a browser and is unsigned, or is signed but executing from a user-writable directory — treat as a confirmed implant and escalate to incident response immediately.
  • !Corporate resolver logs confirm the host produced effectively no normal DNS traffic during the beaconing window, establishing deliberate resolver bypass rather than incidental browser DoH.
  • !Response-size analysis or recovered dns-message bodies indicate bulk data transport rather than name resolution, implying exfiltration over the C2 channel (T1041) is already in progress.
  • !The same process indicator or beacon interval signature is observed on more than one host, indicating a campaign rather than an isolated installation.
  • !The beaconing host sits in a server, datacenter or privileged management segment rather than a standard user endpoint subnet, where no browser DoH explanation is even plausible.
  • !The activity was sustained for more than 48 hours before detection, indicating a long-established channel and a correspondingly wide window for tasking and data movement.

Investigation Guide

Related Techniques

Forensic Artifacts

  • >DeviceNetworkEvents / Sysmon Event ID 3 records tying the DoH sessions to a specific process, path, hash and user — the primary attribution artifact, since network-only telemetry cannot name the client.
  • >Proxy access logs with TLS inspection, showing POST requests to the DoH endpoint with content-type application/dns-message and the response sizes.
  • >Zeek ssl.log entries with server_name set to the DoH provider and the associated JA3/JA3S fingerprints, which cluster non-browser TLS stacks distinctly from real browsers.
  • >Corporate DNS resolver logs for the host, whose sparseness during the beacon window evidences resolver bypass.
  • >Registry policy values for Chrome/Edge Secure DNS and Firefox network.trr.mode preferences in the user profile, establishing whether DoH was enabled administratively or by the implant.
  • >The DoH client binary or script and its dropped configuration file naming the resolver endpoint, plus prefetch, Amcache and scheduled-task/service persistence artifacts for its execution history.
  • >Process memory of the beaconing process, containing the resolver URL, encoding scheme and decoded tasking data.

Tuning Guidance

Start by running the first hunting query in inventory mode for a full week and build two allowlists from the result: the browser executables that legitimately speak DoH in your estate, and the address ranges of any sanctioned internal DoH forwarder or secure-web-gateway egress node. Both allowlists belong in the detection as the ApprovedDohClients dynamic list and the ipv4_is_in_range exclusion respectively — note that resolver exclusions must be expressed with ipv4_is_in_range rather than an in() list, because in() performs string equality and will silently fail to match anything but an exact address literal. The single most effective tuning action is not a query change at all: publish use-application-dns.net as NXDOMAIN on your resolver and set the Chrome/Edge DnsOverHttpsMode policy, which removes the browser-DoH false-positive population wholesale and leaves the remaining hits overwhelmingly interesting. Jitter thresholds should be set from your own baseline; five seconds of standard deviation is a reasonable start for fast beacons, but mature frameworks configured with 30-50 percent jitter will evade it entirely, so run the second hunting query on the jitter-to-mean ratio rather than the absolute value if you suspect a tuned implant. Do not attempt to build the primary detection on corporate DNS resolver logs or on Sentinel DnsEvents — the whole point of the technique is that the query never reaches your resolver, so a resolver-based rule will never fire; resolver telemetry is valuable here only as the negative-space corroboration described in triage. Finally, if your proxy performs TLS inspection, add a content-type application/dns-message arm as a high-confidence standalone rule for non-browser user-agents, and if it does not, substitute Zeek ssl.log JA3 fingerprinting, since non-browser TLS stacks (Go, Python, .NET) cluster into a small, easily-baselined set of JA3 hashes that differ sharply from real browsers.


Hunting Queries

Broad inventory hunt for every process in the estate that talks to a public DoH endpoint, deliberately unfiltered by jitter or volume. The output is intended to be read as an allowlist-building exercise: everything that is not an approved browser or a sanctioned forwarder is a candidate for the tighter beaconing detection, and the process-path column usually makes the anomalies obvious on first read.

Hunting — KQL
kql
// Hunt: hosts whose processes reach public DoH endpoints while producing little or no corporate resolver traffic
let LookbackWindow = 7d;
let DohHostnames = dynamic(["cloudflare-dns.com", "mozilla.cloudflare-dns.com", "dns.google", "doh.opendns.com", "dns.quad9.net", "doh.cleanbrowsing.org", "dns.nextdns.io"]);
DeviceNetworkEvents
| where Timestamp > ago(LookbackWindow)
| where RemotePort == 443
| extend RemoteHost = tolower(tostring(split(tostring(coalesce(RemoteUrl, "")), "/")[0]))
| where RemoteHost has_any (DohHostnames)
    or ipv4_is_in_range(RemoteIP, "1.1.1.1/32")
    or ipv4_is_in_range(RemoteIP, "8.8.8.8/32")
    or ipv4_is_in_range(RemoteIP, "208.67.222.222/32")
| where ipv4_is_in_range(RemoteIP, "10.50.53.0/24") == false
| summarize DohConnections = count(), Destinations = dcount(RemoteIP), FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessFolderPath
| where DohConnections > 10
| sort by DohConnections desc
Hunting — SPL
spl
index=sysmon sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 DestinationPort=443
| search DestinationHostname="cloudflare-dns.com" OR DestinationHostname="mozilla.cloudflare-dns.com" OR DestinationHostname="dns.google" OR DestinationHostname="doh.opendns.com" OR DestinationHostname="dns.quad9.net" OR DestinationHostname="doh.cleanbrowsing.org" OR DestinationHostname="dns.nextdns.io"
| where NOT cidrmatch("10.50.53.0/24", DestinationIp)
| stats count as DohConnections, dc(DestinationIp) as Destinations, earliest(_time) as FirstSeen, latest(_time) as LastSeen by ComputerName, Image, User
| where DohConnections>10
| sort - DohConnections

Hunts purely on pacing regularity, ranking by the ratio of jitter to mean interval so that a slow beacon with a 30-minute period is surfaced alongside a fast one. Deliberately ignores process identity so it also catches implants that have injected into or masqueraded as an approved browser, which the primary detection's approved-client arm would otherwise discount.

Hunting — KQL
kql
// Hunt: metronomic pacing to any public DoH endpoint, surfacing low-jitter callbacks regardless of process identity
let LookbackWindow = 7d;
let DohHostnames = dynamic(["cloudflare-dns.com", "mozilla.cloudflare-dns.com", "dns.google", "doh.opendns.com", "dns.quad9.net"]);
DeviceNetworkEvents
| where Timestamp > ago(LookbackWindow)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 443
| extend RemoteHost = tolower(tostring(split(tostring(coalesce(RemoteUrl, "")), "/")[0]))
| where RemoteHost has_any (DohHostnames) or ipv4_is_in_range(RemoteIP, "1.1.1.1/32") or ipv4_is_in_range(RemoteIP, "8.8.8.8/32")
| sort by DeviceId asc, InitiatingProcessId asc, Timestamp asc
| serialize
| extend PrevTimestamp = prev(Timestamp), PrevDevice = prev(DeviceId), PrevProcess = prev(InitiatingProcessId)
| where DeviceId == PrevDevice and InitiatingProcessId == PrevProcess
| extend GapSeconds = datetime_diff('second', Timestamp, PrevTimestamp)
| summarize Callbacks = count(), AvgGapSeconds = avg(GapSeconds), JitterSeconds = stdev(GapSeconds)
    by DeviceName, InitiatingProcessFileName, RemoteHost, bin(Timestamp, 6h)
| where Callbacks > 20 and JitterSeconds < 10
| extend JitterRatio = JitterSeconds / AvgGapSeconds
| sort by JitterRatio asc, Callbacks desc
Hunting — SPL
spl
index=proxy sourcetype="bluecoat:proxysg:access:syslog" (cs_host="cloudflare-dns.com" OR cs_host="mozilla.cloudflare-dns.com" OR cs_host="dns.google" OR cs_host="doh.opendns.com" OR cs_host="dns.quad9.net")
| sort 0 c_ip, cs_userAgent, _time
| streamstats current=f last(_time) as PrevTime by c_ip, cs_userAgent
| eval GapSeconds=_time-PrevTime
| where isnotnull(GapSeconds) AND GapSeconds>=0
| bucket _time span=6h
| stats count as Callbacks, avg(GapSeconds) as AvgGapSeconds, stdev(GapSeconds) as JitterSeconds by c_ip, cs_userAgent, cs_host, _time
| where Callbacks>20 AND JitterSeconds<10
| eval JitterRatio=round(JitterSeconds/AvgGapSeconds, 3)
| sort JitterRatio, - Callbacks

Atomic Red Team Tests

Test 1 Low-Jitter DoH POST Beacon to Cloudflare from a Non-Browser Process
linux

Simulates an implant using the RFC 8484 POST form of DNS-over-HTTPS against cloudflare-dns.com on a fixed 10-second interval from curl rather than a browser, reproducing all three scored fingerprints (non-browser initiating process, near-zero jitter, sustained frequency) without deploying an actual C2 framework. The base64url payload is a valid DNS wire-format query for example.com A.

Command

bash
for i in $(seq 1 40); do
  printf 'q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB' | base64 -d > /tmp/doh_query.bin 2>/dev/null
  curl -s -o /dev/null -H 'content-type: application/dns-message' -H 'accept: application/dns-message' --data-binary @/tmp/doh_query.bin https://cloudflare-dns.com/dns-query
  sleep 10
done
rm -f /tmp/doh_query.bin
echo 'DoH beacon simulation complete: 40 POST callbacks at a 10s interval'

Cleanup

bash
rm -f /tmp/doh_query.bin

Expected Telemetry

DeviceNetworkEvents (or Sysmon Event ID 3): approximately 40 ConnectionSuccess records from curl to cloudflare-dns.com / 1.1.1.1 on port 443 spaced 10 seconds apart. Proxy access logs with TLS inspection additionally show POST /dns-query with content-type application/dns-message. Corporate DNS resolver logs show no corresponding query for example.com, which is the resolver-bypass half of the signature.

Expected Detection

BeaconCount reaches 40 against MinBeaconCount of 30, AvgGapSeconds lands near 10 against a 300s ceiling, and JitterSeconds stays under 5 given the fixed sleep. IsUnexpectedClient is true because curl is not in ApprovedDohClients, so DohBeaconScore reaches 3 and the rule fires.

Test 2 PowerShell DoH Resolver-Bypass Beacon to Google Public DNS
windows

Simulates a Windows implant resolving DNS entirely outside the corporate resolver by issuing repeated DoH requests to dns.google from powershell.exe on a fixed interval. Uses the JSON API form for readability, which still produces the identical network-layer and process-attribution fingerprint the detection scores on.

Command

powershell
1..40 | ForEach-Object { try { Invoke-WebRequest -Uri 'https://dns.google/resolve?name=example.com&type=A' -Headers @{ 'accept' = 'application/dns-json' } -UseBasicParsing -TimeoutSec 5 | Out-Null } catch {}; Start-Sleep -Seconds 10 }; Write-Host 'DoH resolver-bypass simulation complete: 40 callbacks to dns.google'

Expected Telemetry

DeviceNetworkEvents: approximately 40 ConnectionSuccess records with InitiatingProcessFileName powershell.exe to dns.google / 8.8.8.8 on port 443 at a 10-second cadence. Sysmon Event ID 3 records the same connections with Image set to the PowerShell binary. The corporate DNS server logs contain no example.com query from this host.

Expected Detection

IsUnexpectedClient is true (powershell.exe is not an approved browser), IsLowJitter is true given the fixed Start-Sleep, and IsHighFrequency is true at 40 callbacks with a 10-second mean, giving a DohBeaconScore of 3. The ipv4_is_in_range arm matches 8.8.8.8/32 even if RemoteUrl is not populated.

Test 3 OpenDNS DoH Beacon from a Scripted macOS Client
macos

Simulates a macOS implant beaconing over DoH to doh.opendns.com on a fixed interval from a scripted, non-browser client, validating that the detection's provider list and process-identity arm both cover the third named public provider and the macOS endpoint population.

Command

bash
for i in $(seq 1 40); do
  /usr/bin/curl -s -o /dev/null -H 'accept: application/dns-message' 'https://doh.opendns.com/dns-query?dns=q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB'
  sleep 10
done
echo 'macOS DoH beacon simulation complete: 40 callbacks to doh.opendns.com'

Expected Telemetry

DeviceNetworkEvents on the macOS device: approximately 40 ConnectionSuccess records from curl to doh.opendns.com / 208.67.222.222 on port 443 at a 10-second cadence. Proxy logs show GET /dns-query with a dns= parameter rather than a POST body, which is the GET variant of RFC 8484.

Expected Detection

IsUnexpectedClient and IsLowJitter and IsHighFrequency all evaluate true for a DohBeaconScore of 3. On the SPL proxy rule the content-type arm does not contribute for the GET variant, but the non-browser user-agent, jitter and frequency arms still total 3 and exceed the firing threshold of 2.

Test 4 Jittered DoH Beacon Evasion Baseline
linux

Control test that beacons to cloudflare-dns.com with a randomised 5-to-90 second interval, deliberately defeating the fixed-jitter arm of the detection. Run this after the low-jitter tests to measure how much of the signal survives on process identity and frequency alone, and to calibrate whether the jitter-to-mean ratio hunting query is needed in your environment.

Command

bash
for i in $(seq 1 40); do
  /usr/bin/curl -s -o /dev/null -H 'accept: application/dns-message' 'https://cloudflare-dns.com/dns-query?dns=q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB'
  sleep $(( ( RANDOM % 85 ) + 5 ))
done
echo 'Jittered DoH beacon baseline complete: 40 callbacks with 5-90s randomised interval'

Expected Telemetry

DeviceNetworkEvents: approximately 40 ConnectionSuccess records from curl to cloudflare-dns.com / 1.1.1.1 on port 443 with irregular spacing across roughly 30 minutes.

Expected Detection

IsLowJitter evaluates false because the standard deviation of the interval exceeds 5 seconds. IsUnexpectedClient remains true and IsHighFrequency remains true at a mean interval near 47 seconds, so DohBeaconScore reaches exactly 2 and the rule still fires — this test confirms the composite scoring degrades gracefully rather than failing closed against a jittered implant.

Related Detections