Agent guard remote policies
Agent guard remote policies let security teams change the policy applied to a
guarded action without changing the tool implementation. Developers own the
enforcement point and map application values into the policy contract;
security teams own the policy selected by its label.
Policy contract
Section titled “Policy contract”For each guarded action, security and development teams agree on:
- A stable
label, such asemail.sent. - Whether the policy requires an
actor. - Named inputs, with a type and
SERVERorLOCALexposure. - Rules over those values, each in
LIVEorDRY_RUNmode.
Every Guard call includes the label so Arcjet can apply the matching published policy.
label identifies the action boundary and selects its remote policy. Use a
hardcoded, past-tense action name such as email.sent or refund.issued.
Labels use lowercase letters, digits, dashes, and dots, must start and end with
a letter or digit, and are limited to 256 bytes.
actor is an optional opaque string asserted by trusted application code. It
can represent the authenticated user, service, or tenant responsible for the
action. The SDK does not derive it from a request or tool arguments and Arcjet
does not authenticate the value for your application.
Typed inputs
Section titled “Typed inputs”Every policy input is named and explicitly constructed. Plain values are rejected; the SDK does not inspect the tool schema or automatically discover arguments.
| Exposure | Type | JavaScript / TypeScript | Python | Example value |
|---|---|---|---|---|
SERVER | String | policyInput.server.string(value) | server_input.string(value) | "customer@example.com" |
SERVER | Boolean | policyInput.server.boolean(value) | server_input.boolean(value) | true |
SERVER | Integer | policyInput.server.integer(value) | server_input.integer(value) | 3 |
SERVER | Number | policyInput.server.number(value) | server_input.number(value) | 49.95 |
SERVER | String list | policyInput.server.stringList(value) | server_input.string_list(value) | ["a@example.com", "b@example.com"] |
LOCAL | String | policyInput.local.string(value) | local_input.string(value) | "Email body to inspect" |
For example, an email policy can receive a selected recipient, the trusted allow list to compare it with, and a message body that stays local:
inputs: { recipient: policyInput.server.string(recipient), allowed_recipients: policyInput.server.stringList(allowedRecipients), body: policyInput.local.string(body),}inputs={ "recipient": server_input.string(recipient), "allowed_recipients": server_input.string_list(allowed_recipients), "body": local_input.string(body),}Where inputs are evaluated
Section titled “Where inputs are evaluated”SERVER and LOCAL describe where policy evaluates an input and whether its
raw value is sent to Arcjet.
SERVER inputs
Section titled “SERVER inputs”The typed value is sent to Arcjet for server-side policy evaluation. A call containing only server inputs does not need to fetch a local policy projection. Use server inputs only for values Arcjet is allowed to receive.
LOCAL inputs
Section titled “LOCAL inputs”The raw string remains in SDK memory. The SDK evaluates the downloaded policy projection locally and sends a domain-separated SHA-256 digest plus rule attestation. For sensitive information detection, the attestation includes denied entity types and offsets, not the matched raw values.
Supported rules
Section titled “Supported rules”Agent guard remote policies currently support:
| Rule | Execution | What it evaluates |
|---|---|---|
| Allowed string values | Server | A string against an allow list using exact or email-domain matching |
| Denied string values | Server | A string against a deny list using exact or email-domain matching |
| String length | Server | Minimum and maximum UTF-8 byte length |
| String-list membership | Server | Whether one string is a member of a supplied string-list input |
| Prompt injection | Server | One named server string |
| Sensitive information | Local | One named local string with allowed or denied entity types |
Each rule can be LIVE, where a denial contributes to the final decision, or
DRY_RUN, where it is evaluated and reported without blocking.
Generic URL allow/deny rules, command or shell rules, remote custom rules, and remote rate limits are not currently supported. SDK rules configured in code remain available and can be submitted alongside a remote policy.
Policy examples
Section titled “Policy examples”Restrict a tool to approved recipients
Section titled “Restrict a tool to approved recipients”Configure two SERVER inputs—a string named recipient and a string list
named allowed_recipients—then add a string-list membership rule requiring the
recipient to appear in the list.
inputs: ({ recipient }) => ({ recipient: policyInput.server.string(recipient), allowed_recipients: policyInput.server.stringList(user.allowedRecipients),})inputs=lambda arguments, _config: { "recipient": server_input.string(arguments["recipient"]), "allowed_recipients": server_input.string_list(user.allowed_recipients),}Block sensitive data before it leaves the tool
Section titled “Block sensitive data before it leaves the tool”Configure a LOCAL string input named body, then add a sensitive information
rule with the entity types the tool must not send. The policy evaluates the raw
body locally.
inputs: ({ body }) => ({ body: policyInput.local.string(body),})inputs=lambda arguments, _config: { "body": local_input.string(arguments["body"]),}Check the instruction that triggered an action
Section titled “Check the instruction that triggered an action”Configure a SERVER string input named incoming_message, then add a prompt
injection rule over that input. Resolve the message from the application-owned
agent run rather than from a model-generated tool argument.
inputs: (toolInput) => ({ incoming_message: policyInput.server.string(incomingMessage),})inputs=lambda _arguments, _config: { "incoming_message": server_input.string(incoming_message),}Combine policies in an agent workflow
Section titled “Combine policies in an agent workflow”One email.sent policy can combine all three rules above. The integration maps
the current agent run and generated tool arguments into the policy before the
framework executes the tool:
const sendEmail = guardTool(arcjet, sendEmailTool, { label: "email.sent", actor: currentUser.id, inputs: ({ recipient, body }) => ({ recipient: policyInput.server.string(recipient), allowed_recipients: policyInput.server.stringList( currentUser.allowedRecipients, ), body: policyInput.local.string(body), incoming_message: policyInput.server.string(incomingMessage), }),});
const tools = { sendEmail };const result = await generateText({ model: "openai/gpt-4o-mini", prompt: incomingMessage, tools, toolsContext: aiToolsContext(createAgentContext(), tools),});guarded_send_email = guard_tool( guard=arcjet, tool=send_email_tool, label="email.sent", actor=current_user.id, inputs=lambda arguments, _config: { "recipient": server_input.string(arguments["recipient"]), "allowed_recipients": server_input.string_list( current_user.allowed_recipients ), "body": local_input.string(arguments["body"]), "incoming_message": server_input.string(incoming_message), },)
agent = create_agent( ChatOpenAI(model="gpt-4o-mini"), tools=[guarded_send_email],)result = await agent.ainvoke( {"messages": [{"role": "user", "content": incoming_message}]})The actor, allowed recipients, and incoming message come from application-owned
context. Only recipient and body come from the model’s validated tool call.
If any LIVE rule denies, the integration prevents sendEmailTool from
executing.
Remote rules vs. policies
Section titled “Remote rules vs. policies”Arcjet has two separate remote configuration systems:
| Agent guard remote policies | Request Remote rules | |
|---|---|---|
| SDK call | guard() | protect() |
| Scope | One action label | A site and its HTTP requests |
| Context | Trusted actor and explicit typed inputs | Request metadata and supported request signals |
| Configuration | Agent guard policy | Remote rules |
Request Remote rules protect HTTP requests. Agent guard remote policies protect
tool calls and other actions that use guard().
Policy updates and availability
Section titled “Policy updates and availability”When a usable policy is available, temporary connectivity issues do not cause the SDK to discard it.
If policy evaluation is incomplete or unavailable, the direct client returns an observable failed-open decision. Framework wrappers fail closed by default. See Availability and fail behavior.