Policies#
A policy is what an agent is allowed to do. It is a YAML or JSON document, created and updated like any other resource, and it is the single place the answer lives.
Default posture is deny. A sandbox with no policy can execute code and reach nothing.
Create a policy#
# agent-base.yaml
name: agent-base
description: Baseline egress and inference for production agents
networkDefaultVerdict: deny
networkDefaultTransport: upstream
allowedDomains:
- pattern: api.github.com
verdict: allow
transport: upstream
scheme: https
description: GitHub API
- pattern: registry.npmjs.org
verdict: allow
transport: direct
description: Package downloads
credentials:
- credentialName: github-token
envVarKey: GITHUB_TOKEN
managedInference:
enabled: true
provider: bedrock
curl -fsS -X POST \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/policies \
-d @agent-base.json
Create a policy called agent-base in the production project.
Deny by default. Allow api.github.com over upstream transport and
registry.npmjs.org direct. Attach the github-token credential as
GITHUB_TOKEN, and enable managed inference on bedrock.
nexusctl policy create --project production -f agent-base.yaml
-f - reads from stdin, which is what makes the edit loop work:
nexusctl policy get agent-base -o yaml > policy.yaml
$EDITOR policy.yaml
nexusctl policy update agent-base -f policy.yaml
Update replaces top-level fields
Each top-level field you supply replaces the existing value. allowedDomains is list-replace, not list-merge. Fields you omit are left alone, so the get-edit-update loop round-trips cleanly. Pass an explicit allowedDomains: [] to clear a list.
The policy document#
| Field | Purpose |
|---|---|
name |
Required. Unique within its scope. |
description |
Free text. |
networkDefaultVerdict |
allow or deny for unmatched domains. Use deny. |
networkDefaultTransport |
upstream or direct for unmatched domains when the default verdict allows. |
allowedDomains |
Ordered domain rules. First match wins. |
credentials |
Credentials to attach, by name. |
connectors |
Connector grants with explicit tool allowlists. |
integrations |
Clusters and AWS connections to enable. |
managedInference |
Opt in to platform-proxied model access. |
piiMasking |
PII masking for model calls. |
env |
Environment variables for sandboxes using this policy. |
Domain rules#
allowedDomains:
- pattern: "*.internal.example.com"
verdict: allow
transport: upstream
scheme: https
rules:
- method: GET
path: /v1/reports/*
- method: POST
path: /v1/reports/export
Wildcards are supported in pattern. rules narrow a domain to specific methods and path globs, which is how you express read-only access to an API rather than approximate it.
transport: upstream routes the call through the platform so credentials can be injected and the request fully audited. transport: direct allows it without interception — appropriate for high-volume, low-risk traffic such as package registries.
Connectors#
connectors:
- connectorId: 123e4567-e89b-12d3-a456-426614174000
allowedTools:
- jira__search_issues
- jira__get_issue
credentialId: 223e4567-e89b-12d3-a456-426614174000
allowedTools is required and has no wildcard. An empty array denies every tool in that reference. Tools an upstream adds later are not granted until you name them.
Integrations#
integrations:
- type: kubernetes
name: prod-eu
- type: aws-connection
name: production-aws
PII masking#
piiMasking:
types: [EMAIL, PHONE, CREDIT_CARD, PERSON]
unmaskResponses: true
failOpen: false
failOpen: false is the default and the compliance-safe choice: if masking fails, the request does not go out. See Privacy and PII controls for the full type list.
Scope and resolution#
Policies live at two levels.
| Scope | Created with | Role |
|---|---|---|
| Organization | --org |
Reusable across projects, and a ceiling on what projects may grant. |
| Project | --project |
Specific to one project's work. |
An agent's effective policy is the merge of every policy that reaches it, clipped by the org ceiling. A project binding that grants more than the org policy permits is clipped, not honoured.
Find those cases before they surprise someone:
nexusctl policy-binding list-drift --org acme
Read what actually applies to a running sandbox:
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID/effective-policy
Reaching agents#
Policies do not apply themselves.
- Sandboxes attach policies directly — they are their own principal, so there is nothing to bind. See Sandboxes.
- Users and API tokens get policies through a policy binding.
Inspecting#
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/policies
What does the agent-base policy in production actually permit?
Which sandboxes and bindings use it?
nexusctl policy list --project production
nexusctl policy describe agent-base --project production
describe renders allowed domains, connectors, credentials, integrations, PII masking, and current bindings as one view — the fastest way to answer "what does this policy actually permit".
Starting policy set#
| Policy | Scope | Contents |
|---|---|---|
org-ceiling |
Org | Everything the organization permits at all. Deny by default; no credentials. |
agent-base |
Org | Managed inference, package registries, documentation sites. |
<system>-readonly |
Project | One system, GET only, with its credential. |
<system>-write |
Project | The same system with write methods, bound to fewer subjects. |
Splitting read from write as separate policies makes a privilege grant a visible, reviewable change rather than a line edit inside a larger document.
Related#
- Policy bindings — attaching policies to users and tokens
- Credentials — the secrets policies attach
- Connections — the systems policies grant
- Audit trail — every allow and every deny