Skip to content

Read an Unrecognized env Option as an Undetermined Directory in the Write Guard - #3727

Merged
ptr727 merged 7 commits into
developfrom
feature/burndown-guard-env-abbrev
Oct 31, 2026
Merged

ptr727 merged 7 commits into
developfrom
feature/burndown-guard-env-abbrev

Conversation

@ptr727

@ptr727 ptr727 commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes a finding raised on promotion #3715. GNU env accepts a unique abbreviation of a long option, so a constructed env --ch=/srv/other GIT_DIR=.git git push origin main runs git in /srv/other. The write guard read --ch as a no-op and resolved the push's repository from the working directory.

  • An env option the guard does not recognize, long or short, now leaves the directory a push runs in unnamed for requirement 4. _push_repo and the bare-push branch reader take None in place of the working directory, the value env -C "$D" already produces, so requirement 4 fails closed only for main, master, and develop, 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 absolute cd in a wrapper body still names the repository. A GIT_DIR behind such an option is not read for the repository, since the option may abbreviate --unset. Prefix matching was decided against.
  • Each unrecognized option may or may not take the next word, and a value it takes may be an -S string, which reaches an abbreviated --split-string. _launches reads 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.
  • A push naming no refspec or HEAD behind 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, -i and the value letters keep their behavior.
  • Requirement 6 keeps judging the working directory. The extra readings can only add a deny there, for example env --chd /path/wt git commit run from a primary checkout.
  • host-setup/agent-safety/README.md states the new reading in its lexer paragraph, in requirement 4, and in requirement 6.

Verification

  • Reproduced first with classify on two constructed scratch checkouts, origins alpha/x and beta/y, with a stubbed live-rules lookup requiring a pull request on beta/y's main. Before the fix --ch=<b>, --chd <b>, and -X <b> pushes to main were allowed. After it they are denied, and feature-branch pushes stay allowed.
  • New self-test cases cover --ch=<dir>, --chd <dir>, an unknown short letter, two unrecognized options of different arity, an abbreviated split string, an unknown option ahead of sh -c, a subshell in the body, absolute -C/--git-dir/body cd re-anchoring, a URL remote, bare and HEAD pushes, GH_REPO not leaking from a speculative reading, each recognized option unchanged, and allow on feature/x. Each new repository and branch row fails with its fix reverted.
  • gh-write-guard.py --selftest passes. Pinned ruff format and check, mypy, tests/test_guard_size.py, and the prose gate pass.
  • Seven local strict-review passes, the last raising nothing.

Dropped findings

Pre-existing findings, not filed

  • gh-write-guard.py _bare_push_branch, pre-existing: its leading_cd and moved parameters answer no caller, since every caller passes None and False. One caller is in host-setup/agent-safety/claude/test_install.py, outside this change.

Closes on promotion: #3725

🤖 Generated with Claude Code

ptr727 and others added 3 commits October 31, 2026 11:31
…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>
Copilot AI lite review requested due to automatic review settings October 31, 2026 18:52
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@ptr727

ptr727 commented Oct 31, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 2ce9fbd42dc1af6efe5d5894f7d4f896616479b7, the content this pull request carries at that commit against develop, and it recorded 6 findings.

…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 env flags 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

Comment thread host-setup/agent-safety/README.md Resolved
@codecov

codecov Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.02%. Comparing base (8d71126) to head (6746e61).

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           
Flag Coverage Δ
python-3.13 82.02% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ptr727 and others added 2 commits October 31, 2026 12:02
…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>
@ptr727

ptr727 commented Oct 31, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head f26d9085085ecb16daa18f191f504358a3427f97, the content this pull request carries at that commit against develop, and it recorded 2 findings.

…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>
@ptr727

ptr727 commented Oct 31, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 6746e6186b3562fc1b05e3f32dd551fc1e8a2392, the content this pull request carries at that commit against develop, and it recorded 0 findings.

@ptr727
ptr727 merged commit afeb869 into develop Oct 31, 2026
13 checks passed
@ptr727
ptr727 deleted the feature/burndown-guard-env-abbrev branch October 31, 2026 20:12
ptr727 added a commit that referenced this pull request Oct 31, 2026
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants