Skip to navigation

Annotators

View as Markdown

Annotators handle the detection logic Cedar cannot express: regular expressions, substring and suffix checks, numeric comparisons, and lengths. Each PolicyAnnotator derives named values from the raw request before any rule runs. Rules read the values as context.annotations.<name>, and effects can copy them into headers or finding labels.

A First Example

This annotator flags credential-shaped text and labels its kind:

apiVersion: nvidia.agent-policy-fabric.policy/v1alpha1
kind: PolicyAnnotator
metadata:
name: secret-detector
spec:
description: Detects known credential shapes in request body text.
annotations:
- name: secret_detected
output: bool
set_true_if: any
rules:
- field: body_text
op: regex_match
pattern: 'ghp_[A-Za-z0-9]{36}'
- field: body_text
op: regex_match
pattern: 'AKIA[0-9A-Z]{16}'
- name: secret_kind
output: string
set_value_from: first_match
rules:
- field: body_text
op: regex_match
pattern: 'ghp_[A-Za-z0-9]{36}'
value: github_pat
- field: body_text
op: regex_match
pattern: 'AKIA[0-9A-Z]{16}'
value: aws_access_key

A rule can now write when { context.annotations.secret_detected }, and an effect can set a header from context.annotations.secret_kind.

Annotation Outputs

outputCombinatorPresent in contextValid operations
boolset_true_if: any (default) or allAlways. false when the rules do not match.Predicate operations.
stringset_value_from: first_matchOnly when a rule matched. The value is that rule’s value.Predicate operations. Every rule must set value.
integerFirst rule that produces a valueOnly when a rule produced a value.length and passthrough only.

Guard every string and integer annotation with has before you read it in Cedar:

when {
context.annotations has secret_kind &&
context.annotations.secret_kind == "github_pat"
}

Fields

Each rule reads one field of the request.

fieldReads
body_textThe request body as text. For an MCP call this is the whole JSON-RPC envelope, including the arguments.
body_jsonThe whole parsed JSON body.
body_json.<path>A value inside the JSON body. Use a dotted path such as body_json.params.name, or an RFC 6901 pointer such as body_json./params/name.
args.<name>An MCP tool argument.
target.host, target.path, target.methodThe request target.
user_subjectThe user subject the caller asserts. Empty on the OpenShell middleware path.

A field that does not exist in the request never matches.

Operations

opOperandMatches when
regex_matchpatternThe field is a string that matches the regular expression.
equalsvalueThe field is a string equal to value.
startswithprefixThe field is a string that starts with prefix.
endswithsuffixThe field is a string that ends with suffix.
containssubstringThe field is a string that contains substring.
gtethresholdThe field is an integer, or a string that parses as one, greater than or equal to threshold.
ltethresholdSame, less than or equal to threshold.
lengthnoneInteger output only. Produces the string’s length in characters.
passthroughnoneInteger output only. Produces the field’s integer value.

Patterns use the syntax of the Rust regex crate, which is similar to RE2. Lookaround and backreferences are not supported.

Producing Integers

Use an integer annotation to expose a number to Cedar, for example the length of a message:

- name: message_length
output: integer
rules:
- field: body_json.text
op: length
forbid (principal, action, resource)
when {
context.annotations has message_length &&
context.annotations.message_length > 4000
};

Load-Time Checks

The evaluator rejects an annotator, and with it the whole policy, when:

  • An annotation has no rules.
  • A rule is missing its operation’s operand, or its pattern does not compile.
  • An operation does not fit the output type, such as length on a bool annotation.
  • A string annotation’s rule has no value.
  • A field selector is not one of the supported forms.
  • Two annotations anywhere in the bundle share a name.

Writing Good Detectors

  • Keep detection and decision separate. An annotator should say what it saw, and a rule should decide what to do about it.
  • Use a bool annotation with set_true_if: any for a detection flag, and a string annotation with set_value_from: first_match for its classification. List the same patterns in both, in the same order.
  • Every annotation runs on every request APF evaluates. Keep patterns specific.
  • The repository’s demo bundle includes annotators/secret-detector.yaml and annotators/pii-detector.yaml as starting points.

Next Steps