feat(tools): add CapableTool interface and Capabilities() declarations

Introduce CapableTool extending the base Tool interface. Each tool may
optionally implement Capabilities() → ToolCapabilities to declare:
- Secrets it needs injected (name, env var, header or arg target)
- Network endpoints it may reach (SSRF allow-list)
- Filesystem path rules (read/write/deny, path prefix)
- Shell access level (none, denylist, allowlist, unrestricted)

Implementations:
- ExecTool: ShellDenylist + ReadWrite filesystem
- ReadFileTool: ReadOnly filesystem
- WriteFileTool: ReadWrite filesystem
- ListDirTool: ReadOnly filesystem
- WebSearchTool: brave_api_key secret injection (header), Brave API endpoint
- WebFetchTool: broad HTTP/HTTPS network access

ZeroCapabilities() and ExtractCapabilities() helpers provided so callers
can safely handle tools that do not implement CapableTool.

This capability metadata is consumed by the SecureBus (ITR) to enforce
policy, inject secrets, and scan output for leaks.
This commit is contained in:
ZanzyTHEbar 2026-02-19 12:12:29 +00:00
parent 9b8163caf9
commit 70614c8e54
4 changed files with 155 additions and 0 deletions

View file

@ -2,6 +2,104 @@ package tools
import "context" import "context"
// CapableTool is an optional interface for tools that require elevated
// capabilities: secrets, network endpoints, filesystem paths, or shell access.
//
// Tools that do not implement CapableTool receive zero-capability defaults:
// no secrets, no network, filesystem read-only to workspace, no shell.
// This is the correct default for safe, backward-compatible operation.
//
// The SecureBus reads Capabilities() before each execution, enforces the
// declared policy, injects secrets, and redacts output — all transparent to
// the tool's Execute method.
type CapableTool interface {
Tool
Capabilities() ToolCapabilities
}
// ToolCapabilities declares what a tool needs at runtime.
// All fields are zero-value safe (nil slice / zero enum = deny).
type ToolCapabilities struct {
// Secrets lists named secrets the tool requires. The SecureBus resolves
// each from the SecretStore and injects it per the InjectAs spec.
Secrets []SecretRef
// Network lists URL glob patterns the tool is permitted to contact.
// SSRF validation is still applied within permitted patterns.
Network []EndpointRule
// Filesystem lists path rules the tool may access, relative to workspace root.
Filesystem []PathRule
// Shell declares the shell access level for tools that execute subprocesses.
Shell ShellAccessLevel
}
// SecretRef identifies a named secret and how to deliver it to the tool.
type SecretRef struct {
// Name is the logical secret identifier (e.g. "github_token").
Name string
// InjectAs describes how to deliver the secret:
// "env:VAR_NAME" — set environment variable
// "arg:key" — add to args map under key
// "header:Header-Name" — add as HTTP header (web tools)
InjectAs string
// Required causes tool execution to fail if the secret is missing.
// When false, the tool executes with the secret omitted.
Required bool
}
// EndpointRule permits access to a URL matching the given glob pattern.
// Patterns follow filepath.Match syntax applied to the full URL string.
// Example: "https://api.github.com/**"
type EndpointRule struct {
Pattern string
}
// PathRule grants filesystem access to paths matching Pattern.
// Pattern is relative to the workspace root and uses filepath.Match syntax.
type PathRule struct {
// Pattern is the path glob, e.g. "data/**" or "config/*.toml".
Pattern string
// Mode is one of "r" (read-only), "w" (write-only), "rw" (read-write).
Mode string
}
// ShellAccessLevel controls subprocess execution for shell tools.
type ShellAccessLevel int
const (
// ShellNone disables all subprocess execution.
ShellNone ShellAccessLevel = iota
// ShellDenylist allows subprocess execution but blocks known-dangerous patterns
// (e.g. rm -rf /, /dev/mem access, raw socket creation). This is the default
// for tools that declare Shell access.
ShellDenylist
// ShellAllowlist restricts execution to an explicit permit list of command
// patterns. Any command not matching the list is rejected.
ShellAllowlist
)
// ZeroCapabilities returns the default deny-all capabilities applied to any
// tool that does not implement CapableTool.
func ZeroCapabilities() ToolCapabilities {
return ToolCapabilities{Shell: ShellNone}
}
// ExtractCapabilities returns a tool's declared capabilities, or ZeroCapabilities
// for tools that do not implement CapableTool.
func ExtractCapabilities(t Tool) ToolCapabilities {
if ct, ok := t.(CapableTool); ok {
return ct.Capabilities()
}
return ZeroCapabilities()
}
// Tool is the interface that all tools must implement. // Tool is the interface that all tools must implement.
type Tool interface { type Tool interface {
Name() string Name() string

View file

@ -148,6 +148,13 @@ func (t *ReadFileTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that ReadFileTool requires read-only filesystem access.
func (t *ReadFileTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Filesystem: []PathRule{{Pattern: "**", Mode: "r"}},
}
}
func (t *ReadFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *ReadFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
path, ok := args["path"].(string) path, ok := args["path"].(string)
if !ok { if !ok {
@ -201,6 +208,13 @@ func (t *WriteFileTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that WriteFileTool requires read-write filesystem access.
func (t *WriteFileTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Filesystem: []PathRule{{Pattern: "**", Mode: "rw"}},
}
}
func (t *WriteFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *WriteFileTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
path, ok := args["path"].(string) path, ok := args["path"].(string)
if !ok { if !ok {
@ -263,6 +277,13 @@ func (t *ListDirTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that ListDirTool requires read-only filesystem access.
func (t *ListDirTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Filesystem: []PathRule{{Pattern: "**", Mode: "r"}},
}
}
func (t *ListDirTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *ListDirTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
path, ok := args["path"].(string) path, ok := args["path"].(string)
if !ok { if !ok {

View file

@ -165,6 +165,18 @@ func (t *ExecTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that ExecTool requires shell access with denylist
// enforcement. This satisfies the CapableTool interface so the SecureBus
// applies the correct policy without restricting existing behavior.
func (t *ExecTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Shell: ShellDenylist,
Filesystem: []PathRule{
{Pattern: "**", Mode: "rw"},
},
}
}
func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
command, ok := args["command"].(string) command, ok := args["command"].(string)
if !ok { if !ok {

View file

@ -314,6 +314,19 @@ func (t *WebSearchTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that WebSearchTool requires network access.
// The Brave Search API key is injected as a header by the SecureBus.
func (t *WebSearchTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Secrets: []SecretRef{
{Name: "brave_api_key", InjectAs: "header:X-Subscription-Token", Required: false},
},
Network: []EndpointRule{
{Pattern: "https://api.search.brave.com/**"},
},
}
}
func (t *WebSearchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *WebSearchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
query, ok := args["query"].(string) query, ok := args["query"].(string)
if !ok { if !ok {
@ -377,6 +390,17 @@ func (t *WebFetchTool) Parameters() map[string]interface{} {
} }
} }
// Capabilities declares that WebFetchTool requires broad network access.
// SSRF validation is applied within the SecureBus before any request is made.
func (t *WebFetchTool) Capabilities() ToolCapabilities {
return ToolCapabilities{
Network: []EndpointRule{
{Pattern: "https://**"},
{Pattern: "http://**"},
},
}
}
func (t *WebFetchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { func (t *WebFetchTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
urlStr, ok := args["url"].(string) urlStr, ok := args["url"].(string)
if !ok { if !ok {