Intent boundary
A model may propose an action; the runtime decides whether it can happen
Tool calling converts generated output into potential side effects. The model can choose a tool name and produce arguments, but those values are untrusted input until the runtime parses, validates, and authorizes them. A secure agent keeps this decision outside the prompt so a persuasive model response cannot rewrite its own permissions.
UIntellAgent routes model-driven calls, manual TUI calls, and durable-run calls through one permission engine. The decision has three outcomes: allowed, denied with a reason, or confirmation required. The tool only executes after that decision is satisfied.
Chat, Tools, and Runs do not maintain separate allow lists that can drift or disagree about the same operation.
Read and write decisions use normalized paths, workspace roots, explicit allow patterns, and deny patterns for sensitive locations.
Shell commands check denied patterns first, then destructive behavior, mode, and the workspace command allow list.
The HTTP gateway uses a separate API secret and does not expose ready or chat operations anonymously.
A system prompt can guide behavior, but enforcement must occur in code after tool arguments are parsed and before the side effect begins.
Permission modes
Choose the smallest operating envelope that can complete the task
UIntellAgent loads ~/.uintell/permissions.toml and defaults to workspace mode. Modes establish the broad policy, while path, command, and host lists provide narrower controls.
| Mode | Allowed behavior | Use case |
|---|---|---|
| read-only | No shell access, file writes, network destinations outside policy, or database mutation. Allowed file reads still respect path rules. | Repository inspection, research, or memory review where the agent should not change local state. |
| workspace | Writes within configured workspace paths, shell commands from the allow list, allowed network hosts, and confirmed exceptional actions. | Normal coding work in a known project directory. This is the default mode. |
| full-access | Actions not explicitly denied are allowed, while configured destructive operations still require confirmation. | Trusted local administration where broad host access is necessary and the operator understands the increased risk. |
A deny rule is evaluated before a permissive mode. For example, denied read patterns protect SSH private keys and key files, while denied write roots cover system locations such as /etc, /boot,/sys, /proc, and /dev in the default configuration.
Tool decisions
Different side effects require different policy inputs
A single "tools enabled" switch cannot express the difference between reading Cargo.toml, overwriting a system file, searching the web, and deleting a graph fact. UIntellAgent maps each tool to the policy dimension that controls its risk.
| Tool family | Policy input | Example decision |
|---|---|---|
| terminal | Complete command string | Deny a known destructive pattern, confirm a destructive command, allow a listed prefix, or confirm an unlisted command. |
| file_read and search | Normalized path | Deny protected credentials even when another read root would otherwise allow the path. |
| file_write | Normalized destination | Allow a workspace path, deny a system path, or request confirmation for an unlisted location. |
| browser and search | Parsed network host | Allow configured providers and research hosts; request confirmation for an unknown destination. |
| graph tools | Database operation class | Permit reads, reject writes in read-only mode, and confirm destructive deletion. |
| code_exec | Code-execution command class | Authorize the tool through policy, then apply a separate operating-system sandbox. |
In the Tools workspace, :run <tool> <json> uses this normal decision flow.:run! does not disable policy; it supplies the explicit confirmation required for a call already classified as confirmation-required.
Code sandbox
Permission to execute code is not permission to access the whole host
UIntellAgent supports Python, Bash, Rust, and Node.js code execution. On Linux, the default execution path uses Bubblewrap to create an operating-system isolation boundary around the temporary program. The environment doctor executes advertised runtimes through that path so a missing or broken sandbox is visible before normal use.
Defense in depth
- The permission engine decides whether
code_execmay run at all. - Input is written to a unique temporary execution directory with language-specific filenames.
- Bubblewrap restricts the process view and writable paths according to the sandbox implementation.
- Stdout, stderr, timeout, and the real exit code are captured as the tool result.
- Temporary execution state is cleaned after the result is collected.
Set UINTELL_CODE_SANDBOX=1 to require the sandbox. TheUINTELL_ALLOW_UNSANDBOXED_CODE=1 escape hatch exists only for explicit local development. It should not be a production default because it removes the operating-system boundary around generated code.
export UINTELL_CODE_SANDBOX=1
unset UINTELL_ALLOW_UNSANDBOXED_CODE
./target/release/uintell-agent doctorHTTP gateway
Network access has its own authentication and abuse controls
The Axum gateway exposes GET /health without authentication for process monitoring. Provider readiness and model interaction use GET /ready and POST /chat, both protected byUINTELL_API_KEY through either X-API-Key or a Bearer token.
| Control | Current behavior | Purpose |
|---|---|---|
| Authentication | API key required for ready and chat. | Prevents anonymous provider use and conversation access. |
| Rate limit | 60 requests per minute per API key. | Bounds accidental loops and basic abuse. |
| Model timeout | 120 seconds. | Releases request resources when a provider does not complete. |
| Input limits | 32,000 characters per message; 128 per session ID. | Bounds allocation and stored in-memory session keys. |
| Request identity | Accept or generate X-Request-Id. | Connects client errors and logs to one request. |
| CORS | Explicit origins unless an override allows any origin. | Controls browser-based callers separately from API authentication. |
export UINTELL_API_KEY="replace-with-a-separate-secret"
./target/release/uintell-agent serve --addr 127.0.0.1:3000
curl http://127.0.0.1:3000/ready \
-H "X-API-Key: $UINTELL_API_KEY"Configuration
Make the policy reviewable before giving the agent an objective
The permissions file is ordinary TOML so it can be inspected, backed up, and tailored to a workspace. Start with workspace mode, remove commands and hosts that are not required, and keep destructive confirmation enabled.
mode = "workspace"
workspace_dirs = ["/home/me/project"]
allowed_commands = ["rg", "git status", "git diff", "cargo check", "cargo test"]
denied_commands = ["rm -rf /", "mkfs", "shutdown", "reboot"]
allowed_read_paths = ["/home/me/project"]
denied_read_paths = [".ssh", "*.pem", "*.key", "id_ed25519"]
allowed_write_paths = ["/home/me/project", "/tmp"]
denied_write_paths = ["/etc", "/boot", "/sys", "/proc", "/dev"]
allowed_hosts = ["api.deepseek.com", "github.com", "crates.io"]
denied_hosts = []
confirm_destructive = trueRun uintell-agent doctor after changing policy. Then inspect the Tools workspace to confirm the available catalog and use a harmless manual call before starting autonomous work. A strong permission model is one an operator can explain, not merely one with many configuration fields.