From 90a99e2ff14d3b7483c7a8da8fc3f35e54d47029 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 00:50:04 +0000 Subject: [PATCH] Enhance privacy and security by adding permission checks and improving your awareness. This commit introduces several changes to improve data privacy and security: 1. **Sourcegraph Tool Permission:** I added a permission check to the Sourcegraph tool. You will now be prompted before any search query is sent to the Sourcegraph API, preventing inadvertent leakage of sensitive data that might be included in an LLM-generated query. 2. **File View/Search Permissions:** I added permission checks when viewing or searching file contents. You will be prompted before I read or search file contents, providing an explicit layer of consent. 3. **README Updates for Your Awareness:** * I added a section on "Local Data Storage and Privacy" to inform you that conversation history and file contents are stored locally in an unencrypted SQLite database, explaining the location and risks. * I added a note on "Automatic Context Files" to make you aware that certain files in your project root might be automatically sent to LLMs. * I added a "Tool Usage and Permissions" security note emphasizing the importance of reviewing permission prompts for powerful actions like running commands and fetching web content, and the risks of auto-approving sessions. These changes aim to give you more control over your data and make you more aware of the application's interactions with your file system and external services. --- README.md | 42 +++++++++++++++++++++++++++++++ internal/llm/agent/agent-tool.go | 18 +++++++------ internal/llm/agent/tools.go | 16 ++++++------ internal/llm/tools/grep.go | 32 ++++++++++++++++++++--- internal/llm/tools/sourcegraph.go | 32 +++++++++++++++++++++-- internal/llm/tools/view.go | 30 +++++++++++++++++++--- 6 files changed, 147 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c3e6a95a..7bc912c2 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,48 @@ paru -S opencode-ai-bin go install github.com/opencode-ai/opencode@latest ``` +## Important: Local Data Storage and Privacy + +Opencode is designed to run locally on your machine. To provide features like conversation history and session context, Opencode stores certain data on your computer: + +* **Conversation History:** All interactions, including your prompts, responses from the language model, and commands to/from tools, are saved. +* **File Contents:** When you use features that involve files (e.g., editing a file, referencing it in a prompt), the content of those files may be stored as part of your session data. + +**Storage Details:** + +* This data is stored in an **unencrypted SQLite database file** named `opencode.db`. +* The default location for this database is typically: + * `~/.opencode/opencode.db` (on macOS and Linux if `$XDG_CONFIG_HOME` is not set) + * `$XDG_CONFIG_HOME/opencode/opencode.db` (on Linux if `$XDG_CONFIG_HOME` is set) + +**Security Considerations:** + +* Because this data is stored locally and unencrypted, anyone with access to your user account on your computer could potentially access this information. +* We recommend following security best practices for your computer, such as: + * Using a strong account password. + * Enabling full-disk encryption (e.g., FileVault on macOS, BitLocker on Windows, or LUKS on Linux). + * Keeping your operating system and security software up to date. + +**Data Deletion:** + +* If you wish to remove all your conversation history and locally stored file content from Opencode, you can do so by manually deleting the `opencode.db` file from the directory specified above. Please ensure Opencode is not running when you do this. + +#### Automatic Context Files + +Please be aware that Opencode may automatically include content from certain files found in your project's root directory to provide better context to the language model. These files often include project-specific instructions or guidelines (e.g., `opencode.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, `.cursorrules`). + +If such files exist in your project and contain sensitive information that you do not wish to share with the configured language model, please review their content or avoid using these specific file names for sensitive data in your project root. You can see the list of default files checked in the `defaultContextPaths` variable within the application's source code (`internal/config/config.go`). + +### Tool Usage and Permissions + +Opencode leverages powerful tools to interact with your system, such as executing shell commands (`bash` tool), fetching content from URLs (`fetch` tool), and modifying files (`edit`, `write`, `patch` tools). While these tools enable complex tasks, they also require careful handling: + +* **Review Permission Prompts:** When a tool needs to perform a sensitive action (like writing a file or running a command), Opencode will ask for your permission. **It is crucial to carefully review the details of each permission request before approving it.** + * The language model generates the commands or parameters for these tools. Always verify that the action described in the prompt (e.g., the specific command to be run, the file to be changed, or the URL to be fetched) is exactly what you intend. + * Do not blindly approve requests, especially if they seem suspicious or unexpected. + +* **Auto-Approve Sessions:** Opencode has a feature to "auto-approve" all permission requests within a specific session. While this can streamline workflows, it bypasses the safety net of individual prompts. Use this feature with extreme caution and only in situations where you fully trust the sequence of operations. + ## Configuration OpenCode looks for configuration in the following locations: diff --git a/internal/llm/agent/agent-tool.go b/internal/llm/agent/agent-tool.go index 781720de..4e73405d 100644 --- a/internal/llm/agent/agent-tool.go +++ b/internal/llm/agent/agent-tool.go @@ -9,13 +9,15 @@ import ( "github.com/opencode-ai/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/message" + "github.com/opencode-ai/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/session" ) type agentTool struct { - sessions session.Service - messages message.Service - lspClients map[string]*lsp.Client + sessions session.Service + messages message.Service + lspClients map[string]*lsp.Client + permissions permission.Service } const ( @@ -54,7 +56,7 @@ func (b *agentTool) Run(ctx context.Context, call tools.ToolCall) (tools.ToolRes return tools.ToolResponse{}, fmt.Errorf("session_id and message_id are required") } - agent, err := NewAgent(config.AgentTask, b.sessions, b.messages, TaskAgentTools(b.lspClients)) + agent, err := NewAgent(config.AgentTask, b.sessions, b.messages, TaskAgentTools(b.permissions, b.lspClients)) if err != nil { return tools.ToolResponse{}, fmt.Errorf("error creating agent: %s", err) } @@ -100,10 +102,12 @@ func NewAgentTool( Sessions session.Service, Messages message.Service, LspClients map[string]*lsp.Client, + Permissions permission.Service, ) tools.BaseTool { return &agentTool{ - sessions: Sessions, - messages: Messages, - lspClients: LspClients, + sessions: Sessions, + messages: Messages, + lspClients: LspClients, + permissions: Permissions, } } diff --git a/internal/llm/agent/tools.go b/internal/llm/agent/tools.go index e6b0119a..877cf864 100644 --- a/internal/llm/agent/tools.go +++ b/internal/llm/agent/tools.go @@ -29,23 +29,23 @@ func CoderAgentTools( tools.NewEditTool(lspClients, permissions, history), tools.NewFetchTool(permissions), tools.NewGlobTool(), - tools.NewGrepTool(), + tools.NewGrepTool(permissions), tools.NewLsTool(), - tools.NewSourcegraphTool(), - tools.NewViewTool(lspClients), + tools.NewSourcegraphTool(permissions), + tools.NewViewTool(lspClients, permissions), tools.NewPatchTool(lspClients, permissions, history), tools.NewWriteTool(lspClients, permissions, history), - NewAgentTool(sessions, messages, lspClients), + NewAgentTool(sessions, messages, lspClients, permissions), }, otherTools..., ) } -func TaskAgentTools(lspClients map[string]*lsp.Client) []tools.BaseTool { +func TaskAgentTools(permissions permission.Service, lspClients map[string]*lsp.Client) []tools.BaseTool { return []tools.BaseTool{ tools.NewGlobTool(), - tools.NewGrepTool(), + tools.NewGrepTool(permissions), tools.NewLsTool(), - tools.NewSourcegraphTool(), - tools.NewViewTool(lspClients), + tools.NewSourcegraphTool(permissions), + tools.NewViewTool(lspClients, permissions), } } diff --git a/internal/llm/tools/grep.go b/internal/llm/tools/grep.go index f20d61ef..95106ac8 100644 --- a/internal/llm/tools/grep.go +++ b/internal/llm/tools/grep.go @@ -16,6 +16,7 @@ import ( "github.com/opencode-ai/opencode/internal/config" "github.com/opencode-ai/opencode/internal/fileutil" + "github.com/opencode-ai/opencode/internal/llm/permission" ) type GrepParams struct { @@ -37,7 +38,9 @@ type GrepResponseMetadata struct { Truncated bool `json:"truncated"` } -type grepTool struct{} +type grepTool struct { + permissions permission.Service +} const ( GrepToolName = "grep" @@ -79,8 +82,10 @@ TIPS: - Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.` ) -func NewGrepTool() BaseTool { - return &grepTool{} +func NewGrepTool(permissions permission.Service) BaseTool { + return &grepTool{ + permissions: permissions, + } } func (g *grepTool) Info() ToolInfo { @@ -142,6 +147,27 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) searchPath = config.WorkingDirectory() } + sessionID, messageID := GetContextValues(ctx) + if sessionID == "" || messageID == "" { + return ToolResponse{}, fmt.Errorf("session ID and message ID are required for this tool") + } + + p := g.permissions.Request( + permission.CreatePermissionRequest{ + SessionID: sessionID, + MessageID: messageID, + Path: searchPath, + ToolName: GrepToolName, + Action: "search_content", + Description: fmt.Sprintf("Search content in path '%s' for pattern: %s (include: %s)", searchPath, params.Pattern, params.Include), + Params: params, + }, + ) + + if !p { + return ToolResponse{}, permission.ErrorPermissionDenied + } + matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100) if err != nil { return ToolResponse{}, fmt.Errorf("error searching files: %w", err) diff --git a/internal/llm/tools/sourcegraph.go b/internal/llm/tools/sourcegraph.go index 0d38c975..7a688f6e 100644 --- a/internal/llm/tools/sourcegraph.go +++ b/internal/llm/tools/sourcegraph.go @@ -9,6 +9,9 @@ import ( "net/http" "strings" "time" + + "github.com/sourcegraph/sourcegraph/internal/llm/permission" + "github.com/sourcegraph/sourcegraph/internal/llm/internal/config" ) type SourcegraphParams struct { @@ -24,7 +27,8 @@ type SourcegraphResponseMetadata struct { } type sourcegraphTool struct { - client *http.Client + client *http.Client + permissions permission.Service } const ( @@ -125,11 +129,12 @@ TIPS: - Use type:file to find relevant files` ) -func NewSourcegraphTool() BaseTool { +func NewSourcegraphTool(permissions permission.Service) BaseTool { return &sourcegraphTool{ client: &http.Client{ Timeout: 30 * time.Second, }, + permissions: permissions, } } @@ -165,6 +170,29 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse, return NewTextErrorResponse("Failed to parse sourcegraph parameters: " + err.Error()), nil } + sessionID, messageID := GetContextValues(ctx) + if sessionID == "" || messageID == "" { + // This error handling might need adjustment if sourcegraph tool can operate without session/message IDs + // For now, assume they are required or find an alternative way to handle context for permissions. + return ToolResponse{}, fmt.Errorf("session ID and message ID are required for this tool") + } + + p := t.permissions.Request( + permission.CreatePermissionRequest{ + SessionID: sessionID, + MessageID: messageID, + Path: config.WorkingDirectory(), // Or a more relevant path if applicable + ToolName: SourcegraphToolName, + Action: "search", + Description: fmt.Sprintf("Search Sourcegraph for: %s", params.Query), + Params: params, // Sending the whole params struct + }, + ) + + if !p { + return ToolResponse{}, permission.ErrorPermissionDenied + } + if params.Query == "" { return NewTextErrorResponse("Query parameter is required"), nil } diff --git a/internal/llm/tools/view.go b/internal/llm/tools/view.go index 6d800ce6..71582e41 100644 --- a/internal/llm/tools/view.go +++ b/internal/llm/tools/view.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/opencode-ai/opencode/internal/config" + "github.com/opencode-ai/opencode/internal/llm/permission" "github.com/opencode-ai/opencode/internal/lsp" ) @@ -21,7 +22,8 @@ type ViewParams struct { } type viewTool struct { - lspClients map[string]*lsp.Client + lspClients map[string]*lsp.Client + permissions permission.Service } type ViewResponseMetadata struct { @@ -66,9 +68,10 @@ TIPS: - When viewing large files, use the offset parameter to read specific sections` ) -func NewViewTool(lspClients map[string]*lsp.Client) BaseTool { +func NewViewTool(lspClients map[string]*lsp.Client, permissions permission.Service) BaseTool { return &viewTool{ - lspClients, + lspClients: lspClients, + permissions: permissions, } } @@ -111,6 +114,27 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) filePath = filepath.Join(config.WorkingDirectory(), filePath) } + sessionID, messageID := GetContextValues(ctx) + if sessionID == "" || messageID == "" { + return ToolResponse{}, fmt.Errorf("session ID and message ID are required for this tool") + } + + p := v.permissions.Request( + permission.CreatePermissionRequest{ + SessionID: sessionID, + MessageID: messageID, + Path: filePath, + ToolName: ViewToolName, + Action: "read", + Description: fmt.Sprintf("Read file content: %s", filePath), + Params: params, + }, + ) + + if !p { + return ToolResponse{}, permission.ErrorPermissionDenied + } + // Check if file exists fileInfo, err := os.Stat(filePath) if err != nil {