Skip to content

guard: exempt out-of-project writes from the approval gate #175

Description

@studykit

Description

Extend guard's approval gate to also exempt writes outside the project directory,
alongside the existing git-ignored exemption. A write whose resolved target is not
under CLAUDE_PROJECT_DIR (e.g. the session scratchpad under /private/tmp, or
~/.claude/...) is not the user's tracked project source, and Bash can already write
there ungated — so gating the file-editing tools for it was pure friction.

Changes (plugins/guard/scripts/guard_hook.py):

  • New _is_outside_project(project_dir, target) — true when the resolved target is
    not the project dir and not under it; fail-toward-inside (keep gating) if the project
    dir can't be resolved.
  • cmd_gate now allows an unapproved write when the target is outside the project dir
    (trace allow_outside_repo), checked before the git check-ignore exemption (and it
    skips that subprocess for out-of-repo paths).
  • Guard's own config + state stay excluded via _is_guard_owned; they live in-repo, so
    the out-of-repo exemption never applies to them.
  • Version 0.3.0 → 0.3.1; docs updated (AGENTS.md, dev/design.md, README.md).

Context

Follow-up to #172 / #174. Surfaced by dogfooding: with guard active in-session, a Write
to the session scratchpad (/private/tmp/.../scratchpad) was denied because
git check-ignore can't classify an out-of-repo path, so it wasn't covered by the
git-ignore exemption. The gate's purpose is to guard the user's tracked project source;
out-of-repo paths fall outside that scope entirely.

Acceptance Criteria

  • Direct gate matrix against the real repo (unapproved) — verified:
    • out-of-repo → allow_outside_repo: the session scratchpad under /private/tmp,
      /tmp/whatever.txt, ~/.claude/settings.json.
    • tracked source (guard_hook.py, AGENTS.md) → DENY.
    • in-repo git-ignored (_x.local.md) → allow_gitignored.
    • guard-owned in-repo (.claude/guard/state/evil.json, .claude/guard.local.json)
      → DENY (out-of-repo exemption does not weaken self-arm / self-disable protection).
  • python3 -c "import ast; ast.parse(...)" passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    taskWorkflow implementation task

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions