aws-cloudwatch-tamper-metric-filter

logs:DeleteMetricFilter or logs:PutMetricFilter on a log group removes or corrupts the metric filter expression that feeds CloudWatch alarms, creating a detection blind spot.

derived aws emits CanModifyConfiguration

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} HasPermission {'log_group': None}

where

node_type(?log_group) == LoggingService ?log_group.provider_type == 'AWS::Logs::LogGroup' ?log_group.metricFilterCount > 0 ?principal has EFFECTIVE logs:DeleteMetricFilter OR logs:PutMetricFilter on ?log_group ARN

emit

source typeIdentity
target typeLoggingService
source?principal
target?log_group
permissionslogs:DeleteMetricFilter logs:PutMetricFilter
conditionsiam_permission scp_or_org_policy
state logicACTIVE when logs:DeleteMetricFilter is confirmed EFFECTIVE on the log group ARN (identity-based policy + no explicit deny + no SCP deny) — definitive alarm silence (filter gone, metric no longer increments). CONDITIONAL(condition_expression) when only logs:PutMetricFilter is confirmed effective — alarm silence requires the attacker to correctly rewrite the filter expression to no longer match the events they generate. BLOCKED by an explicit deny on logs:DeleteMetricFilter or logs:PutMetricFilter. This edge is defense evasion / cover-tracks only — NOT escalation.

Narrative

{principal.name} can delete or overwrite a metric filter on {log_group.name} (logs:DeleteMetricFilter / logs:PutMetricFilter). The affected metric filter feeds a CloudWatch alarm; removing or corrupting the filter prevents the metric from incrementing, causing the alarm to remain silent even when the original condition occurs. This creates a detection blind spot - it does not grant access to any resource.

Raw rule rules/derived/aws/cloudwatch.yaml

id: aws-cloudwatch-tamper-metric-filter
emits: CanModifyConfiguration
description: logs:DeleteMetricFilter or logs:PutMetricFilter on a log group removes or corrupts the metric
  filter expression that feeds CloudWatch alarms, creating a detection blind spot.
match:
- - principal: null
  - HasPermission
  - log_group: null
where:
- node_type(?log_group) == LoggingService
- ?log_group.provider_type == 'AWS::Logs::LogGroup'
- ?log_group.metricFilterCount > 0
- ?principal has EFFECTIVE logs:DeleteMetricFilter OR logs:PutMetricFilter on ?log_group ARN
emit:
  source_type: Identity
  target_type: LoggingService
  source: ?principal
  target: ?log_group
  permissions:
  - logs:DeleteMetricFilter
  - logs:PutMetricFilter
  conditions:
  - iam_permission
  - scp_or_org_policy
  state_logic: "ACTIVE when logs:DeleteMetricFilter is confirmed EFFECTIVE on the log group ARN (identity-based\
    \ policy + no explicit deny + no SCP deny) \u2014 definitive alarm silence (filter gone, metric no\
    \ longer increments). CONDITIONAL(condition_expression) when only logs:PutMetricFilter is confirmed\
    \ effective \u2014 alarm silence requires the attacker to correctly rewrite the filter expression\
    \ to no longer match the events they generate. BLOCKED by an explicit deny on logs:DeleteMetricFilter\
    \ or logs:PutMetricFilter. This edge is defense evasion / cover-tracks only \u2014 NOT escalation."
  confidence: min(contributing_confidences) * 0.9
  derived_from:
  - ?principal HasPermission ?log_group (logs:DeleteMetricFilter / logs:PutMetricFilter effective permission)
  false_positive_note: "Two distinct actions with different state values: logs:DeleteMetricFilter (remove\
    \ filter entirely \u2192 definitive silence, ACTIVE) vs. logs:PutMetricFilter (overwrite expression\
    \ \u2192 conditional on the attacker correctly rewriting the filter to exclude their activity, emit\
    \ CONDITIONAL(condition_expression) if rewrite precision is unknown). A log group with NO metric filters\
    \ has no alarm surface to suppress (metricFilterCount check prevents zero-value emissions). This does\
    \ NOT delete the log group or its events; the historical log events remain and may be queried later.\
    \ This does NOT chain to CanExecuteAs or CanEnterAccount. Narrative MUST say \"creates a detection\
    \ blind spot\" not \"grants access.\" Consider splitting this rule into two (aws-cloudwatch- delete-metric-filter\
    \ \u2192 ACTIVE and aws-cloudwatch-overwrite-metric-filter \u2192 CONDITIONAL) if explicit state separation\
    \ is needed for downstream consumers."
  narrative: "{principal.name} can delete or overwrite a metric filter on {log_group.name} (logs:DeleteMetricFilter\
    \ / logs:PutMetricFilter). The affected metric filter feeds a CloudWatch alarm; removing or corrupting\
    \ the filter prevents the metric from incrementing, causing the alarm to remain silent even when the\
    \ original condition occurs. This creates a detection blind spot \u2014 it does not grant access to\
    \ any resource."
move · open · esc close