Repository navigation
Read an Unrecognized env Option as an Undetermined Directory in the Write Guard - #3727
Conversation
…Write Guard An env option the guard does not recognize, such as GNU env's unique abbreviation --ch of --chdir, now leaves the repository a push writes undetermined, so a push to main, master, or develop fails closed. The command behind such an option is read both with and without the option taking the next word. The signal options, --null, and --debug are listed so they keep reading as no-ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ush's Branch Unread Each env option the guard does not recognize now chooses on its own whether it takes the next word, so two abbreviations of different arity in one command are both read. A push naming no refspec behind such an option no longer reads its branch from the working directory, which the option may have left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l Names A push behind an env option the guard does not recognize now reads its directory as unnamed rather than flagging its repository unread, so an absolute -C, an absolute --git-dir, or an absolute cd in a wrapper body still resolves it. A GIT_DIR behind such an option is not read, since the option may be an abbreviation of --unset. A reading past the first adds commands only and changes no shell state, readings are compared whole, and the README states the four-option bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
A recorded local strict-review pass covers head |
…Option's Value as a Split String A subshell inside a wrapper body now restores whether the body's directory is undetermined along with its directory and environment, so an absolute cd inside it no longer anchors a later push. The value of an env option the guard does not recognize is also read as an -S string, which reaches an abbreviation of --split-string. Self-test rows that denied on the old code wherever the live rules query failed are moved to the repository table, and the README says an absolute -C or --git-dir still names the repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Outstanding critical and moderate findings leave subshell state and split-string write detection incorrect, while branch-case tests do not prove repository resolution.
4 open issues
What changed in this PR
This pull request hardens the write guard against unrecognized GNU env options that may alter directory or command resolution.
Changes:
- Tracks unknown options and evaluates bounded possible argument consumption.
- Adds recognized GNU
envflags and expands push-resolution tests. - Documents undetermined-directory behavior and requirement 4 semantics.
Unresolved correctness issues remain around subshell state restoration and split-string option abbreviations.
| File | Summary |
|---|---|
host-setup/agent-safety/README.md |
Documents how unrecognized env options affect directory resolution and requirement 4. |
host-setup/agent-safety/claude/gh-write-guard.py |
Updates env parsing, uncertainty propagation, push resolution, and related self-tests. |
This pull request requires changes.
Review effort: Lite
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #3727 +/- ##
========================================
Coverage 82.02% 82.02%
========================================
Files 27 27
Lines 9275 9275
========================================
Hits 7608 7608
Misses 1667 1667
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…Requirement 6's Directory Requirement 4 names git's own -C, since an absolute env -C behind an unrecognized option is not read. Requirement 6 states that such an option does not move the directory it judges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ized env Option A push naming no refspec, or HEAD, behind an env option the guard does not recognize falls back to the branch checked out in the working directory when nothing else names one, so a push from develop or main still fails closed as it did before the option was read. The README names an absolute cd in a wrapper body among the anchors that still name the repository, and the choices docstring says what it counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
…Leaves Its Branch Unread The fallback that reads a bare or HEAD push's branch as if the unrecognized env option were absent now also reads the GIT_DIR the push carries, as the guard did before, so a GIT_DIR naming a checkout on a protected branch still fails closed. The README names HEAD beside a push naming no refspec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
…d Unread Moves Whole, and Name uv's No-Cache Setting (#3715) Promotes the changes merged into `develop` since promotion #3703 to `main`, the third round of a backlog burndown. - [#3709](#3709) Reads a push's remote, and the branch of a push with no refspec, in the repository git actually uses in the write guard's owner check (requirement 4). Before, `--git-dir` and a `GIT_DIR` on the push were ignored, so a constructed `git --git-dir=<other>/.git push origin develop` was checked against the working directory's repository rules and allowed. A shape whose repository the guard cannot determine (an earlier `GIT_DIR`, a `-c` or `--config-env` override of a `remote.` or `url.` key, any `GIT_CONFIG*` variable, an unresolvable `--git-dir`) now blocks `main`, `master` and `develop`. The self-test covers each shape and `host-setup/agent-safety/README.md` states the requirement. - [#3712](#3712) Reads a file moved out of a source some rule never read in full for that rule in the prose gate: a narrowed directory scope, a `home_path_exempt` file, instruction text for `issue-ref`, and a move that changes the file's lexer. Skips YAML block scalars when reading a Compose file's volume entries. - [#3707](#3707) Names uv's no-cache setting, and its remedies, when the repo gate's resolved PyYAML interpreter is gone at launch, rather than reporting a bare missing-file error. - [#3708](#3708) Moves the section-name lookup key into one `section_key()` in `spec/fidelity_text.py`, used by the carry, the audit, the skill build and the review ledger, with no behavior change. - [#3722](#3722) Applies `env`'s directory and environment options to the command the write guard judges, answering this promotion's first review round: `env -C`/`--chdir` move the repository a relative `GIT_DIR` and the remote resolve in, `env -i`/`-u` clear a `GIT_DIR` that no longer steers the check, and `env -S` is read rather than skipped. A run-time directory reads as undetermined, which blocks `main`, `master` and `develop`. - [#3721](#3721) Finds a Compose block-scalar opener through the YAML lexer the gate already uses rather than a hand-written pattern, so a key holding `#`, quoted or not, opens the opaque region. Also answers this promotion's first review round. - [#3727](#3727) Reads an `env` option the write guard does not recognize, such as GNU `env`'s `--ch` abbreviation of `--chdir`, as leaving the push's repository undetermined, so `main`, `master` and `develop` fail closed, answering this promotion's second review round. A feature-branch push is never newly blocked. - [#3731](#3731) Removes the fallback #3727 kept for a push naming no refspec behind an unrecognized `env` option, answering this promotion's third review round. Such a push resolves its branch only through an absolute `--git-dir`, git's own `-C`, or a `cd` in a wrapper body, and is otherwise read as writing every protected default, so it fails closed even from a feature branch. Deferred review findings: #3705 (`scripts/README.md`'s uvx promise), #3706 (`validate.py`'s exact section-name match), #3710 (whether `section_key` should casefold), #3711 (Compose-name and directory moves in the prose gate), #3713 (the review reader's `open review findings:` spelling), #3714 (an edited shebang that changes a file's lexer), #3720 (the write guard's reading of `git push -o<value>`), #3723 (unused `_bare_push_branch` parameters), #3729 (a bare push after a branch switch earlier in the same command), #3732 (the review reader's `Review findings remain:` spelling). For the maintainer: #3730 asks whether every bare push whose repository the guard cannot determine should fail closed, as #3731 now does behind an unrecognized `env` option. Fixes #3695 Fixes #3684 Fixes #3694 Fixes #2358 Fixes #3686 Fixes #3716 Fixes #3717 Fixes #3725 Fixes #3728 🤖 Generated with [Claude Code](https://claude.com/claude-code)



Summary
Fixes a finding raised on promotion #3715. GNU
envaccepts a unique abbreviation of a long option, so a constructedenv --ch=/srv/other GIT_DIR=.git git push origin mainruns git in/srv/other. The write guard read--chas a no-op and resolved the push's repository from the working directory.envoption the guard does not recognize, long or short, now leaves the directory a push runs in unnamed for requirement 4._push_repoand the bare-push branch reader takeNonein place of the working directory, the valueenv -C "$D"already produces, so requirement 4 fails closed only formain,master, anddevelop, as Read a Push's Remote and Branch in the Repository Git Uses in the Write Guard #3709 and Read env's Directory and Environment Options in the Write Guard #3722 treat a shape they cannot resolve. An absolute--git-dir, git's own-C, or an absolutecdin a wrapper body still names the repository. AGIT_DIRbehind such an option is not read for the repository, since the option may abbreviate--unset. Prefix matching was decided against.-Sstring, which reaches an abbreviated--split-string._launchesreads every combination of up to four of those choices. A reading past the first adds commands only and changes no shell state, and a subshell restores the body's undetermined state with its directory.HEADbehind such an option, whose branch nothing else names, reads it as if the option were absent. So it is never weaker than before and never newly blocks a feature-branch push.--null,--debug, and the GNU signal options are now listed, so they keep reading as no-ops.-0,-v,-iand the value letters keep their behavior.env --chd /path/wt git commitrun from a primary checkout.host-setup/agent-safety/README.mdstates the new reading in its lexer paragraph, in requirement 4, and in requirement 6.Verification
classifyon two constructed scratch checkouts, originsalpha/xandbeta/y, with a stubbed live-rules lookup requiring a pull request onbeta/y'smain. Before the fix--ch=<b>,--chd <b>, and-X <b>pushes tomainwere allowed. After it they are denied, and feature-branch pushes stay allowed.--ch=<dir>,--chd <dir>, an unknown short letter, two unrecognized options of different arity, an abbreviated split string, an unknown option ahead ofsh -c, a subshell in the body, absolute-C/--git-dir/bodycdre-anchoring, a URL remote, bare andHEADpushes,GH_REPOnot leaking from a speculative reading, each recognized option unchanged, and allow onfeature/x. Each new repository and branch row fails with its fix reverted.gh-write-guard.py --selftestpasses. Pinned ruff format and check, mypy,tests/test_guard_size.py, and the prose gate pass.Dropped findings
gh-write-guard.py_env_option,style: reading an unknown short letter as unrecognized was named as over-hardening. The decided fix for Read an Unrecognized env Option as an Undetermined Repository in the Write Guard #3725 names short options explicitly.Pre-existing findings, not filed
gh-write-guard.py_bare_push_branch,pre-existing: itsleading_cdandmovedparameters answer no caller, since every caller passesNoneandFalse. One caller is inhost-setup/agent-safety/claude/test_install.py, outside this change.Closes on promotion: #3725
🤖 Generated with Claude Code