v1.4: Block Claude from writing to the Desktop and AppData
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aecdc732b9
commit
14763a002a
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace QuickClaude
|
||||
{
|
||||
// QuickClaude.exe --guard-write: Claude Code's PreToolUse hook for Write, Edit and NotebookEdit.
|
||||
// Keeps Claude from writing anywhere OpenRules lets things open without a confirm box that a
|
||||
// normal (non-admin) account can write to: your Desktop and all of AppData (which holds your
|
||||
// own Start Menu, AppData\Local\Programs and the Store app aliases). Without this, Claude in
|
||||
// "Edit files" or "Auto" mode could drop a shortcut or program there and then open it unasked.
|
||||
// Every other folder is left to the "Claude can" setting as before.
|
||||
public static class WriteGuard
|
||||
{
|
||||
public static int Run()
|
||||
{
|
||||
string path = null, cwd = null;
|
||||
try
|
||||
{
|
||||
using var stdin = new StreamReader(Console.OpenStandardInput(), new UTF8Encoding(false));
|
||||
using var doc = JsonDocument.Parse(stdin.ReadToEnd());
|
||||
var root = doc.RootElement;
|
||||
cwd = Str(root, "cwd");
|
||||
if (root.TryGetProperty("tool_input", out var input))
|
||||
path = Str(input, "file_path") ?? Str(input, "notebook_path") ?? Str(input, "path");
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (string.IsNullOrEmpty(path)) return 0; // nothing to check: normal rules decide
|
||||
|
||||
if (!Blocked(path, cwd)) return 0;
|
||||
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
hookSpecificOutput = new
|
||||
{
|
||||
hookEventName = "PreToolUse",
|
||||
permissionDecision = "deny",
|
||||
permissionDecisionReason =
|
||||
"QuickClaude doesn't let Claude write to the Desktop or AppData folders (programs and shortcuts there " +
|
||||
"can open without a confirm box). Tell the user, and suggest a different folder if one would do."
|
||||
}
|
||||
});
|
||||
using var stdout = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false));
|
||||
stdout.Write(json);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool Blocked(string path, string cwd)
|
||||
{
|
||||
try
|
||||
{
|
||||
var full = Path.IsPathRooted(path) || string.IsNullOrEmpty(cwd)
|
||||
? Path.GetFullPath(path)
|
||||
: Path.GetFullPath(Path.Combine(cwd, path));
|
||||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var dirs = new[]
|
||||
{
|
||||
Path.Combine(home, "AppData"),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Microsoft", "Windows", "Start Menu"),
|
||||
}.Where(d => !string.IsNullOrEmpty(d));
|
||||
return dirs.Any(d =>
|
||||
{
|
||||
var root = Path.GetFullPath(d).TrimEnd('\\');
|
||||
return full.Equals(root, StringComparison.OrdinalIgnoreCase) ||
|
||||
full.StartsWith(root + "\\", StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
catch { return true; } // a path we can't even parse doesn't get written
|
||||
}
|
||||
|
||||
static string Str(JsonElement e, string name) =>
|
||||
e.ValueKind == JsonValueKind.Object && e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user