Skip to content

Built-in Tools & ToolPolicy

Kova ships production-ready filesystem, shell, and web tools so you don’t have to write them. They’re opt-in behind feature flags (to keep the core dependency-light) and guarded by an injectable ToolPolicy that confines what they can touch. This guide covers enabling, configuring, and securing them.

Two tiers, so you only compile what you need:

# Filesystem + shell tools (light dependencies)
kova-sdk = { version = "0.9", features = ["tools"] }
# The above PLUS web fetching (implies `tools`; pulls in an HTML/readability stack)
kova-sdk = { version = "0.9", features = ["web-tools"] }
FeatureTools it provides
toolsread_file, list_dir, search (regex + glob), edit_file, write_file, patch_file (unified diff), shell
web-toolsall of the above, plus fetch_webpage and the fetch_text SSRF-guarded HTTP helper

The tools never read your app’s configuration — every constraint is injected through a ToolPolicy. Build a policy, then register the whole set:

use std::sync::Arc;
use std::time::Duration;
use kova_sdk::tools::{ToolPolicy, WebPolicy, register_all_tools_with_policy};
let policy = Arc::new(ToolPolicy {
// File tools are confined here; relative paths resolve here; shell runs here.
workspace_root: Some("/srv/project".into()),
// Paths the file tools may never read or write, even inside the workspace.
protected_paths: vec!["/srv/project/.secrets".into()],
// Wall-clock cap per `shell` call; the child is killed on expiry.
shell_timeout: Duration::from_secs(60),
// Network guardrails (see below).
web: WebPolicy::default(),
});
let mut builder = AgentBuilder::new().provider(provider);
for tool in register_all_tools_with_policy(policy) {
builder = builder.tool(tool);
}
let agent = builder.build()?;

register_all_tools_with_policy returns exactly the tools your enabled features include — web tools appear only under web-tools.

FieldTypeEffect
workspace_rootOption<PathBuf>Confines file reads/writes; relative paths resolve here; shell cwd. None = unconfined
protected_pathsVec<PathBuf>Paths the file tools may never touch (secrets, host config)
shell_timeoutDurationPer-shell-call wall-clock cap; child killed on expiry
webWebPolicyNetwork guardrails for the web tools

When workspace_root is set, the file tools resolve every path argument and check it stays inside the root using symlink-aware containment (resolve_for_containment). A path that escapes via .. or a symlink fails the check and the tool returns an in-band error — the model can’t read /etc/passwd by asking for ../../etc/passwd.

The web tools (web-tools feature) defend against SSRF — an attacker (or a confused model) coaxing your server into fetching internal URLs.

let web = WebPolicy {
allow_private_hosts: false, // reject loopback/private/link-local/CGNAT addresses
https_only: true, // refuse plain http://
..WebPolicy::default()
};

WebPolicy::default() is safe out of the box:

  • Private-address rejection — hostnames resolving to loopback, private, link-local, or CGNAT ranges are refused unless allow_private_hosts is set.
  • DNS-rebinding defense — the HTTP client is pinned to the validated IP, so a hostname can’t resolve to a safe address during validation and a malicious one during the request.
  • Per-hop re-validation — redirects are followed manually with every guardrail re-checked on each hop.
  • Size caps — response byte/character limits keep a huge page from blowing up memory.
  • Plus allowed_urls / denied_urls glob lists, default_format, and user_agent.

fetch_text is exposed publicly so you can build your own network tools against trusted endpoints while inheriting the SSRF guard:

use kova_sdk::tools::fetch_text;
// Inside your own Tool::execute:
let body = fetch_text(&url, &web_policy).await?;

Built-in tools are ordinary Arc<dyn Tool> values, so they compose with your own:

let mut builder = AgentBuilder::new().provider(provider);
for tool in register_all_tools_with_policy(policy) {
builder = builder.tool(tool);
}
let agent = builder
.tool(Arc::new(MyCustomTool)) // your tool alongside the built-ins
.build()?;

Even with a policy, you may want a human to approve individual write_file or shell calls at runtime. Combine ToolPolicy (static confinement) with a ToolApprovalHandler (dynamic per-call gate) — see Human-in-the-Loop Approvals.

  • Enable tools (fs + shell) or web-tools (adds web fetch) — off by default.
  • Register the set with register_all_tools_with_policy(Arc<ToolPolicy>).
  • ToolPolicy injects all constraints: workspace_root, protected_paths, shell_timeout, and a WebPolicy.
  • The web tools guard against SSRF (private-IP rejection + DNS pinning + per-hop checks); the file tools confine paths against ../symlink escapes.
  • Prefer an explicit workspace_root over register_all_tools() for anything untrusted; layer approvals for sensitive calls.