Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add a diagnostic job demonstrating the TokenOwner mechanism
Temporarily add a `diag-token` job to the `cygwin-test` workflow that
creates a directory through four code paths and reports its NTFS Owner.
The idea is to empirically establish the cause of the Owner asymmetry: a
Cygwin or Cygwin-derived runtime (`cygwin1.dll` for Cygwin git;
`msys-2.0.dll` loaded by Git for Windows's bundled MSYS2 `sh.exe`)
rewrites the process token's `TokenOwner` field at DLL initialization,
and `CreateProcessW` propagates that mutation to every descendant.

The four tests are:

| Test | Sequence                                        | Predicted Owner          |
| ---- | ----------------------------------------------- | ------------------------ |
| A    | PowerShell `New-Item`                           | `BUILTIN\Administrators` |
| B    | Cygwin `mkdir`                                  | `runneradmin` (197108)   |
| C    | Cygwin bash -> Git for Windows `git init`       | `runneradmin` (197108)   |
| D    | PowerShell -> Git Bash -> PowerShell `New-Item` | `runneradmin` (197108)   |

Test A is the Win32 baseline. The kernel-default `TokenOwner` for
`runneradmin`'s full administrative token is `BUILTIN\Administrators`,
because `runneradmin` is the built-in local Administrator (RID 500) and
`FilterAdministratorToken=0` exempts it from UAC token filtering.

Test B is the Cygwin baseline. Cygwin's `cygheap_user::init` calls
`NtSetInformationToken(hProcToken, TokenOwner, &effec_cygsid, ...)` at
DLL init, and the resulting Owner reflects the rewritten token.

Test C is the load-bearing case. The child is a native Windows program
that loads no Cygwin runtime of its own. It produces a user-owned
`.git`, showing that the rewrite performed by the parent Cygwin bash
propagates through `CreateProcessW`'s token duplication to a native
Windows descendant.

Test D strengthens the case: the first and last links are native Windows
programs (a fresh PowerShell using its built-in `New-Item` cmdlet). Only
the middle link is a Cygwin-family runtime (Git for Windows's MSYS2
`bash.exe`, linked against `msys-2.0.dll`). The directory is still
user-owned, showing that the mechanism does not depend on git, nor on
shell dispatch via `git submodule`, nor on any property of the
final-link binary, but only on whether *any* process in the ancestry has
loaded a Cygwin-family runtime.

This `diag-token` job, like the `reproduce-safe-dir` matrix, is
temporary and should be removed before this work is integrated.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • Loading branch information
EliahKagan and claude committed May 18, 2026
commit 76f0160681f551b5199edd6dfd005012e65ad6d3
67 changes: 67 additions & 0 deletions .github/workflows/cygwin-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,73 @@ jobs:
run: |
pytest --color=yes -p no:sugar --instafail -vv ${{ matrix.additional-pytest-args }}

diag-token:
runs-on: windows-latest

env: *cygwin-env

defaults: *cygwin-defaults

steps:
- *force-lf
- *checkout
- *install-cygwin

- name: PowerShell-side token state and file-creation tests
shell: pwsh
run: |
$repo = "D:\a\GitPython\GitPython"
Write-Host "==================== whoami /all (PowerShell) ===================="
whoami /all
Write-Host ""
Write-Host "==================== Test A: PowerShell New-Item directory ===================="
$td = "$repo\test-pwsh-mkdir"
New-Item -ItemType Directory -Path $td -Force | Out-Null
$acl = Get-Acl -LiteralPath $td
Write-Host "Owner of $td : $($acl.Owner)"
Remove-Item $td -Force
Write-Host ""
Write-Host "==================== Test D: PowerShell -> Git Bash -> PowerShell New-Item ===================="
$td4 = "$repo\test-pwsh-bash-pwsh-mkdir"
$scriptPath = "$repo\inner-mkdir.ps1"
"New-Item -ItemType Directory -Path '$td4' -Force | Out-Null" |
Set-Content -Path $scriptPath -Encoding utf8
$env:MSYS2_ARG_CONV_EXCL = '*'
try {
& "C:\Program Files\Git\bin\bash.exe" -c "powershell.exe -NoProfile -ExecutionPolicy Bypass -File '$scriptPath'" 2>&1 | Out-Null
} finally {
Remove-Item Env:MSYS2_ARG_CONV_EXCL -ErrorAction SilentlyContinue
}
if (Test-Path -LiteralPath $td4) {
$acl = Get-Acl -LiteralPath $td4
Write-Host "Owner of $td4 (PowerShell -> bash -> PowerShell New-Item) : $($acl.Owner)"
Remove-Item $td4 -Force
} else {
Write-Host "Owner of $td4 (PowerShell -> bash -> PowerShell New-Item) : (directory not created)"
}
Remove-Item $scriptPath -Force -ErrorAction SilentlyContinue

- name: Cygwin-side token state and file-creation tests
run: |
set +e
echo "==================== id (Cygwin) ===================="
id
echo
echo "==================== Test B: Cygwin mkdir ===================="
td="$(pwd)/test-cygwin-mkdir"
mkdir "$td"
echo "Owner: $(stat -c '%U(%u)' "$td")"
rmdir "$td"
echo
echo "==================== Test C: Cygwin-spawned Git for Windows git init ===================="
td3="$(pwd)/test-cygwin-spawns-wingit"
mkdir "$td3"
( cd "$td3" && /cygdrive/c/Program\ Files/Git/bin/git.exe init -q )
echo "Owner of $td3 (Cygwin-mkdir) : $(stat -c '%U(%u)' "$td3")"
echo "Owner of $td3/.git (Cygwin->Win git init): $(stat -c '%U(%u)' "$td3/.git")"
rm -rf "$td3"
true

reproduce-safe-dir:
strategy:
matrix:
Expand Down