Skip to content

env: @claude inherits PAGER and neither editor variable (closes #530) - #569

Merged
vyskocilm merged 1 commit into
mainfrom
env/claude-stops-inheriting-editor
Sep 10, 2026
Merged

vyskocilm merged 1 commit into
mainfrom
env/claude-stops-inheriting-editor

Conversation

@vyskocilm

Copy link
Copy Markdown
Contributor

Closes #530.

What

@claude stops inheriting EDITOR and VISUAL. PAGER stays, so do ANTHROPIC_BASE_URL and NO_COLOR.

Why, measured

git exec class reaches sandbox by fallback, both chains measured: GIT_EDITOR -> core.editor -> VISUAL -> EDITOR, GIT_PAGER -> core.pager -> PAGER. inherit hands host's own command string to git inside. Refusing only GIT_* spellings closes invisible half of class, not class — all six stay legal at set/inherit for any profile, all six stay annotated. Table refuses nothing.

Decision is per shipped profile, and rests on measurement not on composability argument. Inside @claude sandbox:

command -v vi vim nano   ->  nothing
command -v less more     ->  /usr/bin/less, /usr/bin/more

Inherited EDITOR names program not there (host vim/code not bound), so git commit fails identically with variable and without. Inherited PAGER=less names one that is. Invariant 2 corollary: grant wider than need shrinks, no rule added. Closes no escape — profiles are trusted layer, nothing untrusted chose these values.

Golden diff (review artifact)

bwrap.claude.txt   -1 --setenv EDITOR vim, +1 --setenv PAGER less
env.claude.txt     EDITOR row + annotation out, PAGER row + annotation in
show.claude.txt    -2 annotated inherit rows

Tests

TestNoBuiltinInheritsAnEditorVariable (internal/profile) — named regression. Asserts PAGER PRESENT in same test: "no builtin inherits an editor" is satisfied by a profile inheriting nothing at all. Negative control both directions: EDITOR = true back in base.toml fails it, removed passes.

Three tests rode on @claude's inherit EDITOR and are repointed — env golden fixture host value (PAGER=less), unchecked-sink negative control (PAGER), forging-rune sweep.

Forging-rune sweep was MEASURED unfailable for two of its probes: with the ESC/CR and bidi fixtures disconnected from any profile the test still passed, because $SNUG_PODMAN satisfies the \x1b assertion and the graft fixture the U+202E one. Now carries a per-probe positive control; cutting the route fails it. Probes arrive through a fixture-defined profile inheriting EDITOR/VISUAL — same policy.Resolve path, same VerbInherit entries.

Also removed from TestAnnotatedEnvPairsAShippedProfileWritesArePinned inventory: a stale row pre-approves the next grant, and that test says so itself.

Docs

  • VERIFY.md §6d-bis — by-hand equivalent, both commands and their real output.
  • VERIFY.md DEL check moved EDITOR -> PAGER: the EDITOR route no longer reaches the document. Re-run, output pinned (less� twice, lossy false, 0 raw DEL bytes).
  • ENVIRONMENT-VARIABLES.md §3.2 rewritten. Legality (both verbs, for anybody) and what a shipped profile carries are two decisions, now stated as two. Stale prose deleted: it claimed GIT_* "are refused (§4.4's list)", named forbiddenEnv (gone), and ended "Still open, still Tracker: git-config adapter leftovers (commit signing, fixed catalogue, the exec class) #35" for a closed issue.
  • dryrun.go and visible_test.go header comments naming @claude's shipped inherit EDITOR corrected in place.

Verification

make gate green. SNUG_REQUIRE_SANDBOX=1 SNUG_SANDBOX_TIMEOUT=25m make integration on this host: 209 PASS, 2 SKIP, engine tests: 46 ran, floor 46 — podman version 6.0.2. Two runs, one failure each, a DIFFERENT test each time and both in network paths this change does not touch: TestSSHReachParityBetweenAPlainRunAndNet (pasta: Couldn't set IPv6 address(es) in namespace: File exists, host load ~21) and TestContainerEgressFollowsNetProfile (connect: network is unreachable). Both pass in isolation, 2.25s and 8.80s. Suite bound raised to 25m because the default 8m does not fit this host, which predates this branch.

No redteam round owed: this withdraws a grant. No mount generation, no seccomp filter, no host-integration surface.

🤖 Generated with Claude Code

git's exec class reaches a sandbox by fallback, both chains measured:
GIT_EDITOR -> core.editor -> VISUAL -> EDITOR and GIT_PAGER -> core.pager
-> PAGER. So `inherit EDITOR` hands git the host's own command string, and
refusing only the GIT_* spellings would close the invisible half of a class
rather than the class. All six spellings stay legal at `set` and `inherit`
for any profile and stay ANNOTATED at both verbs; nothing in the table
refuses.

What changes is what the one shipped profile that touches the environment
asks for, and it changes on a measurement rather than on the composability
argument. Inside @claude's sandbox:

    command -v vi vim nano   ->  nothing
    command -v less more     ->  /usr/bin/less, /usr/bin/more

An inherited EDITOR names a program that is not in there (the host's vim or
code is not bound), so `git commit` fails identically with the variable and
without it. An inherited PAGER=less names one that is. EDITOR and VISUAL
leave [profile.claude.environ.inherit]; PAGER and ANTHROPIC_BASE_URL and
NO_COLOR stay. Invariant 2's corollary: a grant wider than the need shrinks
rather than acquiring a rule. No escape closes — profiles are the trusted
layer and nothing untrusted chose these values.

Golden diff is the review artifact: one --setenv line and two annotated
`inherit` rows leave @claude's argv, environment block and profile show.

Tests. TestNoBuiltinInheritsAnEditorVariable (internal/profile) is the
named regression, and asserts PAGER PRESENT in the same test because
"no builtin inherits an editor" is satisfied by a profile that inherits
nothing at all. Negative control run both ways: re-adding EDITOR = true to
base.toml fails it, removing it passes.

Three tests were riding on @claude's inherit EDITOR and are repointed:
the env golden fixture's host value (PAGER=less), the unchecked-sink
negative control (PAGER), and the forging-rune sweep, whose ESC/CR and bidi
probes now arrive through a fixture-defined profile that inherits EDITOR
and VISUAL. That sweep was MEASURED unfailable for those two probes:
with both fixtures disconnected from any profile it still passed, because
$SNUG_PODMAN satisfies the ESC assertion and the graft fixture the bidi
one. It now carries a per-probe positive control, and cutting the route
fails it.

VERIFY.md §6d-bis is the by-hand equivalent; §4's DEL check moves from
EDITOR to PAGER because the EDITOR route no longer reaches the document.
ENVIRONMENT-VARIABLES.md §3.2 rewritten: legality and what a shipped
profile carries are two decisions, stated as two.

No redteam round: this withdraws a grant, touches no mount generation, no
seccomp filter and no host-integration surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vyskocilm
vyskocilm merged commit 002f2ab into main Sep 10, 2026
5 checks passed
@vyskocilm
vyskocilm deleted the env/claude-stops-inheriting-editor branch September 10, 2026 16:57
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.

EDITOR/VISUAL/PAGER are git's exec class by fallback: refusing the GIT_* spellings closes the invisible half only

1 participant