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.
Enable the feature
Section titled “Enable the feature”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"] }| Feature | Tools it provides |
|---|---|
tools | read_file, list_dir, search (regex + glob), edit_file, write_file, patch_file (unified diff), shell |
web-tools | all of the above, plus fetch_webpage and the fetch_text SSRF-guarded HTTP helper |
Register them with a policy
Section titled “Register them with a policy”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.
ToolPolicy fields
Section titled “ToolPolicy fields”| Field | Type | Effect |
|---|---|---|
workspace_root | Option<PathBuf> | Confines file reads/writes; relative paths resolve here; shell cwd. None = unconfined |
protected_paths | Vec<PathBuf> | Paths the file tools may never touch (secrets, host config) |
shell_timeout | Duration | Per-shell-call wall-clock cap; child killed on expiry |
web | WebPolicy | Network guardrails for the web tools |
How filesystem confinement works
Section titled “How filesystem confinement works”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.
WebPolicy and SSRF defense
Section titled “WebPolicy and SSRF defense”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_hostsis 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_urlsglob lists,default_format, anduser_agent.
Building further web tools
Section titled “Building further web tools”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?;Mixing built-in and custom tools
Section titled “Mixing built-in and custom tools”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()?;Layering approvals on top
Section titled “Layering approvals on top”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.
Key takeaways
Section titled “Key takeaways”- Enable
tools(fs + shell) orweb-tools(adds web fetch) — off by default. - Register the set with
register_all_tools_with_policy(Arc<ToolPolicy>). ToolPolicyinjects all constraints:workspace_root,protected_paths,shell_timeout, and aWebPolicy.- 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_rootoverregister_all_tools()for anything untrusted; layer approvals for sensitive calls.