fix(devkit): isolate git subprocesses from inherited GIT_* env - #446
Merged
Conversation
Under a git hook (husky pre-push running `pnpm test`), the hook exports GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE, which git honours over an explicit cwd. So devkit's git subprocesses — including the real-repo tests in git.test.ts — operated on the hook's repo instead of their temp worktree, clobbering a live worktree and its remote (config, commit, and push all misdirected; observed on PR #445 and again on #444). NodeCommandRunner now scrubs git's repo-location env vars for `git` invocations so cwd is always authoritative; an explicit options.env entry still wins (spread last). Adds runner- and Git-level regression tests that point GIT_* at a decoy and assert operations land in cwd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
✓ Changelog fragment found — thanks! |
Contributor
|
Preview removed for PR #446. |
crs48
added a commit
that referenced
this pull request
Jul 10, 2026
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @xnetjs/devkit@1.0.0 ### Major Changes - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - [#446](#446) [`10c9f87`](10c9f87) Thanks [@crs48](https://github.com/crs48)! - Isolate git subprocesses from inherited repo-location env. When the dev loop (or its tests) ran while a git hook was active — e.g. husky `pre-push` running `pnpm test` — the hook's exported `GIT_DIR`/`GIT_WORK_TREE`/`GIT_INDEX_FILE` leaked into `git` children and overrode the explicit `cwd`, so operations (`config`, `commit`, even `push`) targeted the hook's repo instead of the requested worktree. `NodeCommandRunner` now scrubs git's repo-location env vars for `git` invocations so `cwd` is always authoritative; an explicit `options.env` entry still wins. ## @xnetjs/cli@0.1.0 ### Minor Changes - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`853d849`](853d849), [`10c9f87`](10c9f87), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/runtime@0.2.0 - @xnetjs/devkit@1.0.0 - @xnetjs/data@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/plugins@0.8.0 ### Minor Changes - [#420](#420) [`dd3b1cb`](dd3b1cb) Thanks [@crs48](https://github.com/crs48)! - Single-shell layout primitives (exploration 0284): `createDefaultTree()` and `DEFAULT_WORKSPACE_ID` join the workspace layout API — the one canonical tree (a sectioned sidebar in the rail, the full left dock, tabs on) that replaces the quiet/calm/bench preset trichotomy. Purely additive: `createPresetTree` and the preset ids remain for the devtools seed and portable-workspace round-trips. - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/abuse@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/runtime@0.2.0 ### Minor Changes - [#448](#448) [`853d849`](853d849) Thanks [@crs48](https://github.com/crs48)! - `NodeStoreSyncProvider` now handles hub capacity rejections gracefully: on the first `QUOTA_EXCEEDED` (over the hub's per-user cap) or `STORAGE_FULL` (hub disk full) rejection it pauses outbound sync instead of re-flooding the hub, keeps local data intact, and resumes on the next reconnect. Subscribe to the new `onSyncBlocked(listener)` API (with `SyncBlockedReason`/`SyncBlockedListener` types) to surface a "storage full" notice in your app. ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/history@0.8.0 - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/storage@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/abuse@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/crypto@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/core@0.8.0 ## @xnetjs/data@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/storage@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/data-bridge@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/history@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/identity@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/react@0.8.0 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`853d849`](853d849), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/runtime@0.2.0 - @xnetjs/history@0.8.0 - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/storage@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/sqlite@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/sync@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/core@0.8.0 ## @xnetjs/sqlite@0.8.0 ## xnet-cloud@0.0.11 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 - @xnetjs/cloud@0.0.1 ## @xnetjs/brain@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/vectors@0.0.1 ## @xnetjs/comms@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/dashboard@0.0.12 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/react@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/social@0.0.12 ## @xnetjs/labs@0.0.12 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/data@0.8.0 ## @xnetjs/licenses@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 ## @xnetjs/maps@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 ## @xnetjs/meetings@0.0.5 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/data@0.8.0 ## @xnetjs/server@0.0.11 ### Patch Changes - Updated dependencies []: - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/social@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/unreal@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 ## xnet-desktop@0.8.0 Desktop shell release riding the @xnetjs/core 0.8.0 train. Desktop-specific changes are not tracked here; see the core packages' changelogs for what shipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
@xnetjs/devkit'sNodeCommandRunnernow scrubs git's repo-location environment variables (GIT_DIR,GIT_WORK_TREE,GIT_INDEX_FILE, and friends) forgitinvocations, so the explicitcwdis always authoritative. An explicitoptions.enventry still wins.Why
RunOptions.cwdis required precisely so a caller "can never accidentally run inprocess.cwd()" — but git readsGIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILEfrom the environment overcwd. A husky hook (pre-commit/pre-push) exports exactly those vars pointing at the hook's repo. So when the pre-push hook runspnpm test, devkit's real-repo tests (git.test.ts) spawnedgitchildren that inherited the hook'sGIT_DIRand operated on the actual worktree instead of their temp repo — runninggit config,git commit, and evengit pushagainst it.This was not hypothetical: it clobbered a live worktree and its remote branch during PR #445 (and recurred on #444) — the worktree's
user.name/emailgot overwritten and junkinitial/checkpointcommits were pushed to origin.How
command-runner.ts: forcommand === 'git', merge{ ...process.env, ...<git-location vars set to undefined>, ...options.env }. Node'sspawnomitsundefined-valued env keys, so the inherited leak is dropped while any explicit override is preserved. The scrubbed set is documented in the newGIT_LOCATION_ENV(module-internal — not added to the package barrel, so no public API change → patch).Tests
command-runner.test.ts: with a leaked bogusGIT_DIR,git rev-parse --is-inside-work-treestill resolves thecwdrepo (would be fatal without the scrub); and an explicitoptions.envGIT_DIRstill wins.git.test.ts: acheckpoint()lands incwdwhileGIT_*points at a decoy, and the decoy never becomes a repo.🤖 Generated with Claude Code