Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .infer/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ tools:
- ls
- pwd
- echo
- cat
- head
- tail
- grep
- find
- wc
Expand All @@ -27,7 +24,10 @@ tools:
- ^kubectl get pods$
safety:
require_approval: true
exclude_paths:
- .infer/
- .infer/*
compact:
output_dir: .infer
chat:
default_model: deepseek/deepseek-chat
default_model: ""
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ tools:
- "^kubectl get pods$"
safety:
require_approval: true # Prompt user before executing any command
exclude_paths: # Paths excluded from tool access for security
- ".infer/" # Protect infer's own configuration directory
- ".infer/*" # Protect all files in infer's configuration directory
compact:
output_dir: ".infer" # Directory for compact command exports (default: project root/.infer)
chat:
Expand All @@ -177,6 +180,10 @@ chat:
- `enable`: Enable safety approval prompts
- `disable`: Disable safety approval prompts
- `status`: Show current safety approval status
- `exclude-path`: Manage excluded paths for security
- `list`: List all excluded paths
- `add <path>`: Add a path to the exclusion list
- `remove <path>`: Remove a path from the exclusion list
- `version`: Version information

## Dependencies
Expand Down Expand Up @@ -245,6 +252,11 @@ infer config tools exec "git status"
infer config tools safety enable
infer config tools safety disable
infer config tools safety status

# Manage excluded paths for security
infer config tools exclude-path list
infer config tools exclude-path add ".github/"
infer config tools exclude-path remove "test.txt"
```

## Code Style Guidelines
Expand Down
116 changes: 113 additions & 3 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,35 @@ var configToolsSafetyStatusCmd = &cobra.Command{
RunE: safetyStatus,
}

var configToolsExcludePathCmd = &cobra.Command{
Use: "exclude-path",
Short: "Manage excluded paths",
Long: `Manage paths that are excluded from tool access for security purposes.`,
}

var configToolsExcludePathListCmd = &cobra.Command{
Use: "list",
Short: "List excluded paths",
Long: `Display all paths that are excluded from tool access.`,
RunE: listExcludedPaths,
}

var configToolsExcludePathAddCmd = &cobra.Command{
Use: "add <path>",
Short: "Add a path to the exclusion list",
Long: `Add a path to the exclusion list to prevent tools from accessing it.`,
Args: cobra.ExactArgs(1),
RunE: addExcludedPath,
}

var configToolsExcludePathRemoveCmd = &cobra.Command{
Use: "remove <path>",
Short: "Remove a path from the exclusion list",
Long: `Remove a path from the exclusion list to allow tools to access it again.`,
Args: cobra.ExactArgs(1),
RunE: removeExcludedPath,
}

func setDefaultModel(modelName string) error {
cfg, err := config.LoadConfig("")
if err != nil {
Expand Down Expand Up @@ -158,11 +187,16 @@ func init() {
configToolsCmd.AddCommand(configToolsValidateCmd)
configToolsCmd.AddCommand(configToolsExecCmd)
configToolsCmd.AddCommand(configToolsSafetyCmd)
configToolsCmd.AddCommand(configToolsExcludePathCmd)

configToolsSafetyCmd.AddCommand(configToolsSafetyEnableCmd)
configToolsSafetyCmd.AddCommand(configToolsSafetyDisableCmd)
configToolsSafetyCmd.AddCommand(configToolsSafetyStatusCmd)

configToolsExcludePathCmd.AddCommand(configToolsExcludePathListCmd)
configToolsExcludePathCmd.AddCommand(configToolsExcludePathAddCmd)
configToolsExcludePathCmd.AddCommand(configToolsExcludePathRemoveCmd)

configInitCmd.Flags().Bool("overwrite", false, "Overwrite existing configuration file")
configToolsListCmd.Flags().StringP("format", "f", "text", "Output format (text, json)")
configToolsExecCmd.Flags().StringP("format", "f", "text", "Output format (text, json)")
Expand Down Expand Up @@ -208,9 +242,10 @@ func listTools(cmd *cobra.Command, args []string) error {

if format == "json" {
data := map[string]interface{}{
"enabled": cfg.Tools.Enabled,
"commands": cfg.Tools.Whitelist.Commands,
"patterns": cfg.Tools.Whitelist.Patterns,
"enabled": cfg.Tools.Enabled,
"commands": cfg.Tools.Whitelist.Commands,
"patterns": cfg.Tools.Whitelist.Patterns,
"exclude_paths": cfg.Tools.ExcludePaths,
"safety": map[string]bool{
"require_approval": cfg.Tools.Safety.RequireApproval,
},
Expand Down Expand Up @@ -240,6 +275,15 @@ func listTools(cmd *cobra.Command, args []string) error {
fmt.Printf(" • %s\n", pattern)
}

fmt.Printf("\nExcluded Paths (%d):\n", len(cfg.Tools.ExcludePaths))
if len(cfg.Tools.ExcludePaths) == 0 {
fmt.Printf(" • None\n")
} else {
for _, path := range cfg.Tools.ExcludePaths {
fmt.Printf(" • %s\n", path)
}
}

fmt.Printf("\nSafety Settings:\n")
if cfg.Tools.Safety.RequireApproval {
fmt.Printf(" • Approval required: %s\n", ui.FormatSuccess("Enabled"))
Expand Down Expand Up @@ -381,3 +425,69 @@ func getConfigPath() string {
}
return configPath
}

func listExcludedPaths(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadConfig("")
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

if len(cfg.Tools.ExcludePaths) == 0 {
fmt.Println("No paths are currently excluded.")
return nil
}

fmt.Printf("Excluded Paths (%d):\n", len(cfg.Tools.ExcludePaths))
for _, path := range cfg.Tools.ExcludePaths {
fmt.Printf(" • %s\n", path)
}

return nil
}

func addExcludedPath(cmd *cobra.Command, args []string) error {
pathToAdd := args[0]

_, err := loadAndUpdateConfig(func(c *config.Config) {
for _, existingPath := range c.Tools.ExcludePaths {
if existingPath == pathToAdd {
return
}
}
c.Tools.ExcludePaths = append(c.Tools.ExcludePaths, pathToAdd)
})
if err != nil {
return err
}

fmt.Printf("%s\n", ui.FormatSuccess(fmt.Sprintf("Added '%s' to excluded paths", pathToAdd)))
fmt.Printf("Tools will no longer be able to access this path\n")
return nil
}

func removeExcludedPath(cmd *cobra.Command, args []string) error {
pathToRemove := args[0]
var found bool

_, err := loadAndUpdateConfig(func(c *config.Config) {
for i, existingPath := range c.Tools.ExcludePaths {
if existingPath == pathToRemove {
c.Tools.ExcludePaths = append(c.Tools.ExcludePaths[:i], c.Tools.ExcludePaths[i+1:]...)
found = true
return
}
}
})
if err != nil {
return err
}

if !found {
fmt.Printf("%s\n", ui.FormatWarning(fmt.Sprintf("Path '%s' was not in the excluded paths list", pathToRemove)))
return nil
}

fmt.Printf("%s\n", ui.FormatSuccess(fmt.Sprintf("Removed '%s' from excluded paths", pathToRemove)))
fmt.Printf("Tools can now access this path again\n")
return nil
}
13 changes: 9 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ type OutputConfig struct {

// ToolsConfig contains tool execution settings
type ToolsConfig struct {
Enabled bool `yaml:"enabled"`
Whitelist ToolWhitelistConfig `yaml:"whitelist"`
Safety SafetyConfig `yaml:"safety"`
Enabled bool `yaml:"enabled"`
Whitelist ToolWhitelistConfig `yaml:"whitelist"`
Safety SafetyConfig `yaml:"safety"`
ExcludePaths []string `yaml:"exclude_paths"`
}

// ToolWhitelistConfig contains whitelisted commands and patterns
Expand Down Expand Up @@ -76,7 +77,7 @@ func DefaultConfig() *Config {
Enabled: true,
Whitelist: ToolWhitelistConfig{
Commands: []string{
"ls", "pwd", "echo", "cat", "head", "tail",
"ls", "pwd", "echo",
"grep", "find", "wc", "sort", "uniq",
},
Patterns: []string{
Expand All @@ -89,6 +90,10 @@ func DefaultConfig() *Config {
Safety: SafetyConfig{
RequireApproval: true,
},
ExcludePaths: []string{
".infer/",
".infer/*",
},
},
Compact: CompactConfig{
OutputDir: ".infer",
Expand Down
2 changes: 1 addition & 1 deletion internal/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (c *ServiceContainer) initializeDomainServices() {
c.config.Gateway.APIKey,
)

c.fileService = services.NewLocalFileService()
c.fileService = services.NewLocalFileService(c.config)

if c.config.Tools.Enabled {
c.toolService = services.NewLLMToolService(c.config, c.fileService)
Expand Down
49 changes: 48 additions & 1 deletion internal/services/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sort"
"strings"

"github.com/inference-gateway/cli/config"
"github.com/inference-gateway/cli/internal/domain"
)

Expand All @@ -18,11 +19,13 @@ type LocalFileService struct {
excludeExts map[string]bool
maxFileSize int64
maxDepth int
config *config.Config
}

// NewLocalFileService creates a new local file service
func NewLocalFileService() *LocalFileService {
func NewLocalFileService(cfg *config.Config) *LocalFileService {
return &LocalFileService{
config: cfg,
excludeDirs: map[string]bool{
".git": true,
".github": true,
Expand Down Expand Up @@ -207,6 +210,10 @@ func (s *LocalFileService) ValidateFile(path string) error {
return fmt.Errorf("file path cannot be empty")
}

if s.isPathExcluded(path) {
return fmt.Errorf("file is excluded: %s", path)
}

absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to resolve file path: %w", err)
Expand Down Expand Up @@ -237,6 +244,46 @@ func (s *LocalFileService) ValidateFile(path string) error {
return nil
}

// isPathExcluded checks if a file path should be excluded based on configuration
func (s *LocalFileService) isPathExcluded(path string) bool {
if s.config == nil {
return false
}

cleanPath := filepath.Clean(path)

normalizedPath := filepath.ToSlash(cleanPath)

for _, excludePattern := range s.config.Tools.ExcludePaths {
cleanPattern := filepath.Clean(excludePattern)
normalizedPattern := filepath.ToSlash(cleanPattern)

if normalizedPath == normalizedPattern {
return true
}

if strings.HasSuffix(normalizedPattern, "/*") {
dirPattern := strings.TrimSuffix(normalizedPattern, "/*")
if strings.HasPrefix(normalizedPath, dirPattern+"/") || normalizedPath == dirPattern {
return true
}
}

if strings.HasSuffix(normalizedPattern, "/") {
dirPattern := strings.TrimSuffix(normalizedPattern, "/")
if strings.HasPrefix(normalizedPath, dirPattern+"/") || normalizedPath == dirPattern {
return true
}
}

if strings.HasPrefix(normalizedPath, normalizedPattern) {
return true
}
}

return false
}

func (s *LocalFileService) GetFileInfo(path string) (domain.FileInfo, error) {
absPath, err := filepath.Abs(path)
if err != nil {
Expand Down
5 changes: 2 additions & 3 deletions internal/services/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,7 @@ func (s *LLMToolService) executeRead(filePath string, startLine, endLine int) (*
}

if err != nil {
result.Error = err.Error()
return result, nil
return nil, err
}

result.Content = content
Expand Down Expand Up @@ -308,7 +307,7 @@ func (s *LLMToolService) executeReadTool(args map[string]interface{}) (string, e

result, err := s.executeRead(filePath, startLine, endLine)
if err != nil {
return "", fmt.Errorf("file read failed: %w", err)
return "", err
}

if format == "json" {
Expand Down