Use isolated native Windows browser for attended WSL login
All checks were successful
CCR intake gate / gate (push) Successful in 2m15s
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
This commit is contained in:
codex 2026-11-06 19:53:07 +01:00
parent de06f49037
commit 8519459e6f
4 changed files with 285 additions and 3 deletions

View file

@ -0,0 +1,43 @@
{
"schema": "railiance-platform.operator-browser-native-windows.v1",
"prepared_at": "2026-11-06T18:49:12.290563+00:00",
"trigger_workplan": "railiance-apps/RAPPS-WP-0014-T03",
"problem": "Founder reports WSL Chrome password/OTP delay, tab/close failures and random wrong controls. Same real-launcher X11/software probe remains unreliable; native Windows Edge disposable-profile probe is founder-confirmed responsive.",
"user_native_windows_verdict": "Everything works normally",
"windows_acl_setup": "Use numeric current-user SID with icacls and verify protected owner-only DACL before browser creation. Set-Acl attempted domain-name translation and failed with a domain trust error; no domain/account setting changed.",
"native_profile": "unique private Windows temporary folder; normal browser profile never selected",
"url_transport": "Authorization URL to PowerShell worker uses stdin, not worker argv/encoded script or a handoff file. Edge receives initial authorization URL as its browser startup argument, as Linux Chromium did. Credential-bearing query keys are refused.",
"native_windows_process_environment": "Clear BAO_TOKEN, VAULT_TOKEN and OPENBAO_TOKEN in child process only",
"callback_allowlist": [
"http://localhost:8250/oidc/callback",
"http://127.0.0.1:8250/oidc/callback"
],
"issuer": "https://kc.coulomb.social",
"wsl_selection": "microsoft in kernel release; fail closed without fallback to shared/default browser",
"non_wsl": "existing pinned Linux Chromium path retained",
"lifecycle": "Owned PID/start-time/executable guards, close after browser exit or 600s, bounded private-profile removal retry",
"tests": {
"operator_unit_cases": 15,
"operator_unit_status": "passed",
"native_fixture": {
"production_guard_refuses_local_fixture": true,
"private_acl_verified_before_launch": true,
"ipv4_and_localhost_redirect_reached": true,
"owned_process_closed": true,
"private_profile_removed": true,
"native_identity_request": false
},
"final_native_windows_credential_query_guard": "passed without browser creation or network request"
},
"sources_sha256": {
"scripts/operator-browser/xdg-open": "a6794d50dbe4131ff2b3fd259365d5f30d467702daf8d8d4f63265bab47e2e51",
"scripts/operator-browser/windows_browser.py": "fd952b6960cac3159e50708e492163fcab8540f7b9d6227e88ce75c5626b2824",
"tests/test_operator_windows_browser.py": "b0e87ff253a978649015ddc80c27ff0478834144db3d0938d229b7edd5cdbcdf"
},
"actual_attended_owner_login_with_new_launcher": "pending",
"scope_limits": [
"No issuer/role/callback registrations, output allowlist, token-helper control, factor policy or credential grants changed",
"No real browser page/profile contents, authorization URL, cookies, token helper, password or OTP read by the agent",
"Underlying WSLg issue is bypassed, not claimed repaired"
]
}

View file

@ -0,0 +1,109 @@
"""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

View file

@ -1,12 +1,15 @@
#!/usr/bin/env python3
"""Open the approved OpenBao login in a new empty Playwright Chromium profile.
"""Open approved OpenBao login in a new isolated operator browser profile.
The Windows default browser is not used. A previous login opened Firefox,
which was already signed in as another account. This launcher uses the
headed Chromium binary pinned by coulomb-social's Playwright install.
native Windows Edge in a private disposable profile on WSL, where the Linux
window/input path proved unreliable. Other Linux hosts retain pinned Chromium.
"""
import importlib.util
import json
import os
import platform
import pwd
import subprocess
import sys
@ -14,7 +17,6 @@ import tempfile
from pathlib import Path
from urllib.parse import parse_qs, urlsplit
BROWSERS_JSON = Path(
"/home/worsch/coulomb-social/scripts/node_modules/playwright-core/browsers.json"
)
@ -118,6 +120,20 @@ def stamp(result):
def open_url(url):
if not allowed(url):
stamp("rejected")
return 1
if "microsoft" in platform.release().lower():
path = Path(__file__).resolve().with_name("windows_browser.py")
try:
spec = importlib.util.spec_from_file_location("operator_windows_browser", path)
native = importlib.util.module_from_spec(spec)
spec.loader.exec_module(native)
opened = native.launch(url)
except Exception: # noqa: BLE001 — keep native browser setup details contained
opened = False
stamp("opened" if opened else "windows-open-failed")
return 0 if opened else 1
if not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"):
stamp("no-display")
return 1

View file

@ -0,0 +1,114 @@
"""Native WSL opener boundaries; no GUI, identity request or real profile."""
import base64
import importlib.machinery
import importlib.util
import io
import json
import runpy
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1] / "scripts/operator-browser"
loader = importlib.machinery.SourceFileLoader("native_browser_fixture", str(ROOT / "windows_browser.py"))
spec = importlib.util.spec_from_loader(loader.name, loader)
native = importlib.util.module_from_spec(spec)
loader.exec_module(native)
launcher = runpy.run_path(str(ROOT / "xdg-open"))
URL = "https://kc.coulomb.social/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A8250%2Foidc%2Fcallback&state=synthetic"
class SavedPipe(io.BytesIO):
def close(self):
self.saved = self.getvalue()
super().close()
class WindowsBrowserBoundary(unittest.TestCase):
def process(self, marker=b"opened\r\n"):
return SimpleNamespace(stdin=SavedPipe(), stdout=SavedPipe(marker))
def test_worker_receives_url_only_via_pipe(self):
process = self.process()
with patch.object(native.subprocess, "Popen", return_value=process) as popen, \
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
self.assertTrue(native.launch(URL))
argv = popen.call_args.args[0]
self.assertEqual(json.loads(process.stdin.saved), {"url": URL})
self.assertNotIn(URL, " ".join(argv))
source = base64.b64decode(argv[-1]).decode("utf-16le")
self.assertNotIn("state=synthetic", source)
self.assertIn("[Console]::In.ReadToEnd()", source)
def test_uses_numeric_owner_sid_and_verifies_protection_before_launch(self):
source = base64.b64decode(native.command()[-1]).decode("utf-16le")
self.assertIn("$grant='*'+$sid.Value+':(OI)(CI)F'", source)
self.assertIn("'/inheritance:r'", source)
self.assertIn("$rules.Count -ne 1", source)
self.assertIn("$rules[0].IdentityReference.Value -ne $sid.Value", source)
self.assertLess(source.index("$verified=Get-Acl"), source.index("Start-Process"))
self.assertNotIn("Set-Acl", source)
def test_worker_rechecks_same_issuer_and_both_approved_callbacks(self):
source = native.SCRIPT
self.assertIn("$uri.Host -ne 'kc.coulomb.social'", source)
self.assertIn("$callbacks.Count -ne 1", source)
self.assertIn("http://localhost:8250/oidc/callback", source)
self.assertIn("http://127.0.0.1:8250/oidc/callback", source)
self.assertIn("'code','access_token','id_token','refresh_token','mfa_token','password','client_secret'", source)
for banned in ("--no-sandbox", "--disable-web-security", "--ignore-certificate-errors", "--remote-debugging"):
self.assertNotIn(banned, source)
def test_cleanup_checks_exact_pid_start_and_executable(self):
self.assertIn("$current.StartTime.ToUniversalTime().Ticks -eq $startTicks", native.SCRIPT)
self.assertIn("$current.Path -eq $browser", native.SCRIPT)
self.assertIn("$current.Kill()", native.SCRIPT)
self.assertNotIn("Stop-Process -Name", native.SCRIPT)
for value in (True, 0, 601, "600"):
with self.subTest(value=value), self.assertRaises(ValueError):
native.command(value)
def test_unknown_output_failure_and_timeout_do_not_become_opened(self):
for marker in (b"failed\r\n", b"unexpected synthetic output\n", b""):
process = self.process(marker)
with self.subTest(marker=marker), patch.object(native.subprocess, "Popen", return_value=process), \
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
self.assertFalse(native.launch(URL))
process = self.process()
with patch.object(native.subprocess, "Popen", return_value=process), \
patch.object(native.select, "select", return_value=([], [], [])):
self.assertFalse(native.launch(URL))
def test_wsl_uses_native_before_display_check_without_changing_url(self):
process = self.process()
markers = []
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
patch.object(launcher["platform"], "release", return_value="6.18-microsoft-standard-WSL2"), \
patch.dict(launcher["os"].environ, {}, clear=True), \
patch.object(native.subprocess, "Popen", return_value=process), \
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
self.assertEqual(launcher["open_url"](URL), 0)
self.assertEqual(markers, ["opened"])
self.assertEqual(json.loads(process.stdin.saved), {"url": URL})
def test_foreign_url_never_reaches_native_and_failure_never_falls_back(self):
markers = []
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
patch.object(launcher["platform"], "release", return_value="microsoft"), \
patch.object(native.subprocess, "Popen") as popen:
self.assertEqual(launcher["open_url"]("https://foreign.invalid/"), 1)
popen.assert_not_called()
process = self.process(b"failed\n")
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
patch.object(launcher["platform"], "release", return_value="microsoft"), \
patch.object(native.subprocess, "Popen", return_value=process) as popen, \
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
self.assertEqual(launcher["open_url"](URL), 1)
self.assertEqual(popen.call_count, 1)
self.assertEqual(markers, ["rejected", "windows-open-failed"])
if __name__ == "__main__":
unittest.main()