Condition types: all 11 matchers used in GraphWarden rulesets

GraphWarden rulesets evaluate each Microsoft Graph request against an ordered list of conditions attached to rules. Conditions match on Azure AD object attributes, the calling app identity, the time of day, the caller IP, and the shape of the outbound response. This page documents all eleven condition types, their YAML shape, a matching example, and the edge cases that catch authors out. See the rule schema reference for the top-level YAML contract.

Condition types — full inventory

Prerequisites

- Familiarity with GraphWarden rulesets — see [Rulesets and rules](../concepts/rulesets-and-rules) - A YAML-aware text editor (the proxy parses YAML 1.2) - Read access to the [rule schema reference](./rule-schema) for the top-level `rules:` shape

Condition mechanics

A condition is a YAML object with a type field plus type-specific configuration fields. Conditions attach to a rule's conditions: list. Within a single rule, top-level conditions combine with AND semantics — every condition must evaluate true for the rule to match. For OR or NOT semantics, attach a logic_group key with any_of or none_of and a variants: list on the condition; see Combining conditions at the end of this page.

Each condition below is labelled as an object check (evaluates against the Graph response object — typically a user, group, or device), a request check (evaluates against the incoming HTTP request — caller identity, IP, time), or a response check (evaluates against the outgoing Graph response shape — result-set size).

group_membership

An object check that matches if the target Graph object belongs to a specified Azure AD security group. Membership is resolved via a Graph call and cached.

YAML shape

- type: group_membership
  group_id: "00000000-0000-0000-0000-000000000001"

Example — restrict a response filter to the HR security group

rules:
  - id: hr-group-only
    match:
      app_id: "*"
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: group_membership
        group_id: "00000000-0000-0000-0000-000000000001"
    response_filter:
      allow_properties:
        - id
        - displayName
        - mail
    action: filter

Notes

The condition issues one Graph call the first time it evaluates for a given object; the result is cached for 300 seconds. If the calling app identity lacks GroupMember.Read.All, the resolution returns an empty list and the condition evaluates false silently — confirm the scope is granted when matches do not fire as expected.

object_attribute

An object check that matches on a directory attribute of the target Graph object. Supports three operators: eq (exact match), contains (substring match), and regex (PCRE).

YAML shape

- type: object_attribute
  attribute: "userType"
  value: "Guest"
  operator: eq

Example — block guest access to the members endpoint

rules:
  - id: block-guests-from-members
    match:
      app_id: "*"
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: object_attribute
        attribute: "userType"
        value: "Guest"
        operator: eq
    action: block

Notes

attribute names a top-level property on the Graph object (for example, userType, department, jobTitle). Nested properties are reached with dot notation where supported by the response shape. The regex operator accepts PCRE; anchor the pattern explicitly (^...$) when you intend a full-string match.

domain

An object check that matches when the target object's primary email domain is in a configured list.

YAML shape

- type: domain
  domains:
    - contoso.com
    - fabrikam.com

Example — allow only corporate domains on user reads

rules:
  - id: corporate-domains-only
    match:
      app_id: "*"
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: domain
        domains:
          - contoso.com
    action: allow

Notes

Domain comparison is case-insensitive. The check reads the target object's mail or userPrincipalName domain — guest accounts with mismatched mail and userPrincipalName domains may match or miss depending on which the Graph payload carries. Log an evaluation sample before relying on this condition for sensitive routes.

custom_attribute

An object check that matches on a Directory Extension attribute (also called a custom attribute in the Azure AD portal).

YAML shape

- type: custom_attribute
  extension_name: "extension_abc123_employeeClass"
  value: "restricted"

Example — block reads on users flagged with a custom restriction

rules:
  - id: restricted-employees-block
    match:
      app_id: "*"
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: custom_attribute
        extension_name: "extension_abc123_employeeClass"
        value: "restricted"
    action: block

Notes

extension_name is the full Directory Extension property name as it appears in the Graph response — it includes the app registration GUID fragment (extension_<guid-without-hyphens>_<schemaAttr>). Copy the name from a Graph response body rather than re-deriving it from the schema extension definition.

For the exact extension-property naming rules, see the Microsoft Graph Directory Extensions documentation. This condition compares string-equal against the value returned by Graph; numeric or boolean extensions are compared as their string rendering.

administrative_unit

An object check that matches when the target object belongs to a specified Azure AD Administrative Unit.

YAML shape

- type: administrative_unit
  au_id: "11111111-2222-3333-4444-555555555555"

Example — restrict a rule to a regional Administrative Unit

rules:
  - id: eu-region-only
    match:
      app_id: "*"
      methods: ["GET", "PATCH"]
      endpoint_pattern: "/users*"
    conditions:
      - type: administrative_unit
        au_id: "11111111-2222-3333-4444-555555555555"
    action: allow

Notes

AU membership requires one Graph call to resolve; the result caches for 600 seconds (longer than group_membership because AU membership changes less often). The calling app identity must have AdministrativeUnit.Read.All or equivalent; without it, the condition returns false.

assigned_license

An object check that matches when the target user has an assigned licence SKU.

YAML shape

- type: assigned_license
  sku_id: "ENTERPRISEPACK"

Example — limit password-reset endpoint to E3-licensed users

rules:
  - id: e3-only-password-reset
    match:
      app_id: "*"
      methods: ["POST"]
      endpoint_pattern: "/users/*/authentication/passwordMethods*"
    conditions:
      - type: assigned_license
        sku_id: "ENTERPRISEPACK"
    action: allow

Notes

sku_id accepts either the human-readable SKU part number (ENTERPRISEPACK for E3) or the SKU UUID. The match reads the user's assignedLicenses array from Graph; disabled service plans within an assigned SKU still count the SKU as assigned. Use object_attribute against assignedPlans if you need a service-plan-level check.

caller_identity

A request check that matches on the GraphWarden App Identity making the request. Typically used to scope a rule to a specific calling application.

YAML shape

- type: caller_identity
  app_id: "f63c4abc-0000-0000-0000-000000000000"

Example — allow the HR app to read the full user list, block others

rules:
  - id: hr-app-full-read
    match:
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: caller_identity
        app_id: "f63c4abc-0000-0000-0000-000000000000"
    action: allow

  - id: others-block
    match:
      methods: ["GET"]
      endpoint_pattern: "/users*"
    action: block

Notes

The app_id field is optional — a caller_identity condition without an app_id matches when the request was authenticated by any registered App Identity (useful as a presence check). Per-app routing is more commonly expressed via the rule-level match.app_id field; reserve caller_identity conditions for cases where the app-identity check composes with other gating logic via logic_group.

time_window

A request check that matches when the request arrives within a configured time-of-day window.

YAML shape

- type: time_window
  start_time: "09:00"
  end_time: "17:00"
  utc_offset: "-05:00"

Example — allow destructive admin operations only during business hours

rules:
  - id: business-hours-only
    match:
      app_id: "*"
      methods: ["DELETE", "PATCH"]
      endpoint_pattern: "/users*"
    conditions:
      - type: time_window
        start_time: "09:00"
        end_time: "17:00"
        utc_offset: "-05:00"
    action: allow

Notes

start_time and end_time are 24-hour HH:MM strings. utc_offset accepts a signed ±HH:MM offset — use it to pin the window to a business-hours zone regardless of where the proxy host runs. A window that crosses midnight (for example, 22:00 to 06:00) is supported; the condition evaluates true if the current time is at or after start_time OR before end_time. Daylight-saving transitions track the host clock; pin utc_offset to the standard-time value if predictability across DST matters.

rate_limit

A request check that enforces a sliding-window request cap per authenticated identity.

YAML shape

- type: rate_limit
  max_requests: 100
  window_seconds: 60

Example — throttle batch requests to 10 per minute

rules:
  - id: batch-rate-cap
    match:
      methods: ["POST"]
      endpoint_pattern: "/$batch"
    conditions:
      - type: rate_limit
        max_requests: 10
        window_seconds: 60
    action: block

Notes

The rate counter is keyed on the calling App Identity. When max_requests is exceeded within window_seconds, the condition evaluates true — pair with action: block to deny the over-limit request. The rate window is sliding, not fixed: each request is counted against the trailing window_seconds from its arrival. A proxy restart resets the in-memory counters.

object_count_limit

A response check that matches when the Graph response result set exceeds a configured size.

YAML shape

- type: object_count_limit
  max_count: 100
  threshold_percent: 90

Example — block large enumeration responses

rules:
  - id: cap-user-list-size
    match:
      app_id: "*"
      methods: ["GET"]
      endpoint_pattern: "/users*"
    conditions:
      - type: object_count_limit
        max_count: 100
        threshold_percent: 90
    action: block

Notes

This condition evaluates after the Graph response arrives at the proxy, before the response is returned to the caller. max_count sets the hard cap; threshold_percent sets an early-warning level (log-only observation the proxy records when responses exceed the threshold but stay under the cap). Use this condition to catch accidental full-directory enumerations by narrowly scoped apps.

ip_range

A request check that matches when the caller IP falls inside one or more CIDR ranges.

YAML shape

- type: ip_range
  cidrs:
    - 10.0.0.0/8
    - 192.168.1.0/24

Example — allow admin endpoints only from corporate network ranges

rules:
  - id: admin-from-corp-net
    match:
      app_id: "*"
      methods: ["POST", "DELETE"]
      endpoint_pattern: "/directoryRoles*"
    conditions:
      - type: ip_range
        cidrs:
          - 10.0.0.0/8
          - 192.168.1.0/24
    action: allow

Notes

The proxy reads the caller IP from the TCP connection by default. When the proxy runs behind a load balancer or reverse proxy, configure the upstream to forward X-Forwarded-For and enable ForwardedHeaders on the proxy host; otherwise every caller IP resolves to the load-balancer address. IPv4 and IPv6 CIDR ranges are accepted; mix them freely in the cidrs list.

Combining conditions

Top-level conditions within a single rule combine with AND semantics — all conditions must evaluate true for the rule to match and its action to apply. Rules in a ruleset are evaluated top-down, first-match-wins: once a rule matches, evaluation stops and subsequent rules are not considered.

For OR or NOT semantics within a single condition, attach a logic_group key and a variants: list:

conditions:
  - type: group_membership
    logic_group: "any_of"
    variants:
      - group_id: "00000000-0000-0000-0000-000000000001"
      - group_id: "00000000-0000-0000-0000-000000000002"

  - type: object_attribute
    attribute: "department"
    logic_group: "none_of"
    variants:
      - value: "HR"
      - value: "Finance"

logic_group accepts three values: all_of (the default AND semantics), any_of (OR — the condition matches if any variant matches), and none_of (NOT — the condition matches if none of the variants match).

When no rule matches an incoming request, the proxy's default behaviour is allow (pass through to Graph). Operators who want default-deny place a catch-all rule at the end of the ruleset with match.methods: ["*"] and action: block.

Generate a condition for a GraphWarden rule

Help me write a GraphWarden condition that matches {describe the scenario — which users or requests should trigger, under what gating logic, and what the rule should do when matched}. Use the 11 condition types documented at /docs/api-reference/condition-types, pick the smallest set that expresses the intent, and return a complete rule block including `match:`, `conditions:`, and `action:`. If the intent needs OR or NOT semantics, use `logic_group` with `any_of` or `none_of`. Do not invent new condition type names; only the 11 documented types exist.

Reference: llms.txt

Troubleshooting

A condition referencing a group UUID silently does not match

The group_membership and administrative_unit conditions issue Graph calls to resolve transitive membership. If the calling App Identity lacks GroupMember.Read.All or AdministrativeUnit.Read.All, the resolution returns an empty list and the condition evaluates false with no error surfaced to the caller. Confirm the scope is granted on the App Identity; the proxy logs a warning when resolution returns zero groups for a user who should have some.

The regex operator on object_attribute does not match

The regex operator on object_attribute accepts PCRE. Anchor the pattern with ^ and $ when you intend a full-string match — an unanchored pattern is treated as a substring search by most PCRE engines, which matches more values than operators often expect. Test the pattern against a known Graph response value before deploying.

time_window matches off-by-one across daylight-saving transitions

The utc_offset field is a static signed offset, not a time-zone name. Pinning utc_offset to -04:00 holds the window at Eastern Daylight Time year-round, which is one hour off during Standard Time. If the business rule is "09:00 to 17:00 in New York all year," pin utc_offset to the standard-time offset (-05:00) and accept the one-hour shift during DST, or split the rule across two rulesets swapped on the transition dates.

ip_range matches every request to the load balancer IP

Behind a load balancer or reverse proxy, the TCP connection IP the proxy sees is the load-balancer IP, not the original caller. Configure the upstream to set X-Forwarded-For and enable forwarded-headers processing on the proxy host. Without that, every ip_range condition evaluates true or false uniformly based on the load-balancer address and not the real caller.

A rule with multiple conditions never matches even though each condition is correct alone

Top-level conditions combine with AND semantics. If the intent was OR, wrap the alternatives inside a single condition with logic_group: "any_of" and a variants: list rather than listing them as separate top-level conditions. The Admin app's What-If simulator shows which conditions evaluated true and which did not — use it to isolate the failing branch.

For the response-side transformations that apply when a rule matches with action: filter, see Transform types.

Last reviewed:

Last reviewed: .