> This page is for version Dev (default).
> For other versions, use one of these documentation indexes:
> - Dev (default): https://docs.nvidia.com/agent-policy-fabric/dev/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/agent-policy-fabric/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/agent-policy-fabric/_mcp/server.

# Content Controls

> Write control rules that inspect the content and context of every request, deny what must not leave the sandbox, and redact what may leave only in a safer form.

Content controls decide whether an already-permitted call holds up given what it carries. They are `PolicyRule` documents with `role: control`. Control rules apply to all traffic that reaches APF, MCP tool calls and plain HTTP requests alike. Their permits never grant a tool call. Their forbids deny any request they match.

## A First Example

The demo bundle's `security-content-gate` denies MCP tool calls that carry a secret, and replaces the body of plain HTTP requests that carry one:

```yaml
apiVersion: nvidia.agent-policy-fabric.policy/v1alpha1
kind: PolicyRule
metadata:
  name: security-content-gate
spec:
  description: Deny secret leakage, redact PII.
  language: cedar
  role: control
  definition: |
    @id("baseline-allow")
    permit (principal, action, resource);

    @apf_finding("secret-detected")
    @id("deny-secret")
    forbid (principal, action, resource)
    when { context.has_tool_name && context.annotations.secret_detected };

    @apf_effect("redact-wholesale")
    @id("redact-secret-rest")
    permit (principal, action, resource)
    when { !context.has_tool_name && context.annotations.secret_detected };

    @apf_effect("redact-pii-body")
    @id("redact-pii-rest")
    permit (principal, action, resource)
    when { !context.has_tool_name && context.annotations.pii_detected };
```

The rule reads two signals produced by annotators, `secret_detected` and `pii_detected`. Annotators do the inspection, Cedar decides, and effects carry out the rewrite.

## How a Control Rule Works

| Statement                    | Fires when             | Result                                                                                                                |
| ---------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `forbid` with `@apf_finding` | The condition matches. | The request is denied with reason `security-content-gate/deny-secret`, and the finding is recorded. Evaluation stops. |
| `permit` with `@apf_effect`  | The condition matches. | The request continues, and the effect is applied if the request is finally allowed.                                   |
| `permit` without annotations | The condition matches. | Nothing. A control permit does not grant a tool call.                                                                 |

## Building Blocks

### Detection Signals

Read annotations with `context.annotations.<name>`. Bool annotations are always present. Guard string and integer annotations with `has`:

```cedar
forbid (principal, action, resource)
when {
  context.annotations has secret_kind &&
  context.annotations.secret_kind == "aws_access_key"
};
```

Refer to [Annotators](/agent-policy-fabric/author-policies/annotators).

### Traffic Type

`context.has_tool_name` is `true` for MCP tool calls and `false` for plain HTTP requests. Use it to treat the two differently in one rule, as the demo does.

### Request Target

`context.target_host`, `context.target_method`, `context.target_path`, and `context.target_scheme` describe the destination. They are present when the request has a target, which is always the case on the OpenShell middleware path.

```cedar
@id("block-uploads")
forbid (principal, action, resource)
when {
  context has target_method &&
  context.target_method == "PUT" &&
  !context.has_tool_name
};
```

### Tool Arguments

`context.args` carries MCP tool arguments, with `_len` and `_num` fields derived for string arguments. Refer to [Capability Grants](/agent-policy-fabric/author-policies/capability-grants#constraining-arguments).

## Choosing a Response

| Goal                                    | Pattern                                                                                                                  |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Block the request                       | `forbid`, optionally with `@apf_finding` to record why.                                                                  |
| Let the request through in a safer form | `permit` with `@apf_effect` referencing a body replacement or patch.                                                     |
| Let the request through and flag it     | `permit` with `@apf_effect` referencing an effect whose `verdict` is `warn` or `escalate`, or with `@apf_finding` alone. |
| Record an observation only              | `permit` with `@apf_finding`. The decision is unchanged and the finding is recorded.                                     |

## Fail-Closed Rewrites

A rewrite that cannot be applied denies the request, because forwarding the original would drop a mandated change.

* A body patch requires a JSON body. A patch on a non-JSON body denies the request.
* A `replace` or `remove` patch on a path the body does not contain denies the request. The demo's `redact-pii-body` effect replaces `/text`, so a plain HTTP request that contains PII but has no top-level `text` field is denied.
* An effect that reads an annotation with `source` denies the request if that annotation was not produced.

Prefer a whole-body replacement when you cannot predict the body's shape.

## Scope

Content controls see the request only. APF binds the supervisor middleware's request operation, so it does not inspect responses or WebSocket messages, and it never sees credentials, which OpenShell injects after APF decides. Refer to [Enforcement Coverage and Limitations](/agent-policy-fabric/security/enforcement-coverage-and-limitations).

## Next Steps

* To write the detectors these rules read, refer to [Annotators](/agent-policy-fabric/author-policies/annotators).
* To define rewrites and findings, refer to [Effects and Findings](/agent-policy-fabric/author-policies/effects-and-findings).
* To try these controls end to end, refer to [Protect Sensitive Data](/agent-policy-fabric/tutorials/protect-sensitive-data).