Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
109 lines
5.8 KiB
Python
109 lines
5.8 KiB
Python
"""WSL operator opener: native Edge, private disposable profile, no shared session.
|
|
|
|
Authorization URL reaches the PowerShell worker through stdin, not its command
|
|
argument or a file. Edge receives the initial URL as a browser startup argument,
|
|
as the Linux launcher did. Passwords, OTPs and token values are never accepted.
|
|
The child prints only a fixed startup marker, closes only its owned process and
|
|
removes its private profile after close or the bounded authentication window.
|
|
"""
|
|
|
|
import base64
|
|
import json
|
|
import select
|
|
import subprocess
|
|
|
|
POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
|
|
MAXIMUM_SECONDS = 600
|
|
SCRIPT = r'''
|
|
$ErrorActionPreference='Stop'
|
|
$ProgressPreference='SilentlyContinue'
|
|
$profile=$null;$process=$null;$startTicks=$null;$opened=$false;$removed=$false
|
|
function Remove-PrivateProfile([string]$path) {
|
|
if([IO.Path]::GetDirectoryName($path).TrimEnd('\') -ne [IO.Path]::GetTempPath().TrimEnd('\') -or -not [IO.Path]::GetFileName($path).StartsWith('railiance-operator-browser-')){return $false}
|
|
for($attempt=0;$attempt -lt 20;$attempt++){
|
|
try {Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction Stop;return $true} catch {
|
|
if(-not (Test-Path -LiteralPath $path)){return $true}
|
|
Start-Sleep -Milliseconds 250
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
try {
|
|
foreach($name in @('BAO_TOKEN','VAULT_TOKEN','OPENBAO_TOKEN')){[Environment]::SetEnvironmentVariable($name,$null,'Process')}
|
|
[Console]::InputEncoding=[Text.UTF8Encoding]::new($false)
|
|
$inputBody=[Console]::In.ReadToEnd()
|
|
if($inputBody.Length -gt 32768){throw 'invalid payload'}
|
|
$payload=$inputBody | ConvertFrom-Json
|
|
if(@($payload.PSObject.Properties).Count -ne 1 -or -not ($payload.url -is [string])){throw 'invalid payload'}
|
|
$url=$payload.url
|
|
if($url.Length -gt 16384 -or $url.IndexOfAny([char[]]@([char]0,[char]10,[char]13,[char]34)) -ge 0){throw 'invalid url'}
|
|
$uri=[Uri]$url
|
|
if($uri.Scheme -ne 'https' -or $uri.Host -ne 'kc.coulomb.social' -or $uri.Port -ne 443 -or $uri.UserInfo -or $uri.Fragment){throw 'invalid origin'}
|
|
Add-Type -AssemblyName System.Web
|
|
$query=[Web.HttpUtility]::ParseQueryString($uri.Query)
|
|
foreach($key in @('code','access_token','id_token','refresh_token','mfa_token','password','client_secret')){
|
|
if($query.AllKeys -contains $key){throw 'credential-bearing url refused'}
|
|
}
|
|
$callbacks=$query.GetValues('redirect_uri')
|
|
if($callbacks.Count -ne 1 -or $callbacks[0] -notin @('http://localhost:8250/oidc/callback','http://127.0.0.1:8250/oidc/callback')){throw 'invalid callback'}
|
|
$browser=Join-Path ${env:ProgramFiles(x86)} 'Microsoft\Edge\Application\msedge.exe'
|
|
if(-not (Test-Path -LiteralPath $browser)){throw 'browser unavailable'}
|
|
$profile=Join-Path ([IO.Path]::GetTempPath()) ('railiance-operator-browser-'+[guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Path $profile | Out-Null
|
|
$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
$grant='*'+$sid.Value+':(OI)(CI)F'
|
|
& "$env:SystemRoot\System32\icacls.exe" $profile '/grant:r' $grant '/inheritance:r' | Out-Null
|
|
if($LASTEXITCODE -ne 0){throw 'profile protection failed'}
|
|
$verified=Get-Acl -LiteralPath $profile
|
|
$rules=$verified.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])
|
|
if(-not $verified.AreAccessRulesProtected -or $rules.Count -ne 1 -or $rules[0].IdentityReference.Value -ne $sid.Value -or ($rules[0].FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -ne [Security.AccessControl.FileSystemRights]::FullControl){throw 'profile protection unverified'}
|
|
$arguments=@(('--user-data-dir="'+$profile+'"'),'--no-first-run','--no-default-browser-check','--disable-sync','--new-window',('"'+$url+'"'))
|
|
$process=Start-Process -FilePath $browser -ArgumentList $arguments -PassThru
|
|
$startTicks=$process.StartTime.ToUniversalTime().Ticks
|
|
$opened=$true
|
|
[Console]::Out.WriteLine('opened');[Console]::Out.Flush()
|
|
$deadline=[datetime]::UtcNow.AddSeconds(__MAXIMUM__)
|
|
while(-not $process.HasExited -and [datetime]::UtcNow -lt $deadline){Start-Sleep -Milliseconds 250;$process.Refresh()}
|
|
} catch {
|
|
if(-not $opened){[Console]::Out.WriteLine('failed');[Console]::Out.Flush()}
|
|
} finally {
|
|
if($process -and $startTicks){
|
|
try {
|
|
$current=Get-Process -Id $process.Id -ErrorAction SilentlyContinue
|
|
if($current -and $current.StartTime.ToUniversalTime().Ticks -eq $startTicks -and $current.Path -eq $browser){
|
|
$null=$current.CloseMainWindow()
|
|
if(-not $current.WaitForExit(5000)){$current.Kill();$null=$current.WaitForExit(5000)}
|
|
}
|
|
$process.Refresh()
|
|
if($process.HasExited){$removed=Remove-PrivateProfile $profile}
|
|
} catch { }
|
|
} elseif($profile -and -not $process) {
|
|
$removed=Remove-PrivateProfile $profile
|
|
}
|
|
}
|
|
'''
|
|
|
|
|
|
def command(maximum_seconds=MAXIMUM_SECONDS):
|
|
if type(maximum_seconds) is not int or not 10 <= maximum_seconds <= MAXIMUM_SECONDS:
|
|
raise ValueError("invalid_browser_lifetime")
|
|
source = SCRIPT.replace("__MAXIMUM__", str(maximum_seconds))
|
|
return [POWERSHELL, "-NoProfile", "-NonInteractive", "-EncodedCommand",
|
|
base64.b64encode(source.encode("utf-16le")).decode()]
|
|
|
|
|
|
def launch(url):
|
|
"""Caller must apply its issuer/callback allowlist before passing the URL."""
|
|
if not isinstance(url, str) or len(url) > 16384:
|
|
return False
|
|
try:
|
|
process = subprocess.Popen(command(), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL, start_new_session=True)
|
|
process.stdin.write(json.dumps({"url": url}).encode("utf-8"))
|
|
process.stdin.close()
|
|
readable, _, _ = select.select([process.stdout], [], [], 20)
|
|
marker = process.stdout.readline(32) if readable else b""
|
|
process.stdout.close()
|
|
return marker in (b"opened\n", b"opened\r\n")
|
|
except (OSError, ValueError, subprocess.SubprocessError):
|
|
return False
|