Keep the shell configuration and the key out of the user's reach

The user could empty the authorized_keys and break the login of the
website, change the shell configuration to break the prompt, and write
startup files that zsh reads on every login. The shell configuration and
the key are now read-only for the user, and the shell reads its
configuration from ~/.zsh, so the files in the home directory are
ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zebreus 2026-10-25 15:26:01 +01:00
commit 49c61b6b91

View file

@ -59,10 +59,13 @@ function Restore-Keys($target, $source) {
[IO.File]::WriteAllText($target, (($keys + $required) -join "`n") + "`n")
}
# The permissions of the home directory and of the files in it that have to stay: the user may read and write them
# and delete their own files, but not the home directory itself and not the files the website needs. Only SYSTEM
# and the administrators own them, so the user cannot take the permissions away again.
function Set-ProtectedAcl($path, $forDirectory) {
# The shell configuration and the key the website logs in with must not change, the four texts may be edited.
$readOnly = '.zshrc', '.zshenv', 'authorized_keys'
# The permissions of the home directory and of the files in it that have to stay: the user may read them and write
# the ones that may change, and delete their own files, but not the home directory itself and not the files the
# website needs. Only SYSTEM and the administrators own them, so the user cannot take the permissions away again.
function Set-ProtectedAcl($path, $forDirectory, $writable) {
if (-not $sid) { return }
try {
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
@ -78,10 +81,12 @@ function Set-ProtectedAcl($path, $forDirectory) {
$inheritOnly = [System.Security.AccessControl.PropagationFlags]::InheritOnly
$full = [System.Security.AccessControl.FileSystemRights]::FullControl
$some = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write
$read = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute
$inherit = if ($forDirectory) { $children } else { $none }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $some, $none, $noPropagate, 'Allow'))
$userRights = if ($writable) { $some } else { $read }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, $noPropagate, 'Allow'))
if ($forDirectory) {
# the files in it are the user's own, they may delete them
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $full, $children, $inheritOnly, 'Allow'))
@ -96,7 +101,7 @@ function Set-ProtectedAcl($path, $forDirectory) {
# gets MSYS2's default files from /etc/skel, which MSYS2 itself only copies into a home that does not exist yet.
function New-Home($path) {
New-Item -ItemType Directory -Path $path | Out-Null
Set-ProtectedAcl $path $true
Set-ProtectedAcl $path $true $true
Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force
}
@ -108,7 +113,7 @@ if (-not (Test-Path -LiteralPath $HomeDir)) {
New-Home $HomeDir
Write-Log "replaced the link $HomeDir"
} else {
Set-ProtectedAcl $HomeDir $true
Set-ProtectedAcl $HomeDir $true $true
}
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
@ -119,7 +124,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
Remove-Anything $dir
}
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
Set-ProtectedAcl $dir $true
Set-ProtectedAcl $dir $true $true
if (Test-Path -LiteralPath $target) {
$item = Get-Item -LiteralPath $target -Force
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
@ -135,7 +140,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
} else {
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
}
Set-ProtectedAcl $target $false
Set-ProtectedAcl $target $false ($readOnly -notcontains $copy.Name)
Write-Log "restored $($copy.Name)"
} catch {
Write-Log "failed $($copy.Name) : $_"
@ -192,6 +197,11 @@ if (-not ($keys | Where-Object { ($_ -split '\s+')[1] -eq ($required[0] -split '
}
[IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript)
# The shell configuration used to be in the home directory, the shell reads it from ~/.zsh now.
if (Test-Path -LiteralPath (Join-Path $HomeDir '.zsh\.zshrc')) {
Remove-Item -LiteralPath (Join-Path $HomeDir '.zshrc') -Force -ErrorAction SilentlyContinue
}
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
$triggers = @(
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 3650)