Rule schema: the GraphWarden ruleset YAML format
GraphWarden rulesets are YAML documents with a top-level rules list. Each rule pairs a match block with optional conditions, an optional response_filter, and an action. This page documents the top-level shape and lists the eleven condition types and nine transform types by name; per-type deep dives are deferred to Phase 6 documentation.
Prerequisites
Top-level shape
A ruleset is a YAML document whose root key is rules — an ordered list of rule objects. The minimal ruleset below allows GET requests to /users and has no other rules.
rules:
- id: allow-user-read
match:
app_id: "*"
methods: ["GET"]
endpoint_pattern: "/users*"
action: allow
The canonical reference ruleset from the proxy's fixture library exercises every top-level key:
rules:
- id: hr-app-restrict-users
match:
app_id: "f63c4abc-..."
methods: ["GET"]
endpoint_pattern: "/users*"
conditions:
- type: group_membership
group_id: "00000000-0000-0000-0000-000000000001"
response_filter:
allow_properties:
- id
- displayName
- mail
- department
- manager:
- id
- displayName
action: filter
cache_ttl: 300
- id: block-sensitive-graph-paths
match:
app_id: "*"
methods: ["DELETE", "PATCH"]
endpoint_pattern: "/identityRiskDetections*"
action: block
Rule-level keys
Every rule object supports the following keys. match.methods, match.endpoint_pattern, and action are required; the rest are optional.
| Key | Type | Required | Purpose |
|---|---|---|---|
id |
string | No | Human- or UUID-style identifier. Used in audit events to identify which rule matched. |
match.app_id |
UUID or "*" |
No (default "*") |
The App Identity the rule applies to. "*" matches any calling app. |
match.methods |
list of strings | Yes | HTTP methods the rule considers (GET, POST, PUT, PATCH, DELETE). |
match.endpoint_pattern |
string | Yes | OData-style endpoint pattern. Supports * as a suffix wildcard (for example, /users*). |
conditions |
list of condition objects | No | Gating logic evaluated with AND semantics by default. See condition types below. |
response_filter |
object | No | Applied when action: filter. Contains allow_properties (whitelist) OR strip_properties (blacklist). |
action |
string | Yes | Final verdict: allow, filter, block, or log_only. |
cache_ttl |
integer (seconds) | No | How long to cache the rule-to-object match. 0 or absent means no caching. |
Action values
| Action | Behavior |
|---|---|
allow |
Return the Graph response unchanged. |
filter |
Apply response_filter transforms before returning. |
block |
Return HTTP 403, never call Graph. |
log_only |
Allow the request and emit an audit event. Useful for detection rules without enforcement. |
Condition types (11 total)
The proxy supports eleven condition types. Each condition is a YAML object keyed on type, with type-specific fields. Per-type documentation — every field, every Graph call, every cache behaviour — is deferred to Phase 6.
group_membership— match on Azure AD security-group membership of the target object.object_attribute— match on a directory attribute witheq,contains, orregexoperators.domain— match on the email domain of the target object.custom_attribute— match on a Directory Extension attribute.administrative_unit— match on Administrative Unit membership.assigned_license— match on assigned SKU licence.caller_identity— match on the calling App Identity.time_window— match on time-of-day windows with UTC offset support.rate_limit— enforce a sliding-window request rate per identity.object_count_limit— cap the size of the returned result set.ip_range— match on caller IP against one or more CIDR ranges.
See Phase 6 for per-type documentation — field inventories, Graph call impact, cache behaviour, and worked examples.
Condition composition
Top-level conditions within a single rule combine with AND semantics — all conditions must be true for the rule to match. For OR or NOT semantics, attach a logic_group key and a variants list to the condition:
conditions:
- type: group_membership
logic_group: "any_of"
variants:
- group_id: "group-1-uuid"
- group_id: "group-2-uuid"
- type: object_attribute
attribute: "department"
logic_group: "none_of"
variants:
- value: "HR"
- value: "Finance"
logic_group accepts any_of (OR), all_of (AND — the default behaviour), and none_of (NOT).
Transform types (9 total)
Transforms apply to response properties when action: filter matches. Each transform is identified by name; per-type documentation with input/output examples is deferred to Phase 6.
mask_start— hide the first N characters of a string.mask_end— hide the last N characters of a string.redact— replace the property value with a sentinel (for example,[redacted]).hash— replace the value with its SHA256 hex digest.truncate— trim a string to N characters.regex_replace— match a regex and substitute a template.domain_only— extract the domain portion of an email address.initials— extract initials from a display name.noise— append a random suffix to break aggregation.
See Phase 6 for per-transform documentation — input/output shapes, edge cases, and performance characteristics.
Response filter usage
Without transforms, a response filter operates on property whitelisting or blacklisting alone:
response_filter:
allow_properties:
- id
- displayName
- mail
- manager:
- id
- displayName
strip_properties:
- passwordHash
- onPremisesExtensionAttributes
allow_properties is a whitelist: only the listed properties pass through, and nested objects carry their own sub-list. strip_properties is a blacklist: the listed properties are removed, and all others pass through unchanged. A single response_filter uses one form or the other, not both.
Evaluation order
Rules in a ruleset are evaluated top-down, first-match-wins. Once a rule matches:
- The proxy evaluates the rule's
conditions(AND of all top-level conditions unlesslogic_groupoverrides). - If all conditions are true, the
actionis applied. - Evaluation stops — subsequent rules are not considered.
If no rule matches the incoming request, the proxy's default behaviour is allow (pass through to Graph). Operators who want default-deny place a catch-all rule with match.methods: ["*"] and action: block at the end of the ruleset.
Validating a ruleset
The GraphWarden Admin app validates a ruleset on save — schema errors surface immediately in the UI. For ad-hoc validation outside the Admin app, the proxy exposes a dry-run mode: load the ruleset file locally, start the proxy with Rules:FilePath pointing at it, and inspect the startup log. Parse errors surface as startup failures; logical issues (for example, a condition naming an unknown group UUID) surface on first request evaluation.
A dedicated graphwarden rules validate <file> CLI is deferred to Phase 6. For now, rely on the Admin app's editor validation or the startup-log signal.
Troubleshooting
The proxy refuses to start with "RuleLoader: invalid YAML"
The ruleset file has a YAML parse error — indentation mismatch, unterminated quotes, or tab characters where spaces are required. The log line names the offending file and line. Run the file through any YAML linter (for example, yamllint) to isolate the issue before restarting the proxy.
A rule evaluates as "no match" even though the endpoint looks right
Check match.endpoint_pattern for a leading slash and a trailing wildcard where needed. Graph paths like /users/{id} match /users*, but /users* does not match /users alone unless the pattern is written /users(/*)? or listed as two entries. The pattern is a prefix-plus-wildcard, not a regex.
A condition referencing a group UUID silently does not match
The group_membership condition issues a Graph call to resolve transitive membership. If the calling App Identity lacks the GroupMember.Read.All scope, the resolution returns an empty list and the condition evaluates false. Confirm the scope is granted; the proxy logs a warning when the resolution returns zero groups for a user who should have some.
A response_filter returns 500 at request time
The most common cause: allow_properties names a property that does not exist on the Graph response (for example, a misspelled manger instead of manager). The proxy treats the filter as malformed and fails the request rather than silently shipping an empty object. Verify each property name against the Graph response shape for the endpoint.
action: filter is set but the response is returned unchanged
The response filter is not applied when conditions on the rule evaluate false. Confirm that the rule actually matched: the audit event records the matched rule_id or null when no rule matched. A null rule_id means the default allow applied, not the filter-bearing rule.
For the HMAC-authenticated endpoints that ship rulesets from the control plane to the proxy, see the agent API reference.
Last reviewed: