feat: sandboxed claude session launcher scripts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
346042f1c5
commit
1dea5c697c
3 changed files with 176 additions and 0 deletions
11
scripts/refactor-claude.sh
Executable file
11
scripts/refactor-claude.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Backwards-compatible wrapper for the ~/nixos-refactor/ worktree workflow.
|
||||||
|
# Equivalent to `scripts/sandbox-claude.sh` with no target arg — sandboxes
|
||||||
|
# the worktree containing this script under the policy in
|
||||||
|
# scripts/sandbox-settings.json.
|
||||||
|
#
|
||||||
|
# For sandboxing a different repo (e.g. a new public template under
|
||||||
|
# development), use sandbox-claude.sh <dir> directly.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
exec "$(dirname "$(readlink -f "$0")")/sandbox-claude.sh" "$@"
|
||||||
58
scripts/sandbox-claude.sh
Executable file
58
scripts/sandbox-claude.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch claude under the sandbox allowlist+deny policy in a target dir.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sandbox-claude.sh # target = parent of script dir
|
||||||
|
# # (the worktree containing the script)
|
||||||
|
# sandbox-claude.sh <dir> # target = <dir>
|
||||||
|
# sandbox-claude.sh <dir> <claude-args>...
|
||||||
|
# # remaining args pass to claude
|
||||||
|
#
|
||||||
|
# Behaviour:
|
||||||
|
# - cd to <target>
|
||||||
|
# - copy <script-dir>/sandbox-settings.json -> <target>/.claude/settings.json
|
||||||
|
# fresh on every launch (idempotent — policy evolves in main and
|
||||||
|
# propagates on next launch; hand-editing the runtime copy is pointless).
|
||||||
|
# - exec claude
|
||||||
|
#
|
||||||
|
# Safety model (from sandbox-settings.json):
|
||||||
|
# - defaultMode=acceptEdits — file edits silent
|
||||||
|
# - allow: narrow Bash set — nix build/check/eval, fmt/lint,
|
||||||
|
# in-worktree git, basic inspection
|
||||||
|
# - ask: rm/mv/cp/chmod/chown — prompt every time
|
||||||
|
# - deny: sudo, *-rebuild switch|test|boot, omni-rebuild, nh, git push,
|
||||||
|
# git remote, curl/wget/ssh, systemctl, docker/podman, WebFetch,
|
||||||
|
# WebSearch — hard reject, cannot be overridden
|
||||||
|
# mid-session
|
||||||
|
#
|
||||||
|
# To loosen the policy, edit sandbox-settings.json in the source checkout
|
||||||
|
# and relaunch — the runtime copy in <target>/.claude/ refreshes from it.
|
||||||
|
#
|
||||||
|
# <target> should have `.claude/` in its .gitignore so the runtime copy
|
||||||
|
# doesn't leak into commits. The script warns if it doesn't.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
SETTINGS_SRC="$SCRIPT_DIR/sandbox-settings.json"
|
||||||
|
|
||||||
|
TARGET="$SCRIPT_DIR/.."
|
||||||
|
if [[ $# -gt 0 && -d "$1" ]]; then
|
||||||
|
TARGET="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
TARGET="$(readlink -f "$TARGET")"
|
||||||
|
|
||||||
|
[[ -d "$TARGET" ]] || { echo "sandbox-claude: target does not exist: $TARGET" >&2; exit 1; }
|
||||||
|
[[ -f "$SETTINGS_SRC" ]] || { echo "sandbox-claude: settings file not found: $SETTINGS_SRC" >&2; exit 1; }
|
||||||
|
|
||||||
|
cd "$TARGET"
|
||||||
|
mkdir -p .claude
|
||||||
|
cp -f "$SETTINGS_SRC" .claude/settings.json
|
||||||
|
|
||||||
|
if [[ -f .gitignore ]] && ! grep -qE '(^|/)\.claude/?$' .gitignore; then
|
||||||
|
echo "sandbox-claude: note — '.claude/' not found in $TARGET/.gitignore;" >&2
|
||||||
|
echo " the runtime settings.json may leak into commits." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec claude "$@"
|
||||||
107
scripts/sandbox-settings.json
Normal file
107
scripts/sandbox-settings.json
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"defaultMode": "acceptEdits",
|
||||||
|
"allow": [
|
||||||
|
"Read",
|
||||||
|
"Glob",
|
||||||
|
"Grep",
|
||||||
|
"Edit",
|
||||||
|
"Write",
|
||||||
|
"NotebookEdit",
|
||||||
|
|
||||||
|
"Bash(nix build:*)",
|
||||||
|
"Bash(nix flake check)",
|
||||||
|
"Bash(nix flake check:*)",
|
||||||
|
"Bash(nix flake show)",
|
||||||
|
"Bash(nix flake show:*)",
|
||||||
|
"Bash(nix flake info)",
|
||||||
|
"Bash(nix flake info:*)",
|
||||||
|
"Bash(nix flake metadata)",
|
||||||
|
"Bash(nix flake metadata:*)",
|
||||||
|
"Bash(nix eval:*)",
|
||||||
|
"Bash(nix log:*)",
|
||||||
|
"Bash(nix-instantiate:*)",
|
||||||
|
"Bash(nixos-rebuild build:*)",
|
||||||
|
"Bash(nixos-rebuild dry-build:*)",
|
||||||
|
"Bash(nixos-rebuild build-vm:*)",
|
||||||
|
|
||||||
|
"Bash(nixfmt:*)",
|
||||||
|
"Bash(statix:*)",
|
||||||
|
"Bash(deadnix:*)",
|
||||||
|
"Bash(nix run nixpkgs#nixfmt:*)",
|
||||||
|
"Bash(nix run nixpkgs#nixfmt-rfc-style:*)",
|
||||||
|
"Bash(nix run nixpkgs#statix:*)",
|
||||||
|
"Bash(nix run nixpkgs#deadnix:*)",
|
||||||
|
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(xargs:*)",
|
||||||
|
|
||||||
|
"Bash(git status)",
|
||||||
|
"Bash(git status:*)",
|
||||||
|
"Bash(git diff)",
|
||||||
|
"Bash(git diff:*)",
|
||||||
|
"Bash(git log)",
|
||||||
|
"Bash(git log:*)",
|
||||||
|
"Bash(git show)",
|
||||||
|
"Bash(git show:*)",
|
||||||
|
"Bash(git branch)",
|
||||||
|
"Bash(git branch:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git checkout:*)",
|
||||||
|
"Bash(git restore:*)",
|
||||||
|
"Bash(git stash)",
|
||||||
|
"Bash(git stash:*)",
|
||||||
|
"Bash(git rev-parse:*)",
|
||||||
|
"Bash(git worktree list)",
|
||||||
|
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(pwd)",
|
||||||
|
"Bash(which:*)",
|
||||||
|
"Bash(type:*)",
|
||||||
|
"Bash(env)",
|
||||||
|
"Bash(echo:*)",
|
||||||
|
"Bash(file:*)",
|
||||||
|
"Bash(stat:*)",
|
||||||
|
"Bash(wc:*)",
|
||||||
|
"Bash(head:*)",
|
||||||
|
"Bash(tail:*)",
|
||||||
|
"Bash(cat:*)"
|
||||||
|
],
|
||||||
|
"ask": [
|
||||||
|
"Bash(rm:*)",
|
||||||
|
"Bash(rmdir:*)",
|
||||||
|
"Bash(mv:*)",
|
||||||
|
"Bash(cp:*)",
|
||||||
|
"Bash(chmod:*)",
|
||||||
|
"Bash(chown:*)"
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
"Bash(sudo:*)",
|
||||||
|
"Bash(sudo)",
|
||||||
|
"Bash(doas:*)",
|
||||||
|
"Bash(su:*)",
|
||||||
|
"Bash(nixos-rebuild switch:*)",
|
||||||
|
"Bash(nixos-rebuild test:*)",
|
||||||
|
"Bash(nixos-rebuild boot:*)",
|
||||||
|
"Bash(omni-rebuild)",
|
||||||
|
"Bash(omni-rebuild:*)",
|
||||||
|
"Bash(nh:*)",
|
||||||
|
"Bash(git push)",
|
||||||
|
"Bash(git push:*)",
|
||||||
|
"Bash(git remote:*)",
|
||||||
|
"Bash(git reset --hard:*)",
|
||||||
|
"Bash(curl:*)",
|
||||||
|
"Bash(wget:*)",
|
||||||
|
"Bash(ssh:*)",
|
||||||
|
"Bash(scp:*)",
|
||||||
|
"Bash(rsync:*)",
|
||||||
|
"Bash(nc:*)",
|
||||||
|
"Bash(systemctl:*)",
|
||||||
|
"Bash(docker:*)",
|
||||||
|
"Bash(podman:*)",
|
||||||
|
"WebFetch",
|
||||||
|
"WebSearch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue