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 inventoryPrerequisites
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.
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: