Agent guard remote policies
Agent guard remote policies let authorized site members, including members of
a security team, 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; authorized site members can
publish the policy selected by its label.
This page covers Agent guard remote policies for labeled guard() actions
and tool calls. They are separate from Request Remote rules, which are site-scoped rules merged
with SDK request rules for HTTP requests handled by protect().
Remote policies complement, rather than replace, SDK rules configured in code. For the architecture, ownership trade-offs, and vendor examples, read Application-Native vs Remote Security Policies.
Choose application-native rules, remote policies, or both
Section titled “Choose application-native rules, remote policies, or both”An application-native rule is usually code-native: its semantics and change and release lifecycle are owned by the application, and it is authored and tested with the application. An SDK integration alone does not provide that policy model. An SDK can put enforcement inside an application while all policy remains configured in a remote control plane. Choose the authoring model based on who must change the rule and which lifecycle governs it.
| Application-native (code-native) SDK rules | Agent guard remote policies | |
|---|---|---|
| Primary owner | Application engineering | Authorized site members, often security or platform teams |
| Rule authoring | Application source code | Arcjet control plane |
| Change lifecycle | Code review, tests, build, and deployment | Publish without an application deployment |
| Best fit | Application-specific logic and engineering invariants | Independent policy changes and rapid response |
| Application responsibility | Configure the rule and enforce the decision | Define the label and typed inputs, then enforce the decision |
| Can be used together | Yes; submit SDK rules with the Guard call | Yes; the matching published policy is evaluated on that call |
Keep stable application invariants in code and put policy that security must change independently in the remote control plane. Do not duplicate a rule in both places unless one is an intentional defense-in-depth backstop with clear precedence and separate tests.
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”Today, LOCAL inputs are used by the sensitive-information rule, which operates
on a LOCAL STRING. Its raw value remains in SDK memory. The SDK evaluates the
downloaded policy projection locally and sends a domain-separated SHA-256
digest plus rule attestation. The attestation includes denied entity types and
offsets, not the matched raw values.
Supported rules
Section titled “Supported rules”Agent guard remote policies support the following rules:
| 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 aren’t 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 of the preceding rules. The
integration maps the current agent run and generated tool arguments into the
policy before the framework executes the tool. Framework wrappers such as
guardTool take that slug as action. Direct guard() calls take it as
label.
const sendEmail = guardTool(arcjet, sendEmailTool, { action: "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, action="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 versus policies
Section titled “Remote rules versus 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.