shelltool

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package shelltool provides a shell command execution tool that can be registered with an agent. It mirrors the .NET LocalShellTool / LocalShellExecutor design: approval-in-the-loop is the default security boundary; an allow/deny Policy offers a best-effort pre-execution guardrail.

Security

Running agent-generated shell commands is inherently dangerous. This package provides two complementary controls:

  • Policy: an allow/deny list of regular expressions checked before commands reach the shell. The policy is a UX guardrail, NOT a security boundary — a determined model can trivially work around regex checks.

  • Approval-in-the-loop: NewLocal returns a Local that reports approval is required through tool.ApprovalRequiredTool, so the harness tool-approval middleware prompts a human before every execution. This is the primary security control. Pass LocalConfig.AcknowledgeUnsafe = true only when you have an independent isolation mechanism (e.g. a Docker container) and understand the risk.

Usage

t, err := shelltool.NewLocal(shelltool.LocalConfig{})
if err != nil {
	// handle invalid configuration
}
env := shelltool.NewEnvironmentProvider(t, shelltool.EnvironmentProviderConfig{})
cfg := agent.Config{
	Tools:            []tool.Tool{t},
	ContextProviders: []agent.ContextProvider{env},
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultShellEnvironmentInstructions

func DefaultShellEnvironmentInstructions(snapshot ShellEnvironmentSnapshot) string

DefaultShellEnvironmentInstructions renders shell environment guidance for an agent.

Types

type EnvironmentProvider

type EnvironmentProvider struct {
	// contains filtered or unexported fields
}

EnvironmentProvider probes a local shell and injects shell-specific instructions through an agent.ContextProvider.

func NewEnvironmentProvider

func NewEnvironmentProvider(executor Executor, config EnvironmentProviderConfig) *EnvironmentProvider

NewEnvironmentProvider creates a shell environment provider backed by executor.

func (*EnvironmentProvider) CurrentSnapshot

func (p *EnvironmentProvider) CurrentSnapshot() (ShellEnvironmentSnapshot, bool)

CurrentSnapshot returns the most recently captured snapshot, if one exists.

func (*EnvironmentProvider) Invoked

func (p *EnvironmentProvider) Invoked(ctx context.Context, invoked agent.InvokedContext) error

Invoked implements agent.ContextProvider by delegating to the wrapped provider. The wrapped provider is configured without a Store, so this is a no-op on success.

func (*EnvironmentProvider) Invoking

Invoking implements agent.ContextProvider by delegating to the wrapped provider, applying this provider's context/instructions to the invocation.

func (*EnvironmentProvider) Refresh

Refresh forces a re-probe and stores the new snapshot.

type EnvironmentProviderConfig

type EnvironmentProviderConfig struct {
	// SourceID identifies context injected by this provider.
	// Defaults to "shell_environment".
	SourceID string

	// ProbeTools lists CLI tools whose --version output should be probed.
	// Nil uses a small default list; an empty non-nil slice disables tool probes.
	ProbeTools []string

	// OverrideFamily forces the reported shell family when non-nil.
	OverrideFamily *ShellFamily

	// ProbeTimeout bounds each individual probe. Nil uses a 5 second default.
	// Negative values are invalid.
	ProbeTimeout *time.Duration

	// InstructionsFormatter renders a snapshot into agent instructions.
	// Defaults to [DefaultShellEnvironmentInstructions].
	InstructionsFormatter func(ShellEnvironmentSnapshot) string
}

EnvironmentProviderConfig configures an EnvironmentProvider.

type Executor

type Executor interface {
	Initialize(context.Context) error
	Run(context.Context, string) (Result, error)
}

Executor runs shell commands for environment probing.

type Local

type Local struct {
	// contains filtered or unexported fields
}

Local runs shell commands on behalf of an agent.

func NewLocal

func NewLocal(opts LocalConfig) (*Local, error)

NewLocal returns a local shell command tool for an agent.

func (*Local) ApprovalRequired

func (t *Local) ApprovalRequired() bool

ApprovalRequired reports whether calls should require human approval.

func (*Local) Call

func (t *Local) Call(ctx context.Context, args string) (any, error)

Call unmarshals the command argument, executes it via Run, and returns model-formatted output.

func (*Local) Close

func (t *Local) Close() error

Close terminates any persistent shell owned by the tool.

func (*Local) Description

func (t *Local) Description() string

Description returns the model-facing description of the shell tool.

func (*Local) Initialize

func (t *Local) Initialize(ctx context.Context) error

Initialize starts a persistent shell early; it is a no-op in stateless mode.

func (*Local) Name

func (t *Local) Name() string

Name returns the tool identifier (run_shell).

func (*Local) ReturnSchema

func (t *Local) ReturnSchema() any

ReturnSchema returns nil because the tool yields free-form model-formatted output.

func (*Local) Run

func (t *Local) Run(ctx context.Context, command string) (Result, error)

Run executes command and returns its raw shell result.

func (*Local) Schema

func (t *Local) Schema() any

Schema returns the JSON schema for the tool's command argument.

type LocalConfig

type LocalConfig struct {
	// Shell is an optional override for the shell binary path. When empty,
	// the AGENT_FRAMEWORK_SHELL environment variable is consulted; if that is
	// also unset, the OS default is used (/bin/bash on POSIX, pwsh/cmd on
	// Windows). Mutually exclusive with [LocalConfig.ShellArgv].
	Shell string

	// ShellArgv overrides the shell launch argv. The first element is the
	// shell binary; remaining elements are passed as a launch-time prefix
	// before the standard -c / -Command / persistent suffix. Mutually
	// exclusive with [LocalConfig.Shell].
	ShellArgv []string

	// Mode selects stateless-per-call or persistent-shell execution.
	// Defaults to [ModePersistent].
	Mode Mode

	// WorkingDirectory is the initial working directory for the shell.
	// When empty, defaults to the current process working directory.
	WorkingDirectory string

	// ConfineWorkingDirectory controls whether each persistent command is
	// prefixed with a cd/Set-Location back to [LocalConfig.WorkingDirectory].
	// The default is true.
	ConfineWorkingDirectory *bool

	// Environment contains extra environment variables for the spawned shell.
	// A nil value removes an inherited variable. A nil map means no overrides.
	Environment map[string]*string

	// CleanEnvironment starts the shell with only a small allowlist of
	// inherited variables (PATH, HOME, USER, USERNAME, USERPROFILE,
	// SystemRoot, TEMP, TMP) before applying [LocalConfig.Environment].
	CleanEnvironment bool

	// Timeout is the per-command deadline. Nil means no timeout. Negative values
	// are invalid. 30s is the recommended value.
	Timeout *time.Duration

	// MaxOutputBytes caps each captured output stream per command.
	// Defaults to 64 KiB. Output beyond this limit is
	// silently truncated and [Result.Truncated] is set.
	MaxOutputBytes int

	// Policy is an optional allow/deny filter checked before the command
	// reaches the shell. Nil means allow everything.
	Policy *Policy

	// AcknowledgeUnsafe opts out of the default approval-required gate.
	// When false (the default), the returned tool's ApprovalRequired method
	// reports true so the harness prompts a human before every execution. Set
	// this to true only when you have an independent isolation mechanism and
	// accept the risk.
	AcknowledgeUnsafe bool
}

LocalConfig configures the shell tool returned by NewLocal.

type Mode

type Mode int

Mode controls whether each call spawns a fresh shell or reuses a single long-lived shell process.

const (
	// ModePersistent keeps a single shell alive across calls so that
	// directory changes, exported variables, and history persist. A
	// persistent executor MUST NOT be shared across concurrent users or
	// agent sessions.
	ModePersistent Mode = iota
	// ModeStateless spawns a new shell process for every command. Safe to
	// share across concurrent calls; no state leaks between invocations.
	ModeStateless
)

type Policy

type Policy struct {
	// contains filtered or unexported fields
}

Policy is a layered allow/deny pattern filter for shell commands.

The regex filter is a UX guardrail, NOT a security boundary. It is intended to fast-fail commands operators would rather reject before execution while the primary isolation is approval-in-the-loop or container sandboxing.

A policy constructed with no patterns allows any non-empty command. Allow patterns are checked before deny patterns, so an allow match short-circuits evaluation and skips the deny list.

func NewPolicy

func NewPolicy(cfg PolicyConfig) (*Policy, error)

NewPolicy creates a Policy from cfg. Patterns are matched case-insensitively.

func (*Policy) Evaluate

func (p *Policy) Evaluate(request ShellRequest) (allowed bool, reason string)

Evaluate returns whether request may run and a human-readable reason when one applies. Evaluation order is: empty-command guard, allow patterns, deny patterns, default allow.

type PolicyConfig

type PolicyConfig struct {
	// DenyList contains patterns that trigger a deny outcome. Nil or empty
	// disables the deny list.
	DenyList []string

	// AllowList contains explicit-allow patterns. A match here short-circuits
	// the deny list.
	AllowList []string
}

PolicyConfig configures a Policy.

type Result

type Result struct {
	// Stdout is the captured standard output, possibly truncated.
	Stdout string
	// Stderr is the captured standard error, possibly truncated.
	Stderr string
	// ExitCode is the exit status reported by the process. -1 if the process
	// did not exit cleanly.
	ExitCode int
	// Duration is how long the command ran end-to-end.
	Duration time.Duration
	// Truncated is true when stdout or stderr was truncated.
	Truncated bool
	// TimedOut is true when the command was killed for exceeding the timeout.
	TimedOut bool
}

Result is the outcome of a single shell command invocation.

func (Result) FormatForModel

func (r Result) FormatForModel() string

FormatForModel returns a single text block combining stdout, stderr, status flags, and the exit code — suitable for returning to the language model.

type ShellEnvironmentSnapshot

type ShellEnvironmentSnapshot struct {
	Family           ShellFamily
	OSDescription    string
	ShellVersion     string
	WorkingDirectory string
	ToolVersions     map[string]ToolVersion
}

ShellEnvironmentSnapshot is a point-in-time view of the shell environment the agent is using.

type ShellFamily

type ShellFamily int

ShellFamily identifies the shell syntax family in use.

const (
	// ShellFamilyUnknown means no shell family has been selected or detected.
	ShellFamilyUnknown ShellFamily = iota
	// ShellFamilyPOSIX is a POSIX-style shell such as bash, sh, or zsh.
	ShellFamilyPOSIX
	// ShellFamilyPowerShell is PowerShell, including pwsh and Windows PowerShell.
	ShellFamilyPowerShell
)

func (ShellFamily) String

func (f ShellFamily) String() string

type ShellRequest

type ShellRequest struct {
	// Command is the full command line that the agent wants to run.
	Command string

	// WorkingDirectory is the optional working directory the command will
	// execute in, if known.
	WorkingDirectory string
}

ShellRequest is a shell command awaiting a policy decision.

type ToolVersion

type ToolVersion struct {
	Version string
	Found   bool
}

ToolVersion reports whether a CLI was found and, when available, the first non-empty line from its --version output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL