Behavior-based detection engineering
Phishing Click Followed by Suspicious Cloud Session Use
Correlates email-origin URL clicks with materially different Entra sign-ins and independent identity or device-risk signals.
Behavior
What it detects
An email-origin URL click is followed by a successful browser sign-in for the same identity inside a short window, and the resulting session shows multiple independent indicators such as source change, a phishing verdict, Entra risk, or unmanaged/non-compliant device context.
Engineering decision
Why this detection
AiTM phishing can succeed through a legitimate authentication flow, so a successful MFA event does not automatically make the resulting session trustworthy.
This analytic keeps the primary detection narrow: correlate the user’s email-origin click with the nearest successful browser sign-in inside thirty minutes, then require several independent source, phishing, identity-risk, or device-trust signals. A different public IP is useful context, but it is never sufficient on its own.
The drilldowns deliberately move from the candidate into message provenance, sign-in history, shared infrastructure, and mailbox activity so an analyst can distinguish session theft from normal VPN, proxy, mobile, VDI, or roaming behavior.
Signal chain
Detection logic
- Email-origin URL click
- Same normalized user identity
- Successful browser sign-in within thirty minutes
- Material source or trust-context difference
- Independent phishing, identity-risk, or device-trust signal
- Candidate for mailbox and cloud-activity drilldown
Primary analytic
Query
Q-01Detection logicCorrelate phishing clicks with suspicious cloud sessions
What this checks
Generate high-value candidates where an email click is followed by a successful browser sign-in and multiple independent source, phishing, identity-risk, or device-trust signals.
KQL
let lookback = 2h;
let correlation_window = 30m;
let clicks =
UrlClickEvents
| where Timestamp >= ago(lookback)
| where Workload == "Email"
| where isnotempty(AccountUpn)
| where ActionType == "ClickAllowed"
or IsClickedThrough == true
or ThreatTypes has "Phish"
| project
ClickTime = Timestamp,
AccountUpn = tolower(AccountUpn),
ClickIP = IPAddress,
ClickUrl = Url,
UrlChain,
ClickAction = ActionType,
IsClickedThrough,
ClickThreatTypes = ThreatTypes,
NetworkMessageId;
let signins =
EntraIdSignInEvents
| where Timestamp >= ago(lookback)
| where ErrorCode == 0
| where ClientAppUsed == "Browser"
| where isnotempty(AccountUpn)
| project
SignInTime = Timestamp,
AccountUpn = tolower(AccountUpn),
SignInIP = IPAddress,
Application,
ResourceDisplayName,
Country,
State,
City,
UserAgent,
Browser,
DeviceName,
DeviceTrustType,
IsManaged,
IsCompliant,
AuthenticationRequirement,
RiskLevelDuringSignIn,
RiskLevelAggregated,
RiskEventTypes,
RiskState,
SessionId,
UniqueTokenId,
GatewayJA4;
clicks
| join kind=inner signins on AccountUpn
| where SignInTime between (ClickTime .. ClickTime + correlation_window)
| extend TimeDelta = SignInTime - ClickTime
| summarize arg_min(TimeDelta, *) by AccountUpn, SignInTime, SignInIP
| extend
SourceChanged =
isnotempty(ClickIP)
and isnotempty(SignInIP)
and ClickIP != SignInIP,
ClickThreatSignal =
ClickThreatTypes has "Phish",
RiskSignal =
RiskLevelDuringSignIn in (10, 50, 100)
or RiskLevelAggregated in (10, 50, 100)
or RiskState in (4, 5)
or isnotempty(RiskEventTypes),
DeviceTrustSignal =
IsManaged == 0
or IsCompliant == 0
| extend SignalScore =
iif(SourceChanged, 1, 0)
+ iif(ClickThreatSignal, 1, 0)
+ (2 * iif(RiskSignal, 1, 0))
+ iif(DeviceTrustSignal, 1, 0)
| where SignalScore >= 3
| project
ClickTime,
SignInTime,
TimeDelta,
AccountUpn,
SignalScore,
SourceChanged,
ClickThreatSignal,
RiskSignal,
DeviceTrustSignal,
ClickIP,
SignInIP,
ClickUrl,
UrlChain,
ClickThreatTypes,
NetworkMessageId,
Application,
ResourceDisplayName,
Country,
State,
City,
Browser,
UserAgent,
DeviceName,
DeviceTrustType,
IsManaged,
IsCompliant,
AuthenticationRequirement,
RiskLevelDuringSignIn,
RiskLevelAggregated,
RiskEventTypes,
RiskState,
SessionId,
UniqueTokenId,
GatewayJA4
| order by SignalScore desc, SignInTime descSPL
| multisearch
[ search index=<m365_security_index> sourcetype=<url_click_events_sourcetype> earliest=-2h
| eval stage="click"
]
[ search index=<identity_index> sourcetype=<entra_signin_sourcetype> earliest=-2h
| eval stage="signin"
]
| eval
user=lower(coalesce(user, AccountUpn, user_principal_name)),
src=coalesce(src, IPAddress, ip_address),
click_url=if(stage="click", coalesce(url, Url), null()),
url_chain=if(stage="click", coalesce(url_chain, UrlChain), null()),
workload=if(stage="click", coalesce(workload, Workload), null()),
click_action=if(stage="click", coalesce(action, ActionType), null()),
click_threat_types=if(stage="click", coalesce(threat_types, ThreatTypes), null()),
clicked_through=if(stage="click", coalesce(IsClickedThrough, is_clicked_through), null()),
error_code=if(stage="signin", tonumber(coalesce(ErrorCode, error_code)), null()),
auth_action=if(stage="signin", coalesce(action, auth_action), null()),
risk_during=if(stage="signin", tonumber(coalesce(RiskLevelDuringSignIn, risk_level_during)), null()),
risk_aggregated=if(stage="signin", tonumber(coalesce(RiskLevelAggregated, risk_level_aggregated)), null()),
risk_state=if(stage="signin", tonumber(coalesce(RiskState, risk_state)), null()),
risk_events=if(stage="signin", coalesce(RiskEventTypes, risk_event_types), null()),
is_managed=if(stage="signin", tonumber(coalesce(IsManaged, is_managed)), null()),
is_compliant=if(stage="signin", tonumber(coalesce(IsCompliant, is_compliant)), null()),
device_trust=if(stage="signin", coalesce(DeviceTrustType, device_trust_type), null()),
app=if(stage="signin", coalesce(Application, ResourceDisplayName, app), null()),
session_id=if(stage="signin", coalesce(SessionId, session_id), null()),
user_agent=if(stage="signin", coalesce(UserAgent, user_agent), null())
| where isnotnull(user)
| where
(
stage="click"
AND lower(workload)="email"
AND (
click_action="ClickAllowed"
OR clicked_through=1
OR like(lower(click_threat_types), "%phish%")
)
)
OR
(
stage="signin"
AND (auth_action="success" OR error_code=0)
)
| sort 0 user _time
| streamstats current=f
last(eval(if(stage="click", _time, null()))) as click_time
last(eval(if(stage="click", src, null()))) as click_src
last(eval(if(stage="click", click_url, null()))) as click_url
last(eval(if(stage="click", url_chain, null()))) as url_chain
last(eval(if(stage="click", click_threat_types, null()))) as click_threat_types
by user
| where stage="signin"
AND isnotnull(click_time)
AND _time>=click_time
AND _time<=click_time+1800
| eval
source_changed=if(isnotnull(click_src) AND isnotnull(src) AND click_src!=src,1,0),
click_threat_signal=if(like(lower(click_threat_types), "%phish%"),1,0),
risk_signal=if(
risk_during IN (10,50,100)
OR risk_aggregated IN (10,50,100)
OR risk_state IN (4,5)
OR isnotnull(risk_events),
1,0
),
device_trust_signal=if(is_managed=0 OR is_compliant=0,1,0),
signal_score=source_changed+click_threat_signal+(2*risk_signal)+device_trust_signal,
delta_seconds=_time-click_time
| where signal_score>=3
| table
click_time _time delta_seconds user signal_score
source_changed click_threat_signal risk_signal device_trust_signal
click_src src click_url url_chain click_threat_types
app session_id user_agent
risk_during risk_aggregated risk_state risk_events
is_managed is_compliant device_trust
| sort - signal_score - _timeWhat to look for
A post-click browser session where several independent signals converge; the result is a candidate for analyst investigation, not automatic proof of AiTM.
Technical details
Tested signal
Same-user email click followed by successful browser authentication inside thirty minutes with a combined confidence score.
Assumptions
- Safe Links / UrlClickEvents and EntraIdSignInEvents are available with comparable timestamps and normalized UPNs.
- Expected VPN, secure web gateway, VDI, mobile, roaming, and approved device transitions are understood before production tuning.
- Risk and device fields can be absent in some environments; missing fields must not be silently treated as malicious.
Data requirements and relevant fields
- network
Email-origin URL clicks with message, URL, source, verdict, and user context.
TimestampUrlActionTypeAccountUpnWorkloadNetworkMessageIdThreatTypesIPAddressIsClickedThroughUrlChain
- authentication
Successful browser sign-ins with identity, source, risk, device-trust, and session context.
TimestampApplicationErrorCodeSessionIdAccountUpnResourceDisplayNameDeviceNameDeviceTrustTypeIsManagedIsCompliantAuthenticationRequirementRiskLevelAggregatedRiskLevelDuringSignInRiskEventTypesRiskStateUserAgentClientAppUsedBrowserIPAddressCountryStateCityGatewayJA4UniqueTokenId
KQL schema
Uses UrlClickEvents and EntraIdSignInEvents. Defender for Office 365 and Microsoft Entra ID P2 telemetry are required.
SPL schema
Replace both index/sourcetype placeholders. Map local click and Entra fields before applying the score, and add a trusted-egress lookup for known VPN/SWG/VDI transitions.
Limitations
- A different IP alone is insufficient because legitimate network transitions are common.
- Entra risk can be unset even during malicious activity, so the analytic can miss low-signal session theft.
- Safe Links may not preserve every user click or correlate Draft/Sent clicks by NetworkMessageId.
- A short correlation window can miss delayed replay and should be complemented by wider hunting.
KQL uses native Defender XDR and Entra tables. SPL intentionally uses raw normalized vendor scaffolds because Safe Links message IDs, Entra risk, and device-trust fields are not fully represented by CIM.
Analyst workflow
What the analyst should look for
- Did the user click the message URL?
- Was the post-click sign-in source expected for this user?
- Is the source change explained by VPN, secure web gateway, VDI, mobile, or roaming?
- Was the browser or device managed and compliant?
- What Entra risk signals were present?
- Did MFA succeed inside the suspicious sequence?
- What mailbox or cloud actions followed the sign-in?
- Is the same suspicious IP used by other identities?
- Does the same URL or redirect chain appear for other recipients?
Expected result
A post-click browser session where several independent signals converge; the result is a candidate for analyst investigation, not automatic proof of AiTM.
Investigation pivots
Drilldowns
View query — Reconstruct the phishing message
Q-02DrilldownReconstruct the phishing message
What this checks
Resolve the source message, sender, recipients, delivery state, and threat context from the NetworkMessageId returned by the candidate.
KQL
let target_message_id = "<NetworkMessageId>";
EmailEvents
| where Timestamp >= ago(7d)
| where NetworkMessageId == target_message_id
| project
Timestamp,
NetworkMessageId,
InternetMessageId,
SenderFromAddress,
SenderMailFromAddress,
SenderDisplayName,
SenderFromDomain,
SenderIPv4,
RecipientEmailAddress,
Subject,
EmailDirection,
DeliveryAction,
DeliveryLocation,
LatestDeliveryAction,
LatestDeliveryLocation,
ThreatTypes,
AuthenticationDetails,
UrlCount,
AttachmentCount
| order by Timestamp ascSPL
index=<m365_email_index> sourcetype=<email_events_sourcetype>
earliest=-7d
NetworkMessageId="<NetworkMessageId>"
| eval
message_id=coalesce(message_id, NetworkMessageId),
sender=lower(coalesce(sender, SenderFromAddress, SenderMailFromAddress)),
recipient=lower(coalesce(recipient, RecipientEmailAddress)),
subject=coalesce(subject, Subject),
delivery_action=coalesce(delivery_action, LatestDeliveryAction, DeliveryAction),
delivery_location=coalesce(delivery_location, LatestDeliveryLocation, DeliveryLocation),
threat_types=coalesce(threat_types, ThreatTypes)
| fields _time message_id sender recipient subject delivery_action delivery_location threat_types
| sort 0 _timeWhat to look for
The exact source message plus additional recipients or repeated subjects that justify campaign scoping.
Technical details
Tested signal
Message-level evidence for the exact click candidate.
Assumptions
- The candidate retains NetworkMessageId.
- Message telemetry is retained for the requested lookback.
Data requirements and relevant fields
Message-level sender, recipient, delivery, and threat fields keyed by NetworkMessageId.
TimestampNetworkMessageIdInternetMessageIdSenderFromAddressSenderMailFromAddressSenderDisplayNameSenderFromDomainSenderIPv4RecipientEmailAddressSubjectEmailDirectionDeliveryActionDeliveryLocationLatestDeliveryActionLatestDeliveryLocationThreatTypesAuthenticationDetailsUrlCountAttachmentCount
KQL schema
Uses documented EmailEvents fields.
SPL schema
Replace the mail index/sourcetype and map message, sender, recipient, delivery, and verdict fields.
Limitations
- Some mail sources do not retain NetworkMessageId consistently.
- Sender trust or authentication success does not prove the sender account was uncompromised.
Both variants require message-level identifiers from the local mail-security source.
View query — Rebuild the post-click sign-in timeline
Q-03DrilldownRebuild the post-click sign-in timeline
What this checks
Review authentication around the candidate click and sign-in with source, browser, device-trust, risk, and session context.
KQL
let target_user = "<user@domain>";
let pivot_time = datetime(<YYYY-MM-DDTHH:MM:SSZ>);
EntraIdSignInEvents
| where Timestamp between (pivot_time - 30m .. pivot_time + 2h)
| where AccountUpn =~ target_user
| project
Timestamp,
AccountUpn,
ErrorCode,
Application,
ResourceDisplayName,
IPAddress,
Country,
State,
City,
Browser,
UserAgent,
DeviceName,
DeviceTrustType,
IsManaged,
IsCompliant,
AuthenticationRequirement,
RiskLevelDuringSignIn,
RiskLevelAggregated,
RiskEventTypes,
RiskState,
SessionId,
UniqueTokenId,
GatewayJA4
| order by Timestamp ascSPL
index=<identity_index> sourcetype=<entra_signin_sourcetype>
earliest=<pivot_minus_30m> latest=<pivot_plus_2h>
| eval
user=lower(coalesce(user, AccountUpn, user_principal_name)),
src=coalesce(src, IPAddress, ip_address),
app=coalesce(app, Application, ResourceDisplayName),
browser=coalesce(browser, Browser),
user_agent=coalesce(user_agent, UserAgent),
device=coalesce(device, DeviceName),
session_id=coalesce(session_id, SessionId),
error_code=tonumber(coalesce(ErrorCode, error_code)),
risk_level=tonumber(coalesce(RiskLevelDuringSignIn, RiskLevelAggregated, risk_level)),
risk_state=tonumber(coalesce(RiskState, risk_state)),
is_managed=tonumber(coalesce(IsManaged, is_managed)),
is_compliant=tonumber(coalesce(IsCompliant, is_compliant))
| where user="<user@domain>"
| fields
_time user src app browser user_agent device session_id
error_code risk_level risk_state is_managed is_compliant
| sort 0 _timeWhat to look for
A coherent post-click timeline showing whether the suspicious session differs from nearby expected sessions.
Technical details
Tested signal
Identity-session context around the candidate time.
Assumptions
- The candidate provides a target user and pivot time.
- Entra sign-in timestamps are comparable with click telemetry.
Data requirements and relevant fields
- authentication
Sign-in events around a selected user and pivot time.
TimestampApplicationErrorCodeSessionIdAccountUpnResourceDisplayNameDeviceNameDeviceTrustTypeIsManagedIsCompliantAuthenticationRequirementRiskLevelAggregatedRiskLevelDuringSignInRiskEventTypesRiskStateUserAgentBrowserIPAddressCountryStateCityGatewayJA4UniqueTokenId
KQL schema
Uses EntraIdSignInEvents and a bounded candidate-centered time window.
SPL schema
Replace time placeholders with the local candidate window and map Entra fields to the normalized aliases.
Limitations
- The placeholder pivot time must be replaced with the candidate timestamp.
- IP geolocation and device names can be incomplete or misleading in brokered cloud access.
This is an investigation query. Replace target_user and pivot_time with values returned by the detection.
View query — Find other identities using the suspicious sign-in IP
Q-04DrilldownFind other identities using the suspicious sign-in IP
What this checks
Determine whether the candidate source is isolated to one identity or appears across multiple cloud accounts.
KQL
let suspicious_ip = "<suspicious_signin_ip>";
EntraIdSignInEvents
| where Timestamp >= ago(7d)
| where IPAddress == suspicious_ip
| summarize
FirstSeen=min(Timestamp),
LastSeen=max(Timestamp),
SignIns=count(),
Users=make_set(AccountUpn, 100),
Applications=make_set(ResourceDisplayName, 30),
Countries=make_set(Country, 20),
RiskEvents=make_set(RiskEventTypes, 30)
by IPAddressSPL
index=<identity_index> sourcetype=<entra_signin_sourcetype>
earliest=-7d
| eval
user=lower(coalesce(user, AccountUpn, user_principal_name)),
src=coalesce(src, IPAddress, ip_address),
app=coalesce(app, Application, ResourceDisplayName),
risk=coalesce(RiskEventTypes, risk_event_types)
| where src="<suspicious_signin_ip>"
| stats
min(_time) as first_seen
max(_time) as last_seen
count as signins
dc(user) as unique_users
values(user) as users
values(app) as applications
values(risk) as risk_events
by src
| convert ctime(first_seen) ctime(last_seen)What to look for
Several identities using the same unexplained source address near the campaign window.
Technical details
Tested signal
Multiple identity sign-ins from the same suspicious source address.
Assumptions
- The suspicious IP is taken from the detection candidate rather than guessed.
- Shared corporate egress and trusted identity providers are understood before escalation.
Data requirements and relevant fields
- authentication
Sign-in activity keyed by source IP with user, application, country, and risk context.
TimestampAccountUpnResourceDisplayNameIPAddressCountryRiskEventTypes
KQL schema
Uses documented EntraIdSignInEvents source, account, application, country, and risk fields.
SPL schema
Map the local Entra source to user, src, app, and risk before grouping.
Limitations
- NAT, VPN, proxy, and cloud identity infrastructure can legitimately place many users behind one IP.
- A public IP can change ownership over time.
This is an IOC-free drilldown driven by the current candidate's source IP.
View query — Review mailbox actions after the suspicious session
Q-05DrilldownReview mailbox actions after the suspicious session
What this checks
Review cloud and Exchange Online activity for the affected identity after the candidate session.
KQL
let target_user = "<user@domain>";
let pivot_time = datetime(<YYYY-MM-DDTHH:MM:SSZ>);
CloudAppEvents
| where Timestamp between (pivot_time .. pivot_time + 4h)
| where AccountId =~ target_user
| project
Timestamp,
AccountId,
Application,
ActionType,
ActivityType,
IPAddress,
CountryCode,
City,
Isp,
UserAgent,
ObjectName,
ObjectType,
UncommonForUser,
RawEventData
| order by Timestamp ascSPL
index=<m365_audit_index> sourcetype=<m365_audit_sourcetype>
earliest=<pivot_time> latest=<pivot_plus_4h>
| eval
user=lower(coalesce(user, UserId, AccountId, account_id)),
action=coalesce(action, Operation, ActionType),
src=coalesce(src, ClientIP, IPAddress, src_ip),
object=coalesce(object, ObjectId, ObjectName)
| where user="<user@domain>"
| fields _time user src action object Workload Application Parameters
| sort 0 _timeWhat to look for
Unexpected mailbox-rule changes, message operations, sending, authentication changes, or other cloud actions aligned with the suspicious source/session.
Technical details
Tested signal
Mailbox or cloud operations occurring after suspicious authentication.
Assumptions
- CloudAppEvents or equivalent Microsoft 365 audit data is available.
- The candidate provides a user and pivot time.
Data requirements and relevant fields
- saas audit
Post-authentication Microsoft 365 activity with user, action, source, object, uncommon-user context, and raw details.
TimestampActionTypeApplicationAccountIdIPAddressCountryCodeCityIspUserAgentActivityTypeObjectNameObjectTypeRawEventDataUncommonForUser
KQL schema
Uses CloudAppEvents and a four-hour candidate-centered investigation window.
SPL schema
Map the Microsoft 365 audit source to user, action, src, object, workload/application, and parameters.
Limitations
- ActionType coverage depends on Microsoft 365 connectors and licensing.
- RawEventData schemas vary by action and should be parsed only when structured fields are insufficient.
The query deliberately leaves ActionType broad for investigation. Use structured fields first and parse action-specific RawEventData only when necessary.
Legitimate resemblance
What the analyst should confirm
A secure web gateway, VPN, mobile carrier, VDI platform, or roaming transition changes the public source between click and sign-in.
The transition matches an approved egress path, device/browser context remains expected, identity risk is absent, and no suspicious mailbox or cloud activity follows.A user signs in from an unmanaged or non-compliant personal device after a legitimate email click.
The URL and sender are verified legitimate, the source and user agent fit the user's history, no identity-risk signal is present, and subsequent cloud activity is expected.A legitimate phishing simulation or security-awareness platform produces email-click and threat-verdict telemetry.
The message, URL, campaign owner, recipient scope, simulation window, and post-click authentication behavior match the approved exercise and no attacker-controlled session appears.
Confirmed match
Action after a confirmed match
- Revoke active sessions and tokens.
- Reset credentials after session revocation.
- Review and remove attacker-added or modified MFA authentication methods.
- Review and remove suspicious mailbox rules.
- Review mailbox reads, deletions, sent mail, and other cloud activity.
- Purge confirmed follow-on phishing where supported.
- Hunt other exposed users and shared suspicious infrastructure.
Threat hunt
Could this be happening elsewhere?
Hunt for this behavior across the environment.Technical boundary
Telemetry and limitations
Message-level Microsoft 365 mail telemetry used to reconstruct source messages and recipient scope.
Required fieldsTimestampNetworkMessageIdInternetMessageIdSenderFromAddressSenderMailFromAddressSenderDisplayNameSenderFromDomainSenderIPv4RecipientEmailAddressSubjectEmailDirectionDeliveryActionDeliveryLocationLatestDeliveryActionLatestDeliveryLocationThreatTypesAuthenticationDetailsUrlCountAttachmentCount
- Network
Safe Links URL-click telemetry with user, message identifier, URL, redirect chain, action, source IP, and threat context.
Required fieldsTimestampUrlActionTypeAccountUpnWorkloadNetworkMessageIdThreatTypesDetectionMethodsIPAddressIsClickedThroughUrlChainReportIdAppName
- Authentication
Entra browser sign-ins with source, application, risk, device-trust, and session context.
Required fieldsTimestampApplicationErrorCodeSessionIdAccountUpnResourceDisplayNameDeviceNameDeviceTrustTypeIsManagedIsCompliantAuthenticationRequirementRiskLevelAggregatedRiskLevelDuringSignInRiskEventTypesRiskStateUserAgentClientAppUsedBrowserIPAddressCountryStateCityGatewayJA4UniqueTokenId
- Saas Audit
Microsoft 365 / Exchange Online audit activity used to investigate mailbox and cloud actions after suspicious authentication.
Required fieldsTimestampActionTypeApplicationAccountObjectIdAccountIdIPAddressCountryCodeCityIspUserAgentActivityTypeObjectNameObjectTypeRawEventDataUncommonForUser
Blind spots
- Missing Safe Links or equivalent click telemetry removes the direct email-click correlation.
- Entra risk can remain unset, reducing confidence for otherwise suspicious session replay.
- Delayed replay can fall outside the thirty-minute detection window.
- Trusted proxies, identity brokers, and privacy services can obscure the true source and device context.
- Mailbox activity can occur through connectors or APIs that are not fully represented in the selected audit source.
Behavior mapping
MITRE ATT&CK
T1566.002· Spearphishing LinkThe reported campaigns delivered phishing links through trusted or convincing email and collaboration workflows.
T1204.001· Malicious LinkCompromise depended on a recipient following a malicious or attacker-controlled link path.
T1539· Steal Web Session CookieAiTM infrastructure captured authenticated session material that could be replayed after MFA completed.
T1078.004· Cloud AccountsThe attacker reused the compromised cloud identity to access Microsoft 365 resources.
T1114.002· Remote Email CollectionThe source campaign used mailbox content and recent threads to select recipients and manage follow-on phishing.
Mappings describe the behavior examined by this analytic. They do not prove attribution, deployment, or technique-wide coverage.
Review boundary
Sources and limits
- External sources
- 5
Exact fields, retention, and operational thresholds remain environment-specific.