Skip to content

Add session-level auto-approve for AI file read operations#3101

Open
mits-pl wants to merge 4 commits intowavetermdev:mainfrom
mits-pl:feature/session-read-auto-approve
Open

Add session-level auto-approve for AI file read operations#3101
mits-pl wants to merge 4 commits intowavetermdev:mainfrom
mits-pl:feature/session-read-auto-approve

Conversation

@mits-pl
Copy link

@mits-pl mits-pl commented Mar 21, 2026

Summary

  • Adds an "Allow reading in this session" button to the AI tool approval UI alongside Approve/Deny
  • When clicked, all subsequent file reads from the same directory (and subdirectories) are auto-approved for the rest of the session
  • Session approvals are in-memory only — they reset on app restart for security

Changes

  • New file: pkg/aiusechat/sessionapproval.go — thread-safe in-memory registry for session-approved paths
  • Backend: tools_readfile.go / tools_readdir.goToolApproval checks session allowlist before requiring manual approval
  • RPC: New WaveAISessionReadApproveCommand to register approved paths from frontend
  • Frontend: "Allow reading in this session" button in both single and batch approval views

Security

  • Hardcoded sensitive file blocks (.ssh, .aws, credentials) in isBlockedFile() are enforced via ToolVerifyInput before ToolApproval — session approval cannot bypass them
  • Approvals are in-memory only, never persisted to disk
  • Approvals clear on app restart

Test plan

  • Open AI panel, ask AI to read a file → see Approve / Allow reading in this session / Deny
  • Click "Allow reading in this session" → current read is approved
  • Ask AI to read another file from the same directory → auto-approved without prompt
  • Ask AI to read a file from a different directory → still requires approval
  • Restart app → session approvals are cleared, approval required again
  • Verify sensitive files (e.g. ~/.ssh/id_rsa) are still blocked even with session approval

🤖 Generated with Claude Code

Adds an "Allow reading in this session" button to the AI tool approval UI.
When clicked, all subsequent file reads from the same directory are
auto-approved for the rest of the session (in-memory only, resets on restart).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

Walkthrough

Adds a session-scoped read-approval flow for AI tool reads. Frontend: extracts directory targets from tool descriptions, conditionally shows an “Allow reading in this session” button for read operations, and calls WaveAIModel.sessionReadApprove. Backend: introduces an in-memory SessionApprovalRegistry with home-dir expansion, canonicalization, and sensitive-path checks; adds RPC types, server handler, and client wrappers for waveaisessionreadapprove; and updates read_text_file and read_dir tool approval logic to consult session approvals before requiring explicit approval.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature: adding session-level auto-approval for AI file read operations, matching the core functionality across all changed files.
Description check ✅ Passed The description clearly relates to the changeset, detailing the new session-approval feature, backend/frontend changes, security considerations, and test plan that align with the file modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
frontend/app/aipanel/aitooluse.tsx (1)

205-212: Apply session approvals before sending final approve action.

At Line 211 and Line 291, handleApprove() runs immediately after firing session-approval RPCs. If those calls are delayed/fail, subsequent reads may still prompt unexpectedly despite the user choosing session allow.

💡 Proposed fix
-    const handleAllowSession = () => {
+    const handleAllowSession = async () => {
         const dirs = extractDirsFromParts(parts);
         const model = WaveAIModel.getInstance();
-        for (const dir of dirs) {
-            model.sessionReadApprove(dir);
-        }
+        await Promise.allSettled(dirs.map((dir) => model.sessionReadApprove(dir)));
         handleApprove();
     };
@@
-    const handleAllowSession = () => {
+    const handleAllowSession = async () => {
         const dir = extractDirFromToolDesc(toolData.tooldesc);
         if (dir) {
-            WaveAIModel.getInstance().sessionReadApprove(dir);
+            await WaveAIModel.getInstance().sessionReadApprove(dir);
         }
         handleApprove();
     };

Also applies to: 286-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/aipanel/aitooluse.tsx` around lines 205 - 212, The handler
handleAllowSession calls WaveAIModel.getInstance().sessionReadApprove(dir) in a
loop then immediately calls handleApprove(), which can race with async RPCs;
make handleAllowSession async, collect the promises returned by
sessionReadApprove (via extractDirsFromParts(parts)), await
Promise.all(promises) and only then call handleApprove(); apply the same change
to the other similar handler that performs sessionReadApprove before
handleApprove so approvals complete before sending the final approve action.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/aipanel/aitooluse.tsx`:
- Around line 80-94: The parent-directory extraction in extractDirFromToolDesc
only checks for '/'; change it to consider both '/' and backslash '\\' by
computing the last separator as the max of filePath.lastIndexOf('/') and
filePath.lastIndexOf('\\') and using that index to derive the parent directory
(preserving the existing special-case behavior for root/leading slash). Update
the logic around lastSlash to use this combined separator index so Windows paths
like "C:\..." return the correct parent directory.

In `@pkg/aiusechat/tools_readdir.go`:
- Around line 168-180: ToolApproval's auto-approval path for read_dir (in the
ToolApproval function that calls parseReadDirInput, wavebase.ExpandHomeDir and
IsSessionReadApproved) lacks the blocked-path guard used by read_text_file, so
session approvals can let protected dirs (e.g. ~/.ssh, ~/.aws or their parent
roots) be enumerated; modify ToolApproval to run the same blocked-path check
used by read_text_file (the verifier that enforces forbidden roots/paths) after
expanding the path and before returning ApprovalAutoApproved, and ensure the
check treats directory roots and parent paths as blocked (not just exact file
matches) so any path under a forbidden root is rejected and returns
ApprovalNeedsApproval.

In `@pkg/aiusechat/tools_readfile.go`:
- Around line 405-417: The current ToolApproval flow auto-approves files in
session-approved parent directories but isBlockedFile() only blocks a few exact
filenames and key-like names, so files under sensitive dirs (e.g., ~/.ssh/*,
~/.aws/*) still get auto-approved; update the isBlockedFile helper to reject
paths that have those directory prefixes (after expanding/home-normalizing the
path) so files like ~/.ssh/config and ~/.aws/config are denied even if their
parent dir is session-approved—locate isBlockedFile and ensure it checks
normalized directory prefixes (e.g., ".ssh", ".ssh/", ".aws", ".aws/") and
returns blocked before ToolApproval/IsSessionReadApproved is consulted, using
the same normalization logic as parseReadTextFileInput/wavebase.ExpandHomeDir.

---

Nitpick comments:
In `@frontend/app/aipanel/aitooluse.tsx`:
- Around line 205-212: The handler handleAllowSession calls
WaveAIModel.getInstance().sessionReadApprove(dir) in a loop then immediately
calls handleApprove(), which can race with async RPCs; make handleAllowSession
async, collect the promises returned by sessionReadApprove (via
extractDirsFromParts(parts)), await Promise.all(promises) and only then call
handleApprove(); apply the same change to the other similar handler that
performs sessionReadApprove before handleApprove so approvals complete before
sending the final approve action.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1bb03a68-f057-48d5-aaf5-c5eb0e07b77b

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc4dc3 and 20de8a8.

📒 Files selected for processing (10)
  • frontend/app/aipanel/aitooluse.tsx
  • frontend/app/aipanel/waveai-model.tsx
  • frontend/app/store/wshclientapi.ts
  • frontend/types/gotypes.d.ts
  • pkg/aiusechat/sessionapproval.go
  • pkg/aiusechat/tools_readdir.go
  • pkg/aiusechat/tools_readfile.go
  • pkg/wshrpc/wshclient/wshclient.go
  • pkg/wshrpc/wshrpctypes.go
  • pkg/wshrpc/wshserver/wshserver.go

programista-wordpress and others added 2 commits March 21, 2026 22:26
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sensitive paths (~/.ssh, ~/.aws, ~/.gnupg, ~/.kube, ~/.password-store,
~/.secrets, keychains, Windows credentials) are now excluded from
session auto-approval. Even if a parent directory like ~ is session-approved,
reads from these directories still require manual approval.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/aiusechat/sessionapproval.go`:
- Around line 29-42: The stored approved path must be fully canonicalized
(absolute + symlink-resolved and normalized case on Windows) before inserting
into globalSessionApproval.approvedPaths and candidate paths must be
canonicalized before any prefix checks; update AddSessionReadApproval to call
filepath.Abs then filepath.EvalSymlinks (fall back to the Abs/cleaned value only
if EvalSymlinks returns an error), normalize case on Windows (e.g.,
strings.ToLower), ensure a trailing filepath.Separator, and store that canonical
string in globalSessionApproval.approvedPaths; also update the code that checks
approvals (the code paths invoked by WaveAISessionReadApproveCommand) to apply
the same canonicalization to the requested path before comparing prefixes and
handle canonicalization errors by denying approval or returning a clear error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4cb7cfea-2255-44b5-93eb-6d605216c446

📥 Commits

Reviewing files that changed from the base of the PR and between 03ae5bd and 0ab2969.

📒 Files selected for processing (1)
  • pkg/aiusechat/sessionapproval.go

Use filepath.EvalSymlinks() on both stored approval roots and candidate
paths to prevent symlink-based bypass of session approval boundaries.
Also reject sensitive directories at registration time in AddSessionReadApproval.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
pkg/aiusechat/sessionapproval.go (1)

62-99: ⚠️ Potential issue | 🟠 Major

Normalize both sides before sensitive/prefix checks (case + canonical roots).

Line 67/Line 83 and Line 115 compare path strings directly, but only the candidate path is canonicalized. Sensitive roots and approval roots are not normalized to the same comparison form (notably case on Windows, and alias/symlink variants of protected roots), so sensitive-path detection and approval matching can be bypassed or behave inconsistently.

Suggested fix
 import (
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"sync"
@@
 func canonicalizePath(rawPath string) string {
@@
 	return resolved
 }
+
+func normalizePathForCompare(rawPath string) string {
+	p := filepath.Clean(canonicalizePath(rawPath))
+	if runtime.GOOS == "windows" {
+		p = strings.ToLower(p)
+	}
+	return p
+}
@@
 func AddSessionReadApproval(dirPath string) {
-	canonical := canonicalizePath(dirPath)
+	canonical := normalizePathForCompare(dirPath)
@@
 func isSensitivePath(expandedPath string) bool {
-	homeDir := os.Getenv("HOME")
-	if homeDir == "" {
-		homeDir = os.Getenv("USERPROFILE")
-	}
-	cleanPath := filepath.Clean(expandedPath)
+	homeDir, _ := os.UserHomeDir()
+	cleanPath := normalizePathForCompare(expandedPath)
@@
 	for _, dir := range sensitiveDirs {
-		dirWithSep := dir + string(filepath.Separator)
-		if cleanPath == dir || strings.HasPrefix(cleanPath, dirWithSep) {
+		normalizedDir := normalizePathForCompare(dir)
+		dirWithSep := normalizedDir + string(filepath.Separator)
+		if cleanPath == normalizedDir || strings.HasPrefix(cleanPath, dirWithSep) {
 			return true
 		}
 	}
@@
 	if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
-		credPath := filepath.Join(localAppData, "Microsoft", "Credentials")
+		credPath := normalizePathForCompare(filepath.Join(localAppData, "Microsoft", "Credentials"))
 		if cleanPath == credPath || strings.HasPrefix(cleanPath, credPath+string(filepath.Separator)) {
 			return true
 		}
 	}
 	if appData := os.Getenv("APPDATA"); appData != "" {
-		credPath := filepath.Join(appData, "Microsoft", "Credentials")
+		credPath := normalizePathForCompare(filepath.Join(appData, "Microsoft", "Credentials"))
 		if cleanPath == credPath || strings.HasPrefix(cleanPath, credPath+string(filepath.Separator)) {
 			return true
 		}
 	}
@@
 func IsSessionReadApproved(filePath string) bool {
-	canonical := canonicalizePath(filePath)
+	canonical := normalizePathForCompare(filePath)
#!/bin/bash
set -euo pipefail

echo "Inspect current comparison normalization in session approval logic:"
sed -n '27,125p' pkg/aiusechat/sessionapproval.go

echo
echo "Check whether case normalization is currently present:"
rg -n 'ToLower|EqualFold|runtime\.GOOS' pkg/aiusechat/sessionapproval.go

echo
echo "Check whether sensitive roots are canonicalized before comparison:"
rg -n 'sensitiveDirs|isSensitivePath|canonicalizePath\(|normalizePathForCompare' pkg/aiusechat/sessionapproval.go

Also applies to: 107-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/aiusechat/sessionapproval.go` around lines 62 - 99, The isSensitivePath
logic compares only the candidate path after filepath.Clean, which allows
mismatches on case-insensitive OSes and symlink/alias variants; update
isSensitivePath (and the related approval-root checks around lines referenced)
to canonicalize and normalize both sides before comparing: resolve abs +
EvalSymlinks (or fall back to Clean), normalize separators, and on Windows use
case-insensitive comparison (strings.EqualFold) or strings.ToLower for both the
cleaned expandedPath and each sensitiveDir/approvalRoot; ensure you canonicalize
every entry in sensitiveDirs (and the LOCALAPPDATA/APPDATA derived credPath)
prior to the equality/HasPrefix checks and use the same dirWithSep normalization
for prefix checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/aiusechat/sessionapproval.go`:
- Around line 62-99: The isSensitivePath logic compares only the candidate path
after filepath.Clean, which allows mismatches on case-insensitive OSes and
symlink/alias variants; update isSensitivePath (and the related approval-root
checks around lines referenced) to canonicalize and normalize both sides before
comparing: resolve abs + EvalSymlinks (or fall back to Clean), normalize
separators, and on Windows use case-insensitive comparison (strings.EqualFold)
or strings.ToLower for both the cleaned expandedPath and each
sensitiveDir/approvalRoot; ensure you canonicalize every entry in sensitiveDirs
(and the LOCALAPPDATA/APPDATA derived credPath) prior to the equality/HasPrefix
checks and use the same dirWithSep normalization for prefix checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 102c550f-4cb5-4391-8183-5281b3706968

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab2969 and 42a16e7.

📒 Files selected for processing (1)
  • pkg/aiusechat/sessionapproval.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants