Detection rules › Kusto
Tailscale: Mass credential revocation in short window
Identifies when five or more API keys, OAuth clients, or auth keys are revoked or deleted within one hour. May be routine rotation, or a typical cleanup pattern after credential compromise.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule body
id: f817e2fa-6fa0-fc25-5369-cef9b58771af
name: "Tailscale: Mass credential revocation in short window"
description: |
Identifies when five or more API keys, OAuth clients, or auth keys are revoked or deleted within one hour. May be routine rotation, or a typical cleanup pattern after credential compromise.
severity: High
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Audit_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Impact
relevantTechniques:
- T1070
query: |
Tailscale_Audit_CL
| where Action in ("REVOKE", "DELETE")
| where tostring(Target.type) in ("API_KEY", "OAUTH_CLIENT", "AUTH_KEY")
| extend ActorLogin = tostring(Actor.loginName)
| summarize
RevokedCount = count(),
TargetTypes = make_set(tostring(Target.type)),
TargetIds = make_set(tostring(Target.id)),
FirstEvent = min(TimeGenerated),
LastEvent = max(TimeGenerated)
by ActorLogin, bin(TimeGenerated, 1h)
| where RevokedCount >= 5
| project TimeGenerated = LastEvent, ActorLogin, RevokedCount, TargetTypes, TargetIds, FirstEvent, LastEvent
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorLogin
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Tailscale_Audit_CL
Stage 2: where
| where Action in ("REVOKE", "DELETE")
Stage 3: where
| where tostring(Target.type) in ("API_KEY", "OAUTH_CLIENT", "AUTH_KEY")
Stage 4: extend
| extend ActorLogin = tostring(Actor.loginName)
Stage 5: summarize
| summarize
RevokedCount = count(),
TargetTypes = make_set(tostring(Target.type)),
TargetIds = make_set(tostring(Target.id)),
FirstEvent = min(TimeGenerated),
LastEvent = max(TimeGenerated)
by ActorLogin, bin(TimeGenerated, 1h)
Stage 6: where
| where RevokedCount >= 5
Stage 7: project
| project TimeGenerated = LastEvent, ActorLogin, RevokedCount, TargetTypes, TargetIds, FirstEvent, LastEvent
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | in |
| field:"Action" kind:in |
RevokedCount | ge |
| field:"RevokedCount" kind:ge value:"5" |
type | in |
| field:"type" kind:in |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
FirstEvent | project |
LastEvent | project |
RevokedCount | project |
TargetIds | project |
TargetTypes | project |
TimeGenerated | project |