aws-secretsmanager-resource-policy-self-grant

PutResourcePolicy self-grants GetSecretValue on a secret that is credentials for a more-privileged identity - escalation.

derived aws emits CanEscalateTo

match

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

{'principal': None} CanModifyPolicy {'secret': None} {'secret': None} CredentialsFor {'identity': None}

where

node_class(?secret) == Secret node_class(?identity) == Identity ?principal has EFFECTIVE secretsmanager:PutResourcePolicy on ?secret privilege(?identity) > privilege(?principal)

emit

source typeIdentity
target typeIdentity
source?principal
target?identity
permissionssecretsmanager:PutResourcePolicy secretsmanager:GetSecretValue kms:Decrypt
conditionsiam_permission key_permission
state logicif ?secret is wrapped by the AWS-managed key (self-granted GetSecretValue then decrypts transparently same-account): ACTIVE; if ?secret is CMK-wrapped: CONDITIONAL(key_permission) — the self-grant only rewrites the SECRET resource policy, NOT the KMS key policy, so a customer CMK still blocks plaintext unless the principal separately holds kms:Decrypt. BLOCKED if an SCP denies secretsmanager:PutResourcePolicy.

Narrative

{principal.name} can rewrite the resource policy of {secret.name} (secretsmanager:PutResourcePolicy) to self-grant GetSecretValue; the secret is credentials for the more-privileged {identity.name}, so this escalates to {identity.name} (CMK secrets still need kms:Decrypt).

Raw rule rules/derived/aws/secretsmanager.yaml

id: aws-secretsmanager-resource-policy-self-grant
emits: CanEscalateTo
description: "PutResourcePolicy self-grants GetSecretValue on a secret that is credentials for a more-privileged\
  \ identity \u2014 escalation."
match:
- - principal: null
  - CanModifyPolicy
  - secret: null
- - secret: null
  - CredentialsFor
  - identity: null
where:
- node_class(?secret) == Secret
- node_class(?identity) == Identity
- ?principal has EFFECTIVE secretsmanager:PutResourcePolicy on ?secret
- privilege(?identity) > privilege(?principal)
emit:
  source_type: Identity
  target_type: Identity
  source: ?principal
  target: ?identity
  permissions:
  - secretsmanager:PutResourcePolicy
  - secretsmanager:GetSecretValue
  - kms:Decrypt
  conditions:
  - iam_permission
  - key_permission
  state_logic: "if ?secret is wrapped by the AWS-managed key (self-granted GetSecretValue then decrypts\
    \ transparently same-account): ACTIVE; if ?secret is CMK-wrapped: CONDITIONAL(key_permission) \u2014\
    \ the self-grant only rewrites the SECRET resource policy, NOT the KMS key policy, so a customer CMK\
    \ still blocks plaintext unless the principal separately holds kms:Decrypt. BLOCKED if an SCP denies\
    \ secretsmanager:PutResourcePolicy."
  confidence: min(contributing_confidences) * 0.9
  derived_from:
  - <CanModifyPolicy(PutResourcePolicy) edge_id>
  - <CredentialsFor edge_id>
  false_positive_note: 'The self-grant only touches the SECRET''s resource policy, not the wrapping CMK''s
    key policy: self-granting GetSecretValue does NOT bypass kms:Decrypt on a customer CMK -> CONDITIONAL(key_permission),
    never assume ACTIVE for a CMK secret. AWS BlockPublicPolicy blocks a broadly public policy unless
    explicitly disabled. Emit ONLY when the secret is evidenced CredentialsFor a STRICTLY more-privileged
    identity; a self-grant on a secret with no CredentialsFor, or for a lateral identity, is access not
    escalation. Dedup against the generic read-secret-yields-identity -> impersonate-is-escalation collapse
    by (type,source,target,scope).'
  narrative: '{principal.name} can rewrite the resource policy of {secret.name} (secretsmanager:PutResourcePolicy)
    to self-grant GetSecretValue; the secret is credentials for the more-privileged {identity.name}, so
    this escalates to {identity.name} (CMK secrets still need kms:Decrypt).'
move · open · esc close