u-chan-secrets 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ // Two-stage disposable check: --prepare runs non-elevated; --run requires UAC approval.
2
+ // No vault is unlocked and no real passwords, keyboard text or screenshots are recorded.
3
+ import fs from 'node:fs/promises'
4
+ import path from 'node:path'
5
+ import os from 'node:os'
6
+ import { randomUUID } from 'node:crypto'
7
+ import { spawn, execFile } from 'node:child_process'
8
+ import { promisify } from 'node:util'
9
+ import { compileCsharp } from '../dist/platform/windows/assets/compiler.js'
10
+ import { PROMPT_HELPER_CSHARP } from '../dist/platform/windows/assets/prompt-src.js'
11
+ import { defaultAdminCheck } from '../dist/platform/windows/install.js'
12
+ import { setAdminOnlyAcl } from '../dist/platform/windows/acl.js'
13
+ import { accepted } from './windows-security/result-policy.mjs'
14
+
15
+ if (process.platform !== 'win32') throw new Error('Windows required')
16
+ const exec = promisify(execFile), command = process.argv[2], runIdArgument = process.argv[3]
17
+ const quote = s => "'" + s.replaceAll("'", "''") + "'"
18
+ const ps = script => exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { windowsHide: true })
19
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
20
+ const publicRoot = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'UChanSecurityChecks')
21
+ async function createChannel(runId) {
22
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(runId ?? '')) throw new Error('Fresh run UUID required')
23
+ const directory = path.join(publicRoot, runId)
24
+ const channelScript = await fs.readFile(new URL('./windows-security/public-channel.ps1', import.meta.url), 'utf8')
25
+ await ps('$probePublicRoot=' + quote(publicRoot) + ';$probeRunDirectory=' + quote(directory) + ';' + channelScript)
26
+ return directory
27
+ }
28
+ async function publishReport(directory, report) {
29
+ const temporaryReport = path.join(directory, 'report.json.tmp')
30
+ await fs.writeFile(temporaryReport, JSON.stringify(report, null, 2), { flag: 'wx' })
31
+ await fs.rename(temporaryReport, path.join(directory, 'report.json'))
32
+ }
33
+
34
+ if (command === '--prepare') {
35
+ if (await defaultAdminCheck()) throw new Error('Prepare must run non-elevated')
36
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'u-chan-security-'))
37
+ const runId = randomUUID(), prefix = path.join(directory, 'probe'), publicDirectory = path.join(publicRoot, runId), publicPrefix = path.join(publicDirectory, 'probe'), attacker = path.join(directory, 'attacker.exe')
38
+ await compileCsharp(await fs.readFile(new URL('./windows-security/attacker.cs', import.meta.url), 'utf8'), attacker, { outputType: 'WindowsApplication', referencedAssemblies: ['System.Windows.Forms.dll', 'System.Drawing.dll'] })
39
+ const child = spawn(attacker, [publicPrefix, prefix, runId], { windowsHide: true, stdio: 'ignore' })
40
+ let spawnError
41
+ child.once('error', e => { spawnError = e })
42
+ const exited = new Promise(resolve => child.once('exit', resolve))
43
+ const config = { runId, prefix, attackerPid: child.pid, directory, publicDirectory }
44
+ await fs.writeFile(path.join(directory, 'config.json'), JSON.stringify(config))
45
+ const deadline = Date.now() + 5000
46
+ while (Date.now() < deadline) {
47
+ if (spawnError) throw spawnError
48
+ if (await fs.stat(prefix + '.armed').catch(() => null)) break
49
+ if (await fs.stat(prefix + '.attacker').catch(() => null)) throw new Error('Attacker positive control failed: ' + await fs.readFile(prefix + '.attacker', 'utf8'))
50
+ await sleep(100)
51
+ }
52
+ if (!await fs.stat(prefix + '.armed').catch(() => null)) throw new Error('Attacker did not arm')
53
+ // Keep the child referenced: run --run <runId> concurrently in a second, elevated terminal.
54
+ console.log(JSON.stringify({ ...config, armed: true, nextCommand: 'node scripts/verify-windows-adversarial.mjs --run ' + runId }))
55
+ await exited
56
+ const nativeReport = path.join(publicDirectory, 'report.json'), reportDeadline = Date.now() + 30000
57
+ while (Date.now() < reportDeadline && !await fs.stat(nativeReport).catch(() => null)) await sleep(100)
58
+ const native = JSON.parse(await fs.readFile(nativeReport, 'utf8'))
59
+ const observed = Object.fromEntries((await fs.readFile(prefix + '.attacker', 'utf8')).trim().split(/\r?\n/).map(line => line.split('=')))
60
+ const report = { ...native, attacker: observed, ok: accepted(native, observed, runId) }
61
+ await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify(report, null, 2))
62
+ console.log(JSON.stringify(report))
63
+ if (!report.ok) process.exitCode = 1
64
+ } else if (command === '--run') {
65
+ if (!await defaultAdminCheck()) throw new Error('Run requires administrator privileges')
66
+ const runId = runIdArgument
67
+ // Atomically create a read-public, admin/SYSTEM-write-only channel. Never read or write user-supplied paths while elevated.
68
+ const directory = await createChannel(runId), prefix = path.join(directory, 'probe')
69
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
70
+ const root = path.join(home, '#0 backup', 'adversarial-' + randomUUID())
71
+ const task = 'UChanSecurityProbe-' + randomUUID()
72
+ const report = { ok: false, nativeFixture: true, root, runId }
73
+ let registered = false
74
+ try {
75
+ await fs.mkdir(root); await setAdminOnlyAcl(root)
76
+ const trace = path.join(root, 'trace.txt')
77
+ const metadata = prefix + '.metadata'
78
+ const head = 'public static class PromptHelper\n {'
79
+ if (!PROMPT_HELPER_CSHARP.includes(head)) throw new Error('Fixture hooks no longer match')
80
+ let source = PROMPT_HELPER_CSHARP.replace(head, head + '\n private static string ProbeDesktop; private static NamedPipeServerStream ProbeIdlePipe;')
81
+ .replace('desktop = CreateDesktop("UChan-" + Guid.NewGuid().ToString("N"),', 'ProbeDesktop="UChanProbe-"+Guid.NewGuid().ToString("N"); desktop = CreateDesktop(ProbeDesktop,')
82
+ .replace('int remainingSeconds = 120;', 'int remainingSeconds = 4;')
83
+ .replace('SwitchDesktop(original);', `if(SwitchDesktop(original)) File.AppendAllText(@"${trace}", "restored"+Environment.NewLine);`)
84
+ source = source.replace('timer.Start();\n if (txtPassword != null)', `timer.Start();
85
+ string probePipe="";
86
+ for(int k=0;k+1<args.Length;k++)if(args[k]=="--output-pipe")probePipe=args[k+1];
87
+ File.AppendAllText(@"${trace}", "shown-system:"+WindowsIdentity.GetCurrent().IsSystem+";session:"+Process.GetCurrentProcess().SessionId+Environment.NewLine);
88
+ File.WriteAllLines(@"${metadata}",new string[]{ProbeDesktop,probePipe,form.Handle.ToInt64().ToString(),btnOk.Handle.ToInt64().ToString(),"${runId}",probePipe+"-idle"});
89
+ form.BeginInvoke((Action)delegate { form.Activate();btnOk.Focus();File.AppendAllText(@"${trace}","canary-send-count:"+UChanProbeCanary.Send()+Environment.NewLine); });
90
+ if (txtPassword != null)`)
91
+ source = source.replace('DialogResult result = form.ShowDialog();', `Panel probeMarker=new Panel(); probeMarker.BackColor=Color.FromArgb(1,254,71); probeMarker.Size=new Size(64,64); probeMarker.Location=new Point(0,0); form.Controls.Add(probeMarker); probeMarker.BringToFront();
92
+ form.KeyPreview=true; form.KeyDown+=(sender,eventArgs)=>{if(eventArgs.KeyCode==Keys.F24)File.AppendAllText(@"${trace}","protected-canary-received"+Environment.NewLine);};
93
+ DialogResult result = form.ShowDialog();`)
94
+ source = source.replace('List<string> childArgs = new List<string>(args);', `ProbeIdlePipe=new NamedPipeServerStream(name+"-idle",PipeDirection.In,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous,4096,4096,security);bool onlySystem=true;int systemRules=0;foreach(PipeAccessRule rule in pipe.GetAccessControl().GetAccessRules(true,true,typeof(SecurityIdentifier))){if(rule.AccessControlType==AccessControlType.Allow){systemRules++;if(rule.IdentityReference.Value!="S-1-5-18")onlySystem=false;}}File.AppendAllText(@"${trace}","pipe-system-only:"+(onlySystem&&systemRules>0)+Environment.NewLine);List<string> childArgs = new List<string>(args);`)
95
+ source += '\n' + await fs.readFile(new URL('./windows-security/canary.cs', import.meta.url), 'utf8')
96
+ if (!source.includes('shown-system:') || source.includes('int remainingSeconds = 120;')) throw new Error('Fixture substitution failed')
97
+ const attacked = path.join(root, 'attacked.exe'), failure = path.join(root, 'failure.exe')
98
+ await compileCsharp(source, attacked, { outputType: 'WindowsApplication', referencedAssemblies: ['System.Windows.Forms.dll', 'System.Drawing.dll'] })
99
+ const failing = PROMPT_HELPER_CSHARP
100
+ .replace('return RunDialog(args);', 'throw new InvalidOperationException("Disposable failure fixture");')
101
+ .replace('SwitchDesktop(original);', `if(SwitchDesktop(original)) File.AppendAllText(@"${trace}","failure-restored"+Environment.NewLine);`)
102
+ await compileCsharp(failing, failure, { outputType: 'WindowsApplication', referencedAssemblies: ['System.Windows.Forms.dll', 'System.Drawing.dll'] })
103
+ const output = path.join(root, 'native-result.json'), runner = path.join(root, 'runner.mjs')
104
+ await fs.writeFile(runner, `import{execFile}from'node:child_process';import fs from'node:fs';import{promisify}from'node:util';const exec=promisify(execFile);const results={};for(const[name,file]of ${JSON.stringify([['attacked', attacked], ['failure', failure]])}){const start=Date.now();try{const r=await exec(file,['--mode','confirm','--title','DISPOSABLE SECURITY TEST - DO NOT INTERACT','--phrase','DISPOSABLE TEST - NO PASSWORD','--lines','[]'],{timeout:20000,windowsHide:true});results[name]={exitCode:0,stdoutBytes:Buffer.byteLength(r.stdout),elapsedMs:Date.now()-start};}catch(e){results[name]={exitCode:e.code,stdoutBytes:Buffer.byteLength(e.stdout??''),elapsedMs:Date.now()-start};}if(name==='attacked')fs.writeFileSync(${JSON.stringify(prefix + '.done')},'done');}fs.writeFileSync(${JSON.stringify(output)},JSON.stringify(results));`)
105
+ await setAdminOnlyAcl(root)
106
+ await ps(`$ErrorActionPreference='Stop';$a=New-ScheduledTaskAction -Execute ${quote(path.join(home, 'bin', 'node.exe'))} -Argument ${quote('"' + runner + '"')} -WorkingDirectory ${quote(root)};$p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest;$s=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName ${quote(task)} -Action $a -Principal $p -Settings $s | Out-Null`)
107
+ registered = true
108
+ await ps(`Start-ScheduledTask -TaskName ${quote(task)}`)
109
+ const deadline = Date.now() + 60000
110
+ while (Date.now() < deadline && !await fs.stat(output).catch(() => null)) await sleep(200)
111
+ report.execution = JSON.parse(await fs.readFile(output, 'utf8'))
112
+ report.events = (await fs.readFile(trace, 'utf8')).trim().split(/\r?\n/)
113
+ report.ok = report.execution.attacked.exitCode === 1 && report.execution.attacked.elapsedMs >= 3500 && report.execution.attacked.stdoutBytes === 0 && report.execution.failure.exitCode === 1 && report.events.some(e => e.startsWith('shown-system:True;session:')) && report.events.includes('protected-canary-received') && report.events.includes('pipe-system-only:True') && report.events.includes('restored') && report.events.includes('failure-restored')
114
+ } catch (e) { report.errorType = e.name; report.errorMessage = String(e.message).slice(0, 300) }
115
+ finally { if (registered) await ps(`Stop-ScheduledTask -TaskName ${quote(task)} -ErrorAction SilentlyContinue;Unregister-ScheduledTask -TaskName ${quote(task)} -Confirm:$false`) }
116
+ await publishReport(directory, report)
117
+ console.log(JSON.stringify(report))
118
+ if (!report.ok) process.exitCode = 1
119
+ } else if (command === '--browser') {
120
+ if (!await defaultAdminCheck()) throw new Error('Browser fixture requires administrator privileges')
121
+ const publicDirectory = await createChannel(runIdArgument ?? randomUUID())
122
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
123
+ const root = path.join(home, '#0 backup', 'browser-probe-' + randomUUID()), task = 'UChanBrowserProbe-' + randomUUID()
124
+ let registered = false
125
+ try {
126
+ await fs.mkdir(root); await setAdminOnlyAcl(root)
127
+ const runner = path.join(root, 'runner.mjs')
128
+ await fs.copyFile(new URL('./windows-security/browser-worker.mjs', import.meta.url), runner)
129
+ await fs.copyFile(new URL('./windows-security/module-audit.ps1', import.meta.url), path.join(root, 'module-audit.ps1'))
130
+ await fs.copyFile(new URL('./windows-security/result-policy.mjs', import.meta.url), path.join(root, 'result-policy.mjs'))
131
+ await setAdminOnlyAcl(root)
132
+ const transcripts = path.join(process.env.USERPROFILE, '.codex', 'sessions')
133
+ await ps(`$ErrorActionPreference='Stop';$a=New-ScheduledTaskAction -Execute ${quote(path.join(home, 'bin', 'node.exe'))} -Argument ${quote('"' + runner + '" "' + root + '" "' + transcripts + '"')} -WorkingDirectory ${quote(root)};$p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest;$s=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 3) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName ${quote(task)} -Action $a -Principal $p -Settings $s | Out-Null`)
134
+ registered = true; await ps(`Start-ScheduledTask -TaskName ${quote(task)}`)
135
+ const output = path.join(root, 'browser-result.json'), deadline = Date.now() + 90000
136
+ while (Date.now() < deadline && !await fs.stat(output).catch(() => null)) await sleep(200)
137
+ const report = JSON.parse(await fs.readFile(output, 'utf8'))
138
+ await publishReport(publicDirectory, { ...report, evidence: root })
139
+ console.log(JSON.stringify({ ...report, evidence: root }))
140
+ if (!report.ok) process.exitCode = 1
141
+ } finally { if (registered) await ps(`Stop-ScheduledTask -TaskName ${quote(task)} -ErrorAction SilentlyContinue;Unregister-ScheduledTask -TaskName ${quote(task)} -Confirm:$false`) }
142
+ } else throw new Error('Use concurrent --prepare and --run <runId> terminals, or --browser from an administrator terminal')
@@ -1,50 +1,50 @@
1
- // Run from a trusted administrator terminal after building and installing the service.
2
- // Exercises SYSTEM relay, desktop entry, restoration and reply transport without a password UI.
3
- import fs from 'node:fs/promises'
4
- import path from 'node:path'
5
- import { randomUUID } from 'node:crypto'
6
- import { promisify } from 'node:util'
7
- import { execFile } from 'node:child_process'
8
- import { compileCsharp } from '../dist/platform/windows/assets/compiler.js'
9
- import { PROMPT_HELPER_CSHARP } from '../dist/platform/windows/assets/prompt-src.js'
10
- import { defaultAdminCheck } from '../dist/platform/windows/install.js'
11
- import { setAdminOnlyAcl } from '../dist/platform/windows/acl.js'
12
-
13
- if (process.platform !== 'win32' || !await defaultAdminCheck()) throw new Error('Use a trusted Windows administrator terminal')
14
- const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
15
- const root = path.join(home, '#0 backup', 'desktop-probe-' + randomUUID())
16
- const task = 'UChanDesktopProbe-' + randomUUID()
17
- const exec = promisify(execFile)
18
- const powershell = script => exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { windowsHide: true })
19
- const quote = value => "'" + value.replaceAll("'", "''") + "'"
20
- let registered = false
21
- try {
22
- await fs.mkdir(root)
23
- await setAdminOnlyAcl(root)
24
- const trace = path.join(root, 'desktop-trace.txt')
25
- if (!PROMPT_HELPER_CSHARP.includes('return RunDialog(args);') || !PROMPT_HELPER_CSHARP.includes('SwitchDesktop(original);')) throw new Error('Native probe hooks no longer match; no helper was run')
26
- const source = PROMPT_HELPER_CSHARP
27
- .replace('return RunDialog(args);', `File.AppendAllText(@"${trace}", "entered"+Environment.NewLine); return 0;`)
28
- .replace('SwitchDesktop(original);', `if (SwitchDesktop(original)) File.AppendAllText(@"${trace}", "restored"+Environment.NewLine);`)
29
- const helper = path.join(root, 'probe.exe')
30
- await compileCsharp(source, helper, { outputType: 'WindowsApplication', referencedAssemblies: ['System.Windows.Forms.dll', 'System.Drawing.dll'] })
31
- const output = path.join(root, 'result.json'), runner = path.join(root, 'runner.mjs')
32
- await fs.writeFile(runner, `import{execFile}from'node:child_process';import fs from'node:fs';execFile(${JSON.stringify(helper)},['--mode','confirm','--phrase','DISPOSABLE-NATIVE-PROBE','--lines','[]'],{timeout:35000,windowsHide:true},(e,out)=>fs.writeFileSync(${JSON.stringify(output)},JSON.stringify({exitCode:e?.code??0,stdoutBytes:Buffer.byteLength(out)})));`)
33
- await setAdminOnlyAcl(root)
34
- await powershell(`$ErrorActionPreference='Stop';$a=New-ScheduledTaskAction -Execute ${quote(path.join(home, 'bin', 'node.exe'))} -Argument ${quote('"' + runner + '"')};$p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest;$s=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName ${quote(task)} -Action $a -Principal $p -Settings $s | Out-Null`)
35
- registered = true
36
- await powershell(`Start-ScheduledTask -TaskName ${quote(task)}`)
37
- const deadline = Date.now() + 60000
38
- let execution
39
- while (Date.now() < deadline) {
40
- try { execution = JSON.parse(await fs.readFile(output, 'utf8')); break }
41
- catch (e) { if (e.code !== 'ENOENT') throw e }
42
- await new Promise(resolve => setTimeout(resolve, 500))
43
- }
44
- const events = await fs.readFile(trace, 'utf8').catch(() => '')
45
- const ok = execution?.exitCode === 0 && execution.stdoutBytes === 0 && events.trim() === 'entered\r\nrestored'
46
- console.log(JSON.stringify({ ok, nativeDryRun: true, desktopEntered: events.includes('entered'), desktopRestored: events.includes('restored'), execution, evidence: root }))
47
- if (!ok) process.exitCode = 1
48
- } finally {
49
- if (registered) await powershell(`Stop-ScheduledTask -TaskName ${quote(task)} -ErrorAction SilentlyContinue;Unregister-ScheduledTask -TaskName ${quote(task)} -Confirm:$false`)
50
- }
1
+ // Run from a trusted administrator terminal after building and installing the service.
2
+ // Exercises SYSTEM relay, desktop entry, restoration and reply transport without a password UI.
3
+ import fs from 'node:fs/promises'
4
+ import path from 'node:path'
5
+ import { randomUUID } from 'node:crypto'
6
+ import { promisify } from 'node:util'
7
+ import { execFile } from 'node:child_process'
8
+ import { compileCsharp } from '../dist/platform/windows/assets/compiler.js'
9
+ import { PROMPT_HELPER_CSHARP } from '../dist/platform/windows/assets/prompt-src.js'
10
+ import { defaultAdminCheck } from '../dist/platform/windows/install.js'
11
+ import { setAdminOnlyAcl } from '../dist/platform/windows/acl.js'
12
+
13
+ if (process.platform !== 'win32' || !await defaultAdminCheck()) throw new Error('Use a trusted Windows administrator terminal')
14
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
15
+ const root = path.join(home, '#0 backup', 'desktop-probe-' + randomUUID())
16
+ const task = 'UChanDesktopProbe-' + randomUUID()
17
+ const exec = promisify(execFile)
18
+ const powershell = script => exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { windowsHide: true })
19
+ const quote = value => "'" + value.replaceAll("'", "''") + "'"
20
+ let registered = false
21
+ try {
22
+ await fs.mkdir(root)
23
+ await setAdminOnlyAcl(root)
24
+ const trace = path.join(root, 'desktop-trace.txt')
25
+ if (!PROMPT_HELPER_CSHARP.includes('return RunDialog(args);') || !PROMPT_HELPER_CSHARP.includes('SwitchDesktop(original);')) throw new Error('Native probe hooks no longer match; no helper was run')
26
+ const source = PROMPT_HELPER_CSHARP
27
+ .replace('return RunDialog(args);', `File.AppendAllText(@"${trace}", "entered"+Environment.NewLine); return 0;`)
28
+ .replace('SwitchDesktop(original);', `if (SwitchDesktop(original)) File.AppendAllText(@"${trace}", "restored"+Environment.NewLine);`)
29
+ const helper = path.join(root, 'probe.exe')
30
+ await compileCsharp(source, helper, { outputType: 'WindowsApplication', referencedAssemblies: ['System.Windows.Forms.dll', 'System.Drawing.dll'] })
31
+ const output = path.join(root, 'result.json'), runner = path.join(root, 'runner.mjs')
32
+ await fs.writeFile(runner, `import{execFile}from'node:child_process';import fs from'node:fs';execFile(${JSON.stringify(helper)},['--mode','confirm','--phrase','DISPOSABLE-NATIVE-PROBE','--lines','[]'],{timeout:35000,windowsHide:true},(e,out)=>fs.writeFileSync(${JSON.stringify(output)},JSON.stringify({exitCode:e?.code??0,stdoutBytes:Buffer.byteLength(out)})));`)
33
+ await setAdminOnlyAcl(root)
34
+ await powershell(`$ErrorActionPreference='Stop';$a=New-ScheduledTaskAction -Execute ${quote(path.join(home, 'bin', 'node.exe'))} -Argument ${quote('"' + runner + '"')};$p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest;$s=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName ${quote(task)} -Action $a -Principal $p -Settings $s | Out-Null`)
35
+ registered = true
36
+ await powershell(`Start-ScheduledTask -TaskName ${quote(task)}`)
37
+ const deadline = Date.now() + 60000
38
+ let execution
39
+ while (Date.now() < deadline) {
40
+ try { execution = JSON.parse(await fs.readFile(output, 'utf8')); break }
41
+ catch (e) { if (e.code !== 'ENOENT') throw e }
42
+ await new Promise(resolve => setTimeout(resolve, 500))
43
+ }
44
+ const events = await fs.readFile(trace, 'utf8').catch(() => '')
45
+ const ok = execution?.exitCode === 0 && execution.stdoutBytes === 0 && events.trim() === 'entered\r\nrestored'
46
+ console.log(JSON.stringify({ ok, nativeDryRun: true, desktopEntered: events.includes('entered'), desktopRestored: events.includes('restored'), execution, evidence: root }))
47
+ if (!ok) process.exitCode = 1
48
+ } finally {
49
+ if (registered) await powershell(`Stop-ScheduledTask -TaskName ${quote(task)} -ErrorAction SilentlyContinue;Unregister-ScheduledTask -TaskName ${quote(task)} -Confirm:$false`)
50
+ }
@@ -0,0 +1,56 @@
1
+ // Non-destructive checks. Run as the ordinary desktop user, never as administrator.
2
+ import fs from 'node:fs/promises'
3
+ import net from 'node:net'
4
+ import path from 'node:path'
5
+ import { defaultAdminCheck } from '../dist/platform/windows/install.js'
6
+ import { IpcClient } from '../dist/ipc/client.js'
7
+ import { defaultEndpoint } from '../dist/ipc/endpoint.js'
8
+ import { FrameReader } from '../dist/ipc/protocol.js'
9
+
10
+ if (process.platform !== 'win32' || await defaultAdminCheck()) throw new Error('Run from a non-elevated Windows terminal')
11
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
12
+ const targets = ['settings.json', 'allowlist.json', 'audit.log', 'bin/node.exe', 'bin/bw.exe', 'bin/prompt-helper.exe', 'bin/service-host.exe', 'bin/service-target.txt', 'program/package.json', 'program/dist/service/main.js', 'bin/browser/chrome.exe']
13
+ const files = []
14
+ for (const target of targets) {
15
+ const item = { target, readDenied: false, writeDenied: false }
16
+ for (const [flag, key] of [['r', 'readDenied'], ['r+', 'writeDenied']]) {
17
+ try { const handle = await fs.open(path.join(home, target), flag); await handle.close() }
18
+ catch (e) { if (e.code === 'EACCES' || e.code === 'EPERM') item[key] = true; else item[key + 'Error'] = e.code }
19
+ }
20
+ files.push(item)
21
+ }
22
+ let protectedDirectoryDenied = false
23
+ try { await fs.opendir(path.join(home, 'bw-data')).then(d => d.close()) }
24
+ catch (e) { protectedDirectoryDenied = e.code === 'EACCES' || e.code === 'EPERM' }
25
+ const endpoint = defaultEndpoint(process.env, process.platform)
26
+ const client = new IpcClient(endpoint, { timeoutMs: 10000 })
27
+ const before = await client.listAllowed()
28
+ if (!before.ok) throw new Error('Cannot establish grant baseline')
29
+ const options = [
30
+ { grant: { kind: 'timed', minutes: 0 } }, { grant: { kind: 'timed', minutes: 1441 } },
31
+ { grant: { kind: 'timed', minutes: 1.5 } }, { grant: { kind: 'once', remainingUses: 100 } },
32
+ { grant: { kind: 'permanent', expiresAt: 'never' } }, { success: { url: 'http://example.invalid/' } },
33
+ { success: { url: 'https://user:password@example.invalid/' } }, { success: { selector: '' } },
34
+ { grant: { kind: 'other' } }, { bypassApproval: true },
35
+ ]
36
+ async function raw(params) {
37
+ return new Promise((resolve, reject) => {
38
+ const socket = net.connect(endpoint), reader = new FrameReader()
39
+ const timer = setTimeout(() => { socket.destroy(); reject(new Error('Invalid request did not fail promptly')) }, 3000)
40
+ const finish = r => { clearTimeout(timer); socket.destroy(); resolve(r) }
41
+ socket.on('connect', () => socket.write(JSON.stringify({ id: 1, method: 'request_allow', params }) + '\n'))
42
+ socket.on('data', chunk => { for (const frame of reader.push(chunk)) finish(frame) })
43
+ socket.on('error', e => { clearTimeout(timer); reject(e) })
44
+ })
45
+ }
46
+ let malformedRequestsDenied = 0
47
+ for (const value of options) {
48
+ const r = await raw({ domain: 'v3-verification.invalid', loginUrl: 'https://v3-verification.invalid/', item: 'DISPOSABLE-INVALID-REQUEST', options: value })
49
+ if (r.ok !== false || r.message !== 'bad request') throw new Error('Malformed policy reached approval or mutation')
50
+ malformedRequestsDenied++
51
+ }
52
+ const after = await client.listAllowed()
53
+ const grantsUnchanged = after.ok && before.message === after.message
54
+ const ok = files.every(f => f.readDenied && f.writeDenied) && protectedDirectoryDenied && grantsUnchanged
55
+ console.log(JSON.stringify({ ok, nonElevated: true, files, protectedDirectoryDenied, malformedRequestsDenied, grantsUnchanged }))
56
+ if (!ok) process.exitCode = 1
@@ -0,0 +1,137 @@
1
+ // Disposable fixture only. Records F24 canary counts, never ordinary keystrokes or images.
2
+ using System;
3
+ using System.IO;
4
+ using System.IO.Pipes;
5
+ using System.Drawing;
6
+ using System.Diagnostics;
7
+ using System.Collections.Generic;
8
+ using System.Runtime.InteropServices;
9
+ using System.Security.Principal;
10
+ using System.Threading;
11
+ using System.Windows.Forms;
12
+
13
+ public static class UChanDesktopAttacker
14
+ {
15
+ private delegate IntPtr Hook(int code, IntPtr message, IntPtr data);
16
+ private static Hook callback = ObserveCanary;
17
+ private static int canaries;
18
+ [DllImport("user32.dll", SetLastError=true)] private static extern IntPtr SetWindowsHookEx(int kind, Hook hook, IntPtr module, uint thread);
19
+ [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hook);
20
+ [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hook, int code, IntPtr message, IntPtr data);
21
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string name);
22
+ [DllImport("user32.dll", SetLastError=true)] private static extern uint SendInput(uint count, INPUT[] inputs, int size);
23
+ [DllImport("user32.dll", SetLastError=true)] private static extern bool PostMessage(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);
24
+ [DllImport("user32.dll", SetLastError=true)] private static extern bool PrintWindow(IntPtr window, IntPtr dc, uint flags);
25
+ [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr window,int command);
26
+ [DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true)] private static extern IntPtr OpenDesktop(string name, uint flags, bool inherit, uint access);
27
+ [DllImport("user32.dll")] private static extern bool CloseDesktop(IntPtr desktop);
28
+ [StructLayout(LayoutKind.Sequential)] private struct MOUSE { public int x,y; public uint data,flags,time; public IntPtr extra; }
29
+ [StructLayout(LayoutKind.Sequential)] private struct KEY { public ushort key,scan; public uint flags,time; public IntPtr extra; }
30
+ [StructLayout(LayoutKind.Explicit)] private struct UNION { [FieldOffset(0)] public MOUSE mouse; [FieldOffset(0)] public KEY keyboard; }
31
+ [StructLayout(LayoutKind.Sequential)] private struct INPUT { public uint kind; public UNION data; }
32
+
33
+ private static IntPtr ObserveCanary(int code, IntPtr message, IntPtr data)
34
+ {
35
+ if(code >= 0 && Marshal.ReadInt32(data) == 0x87) canaries++;
36
+ return CallNextHookEx(IntPtr.Zero,code,message,data);
37
+ }
38
+ private static uint Key(ushort key)
39
+ {
40
+ INPUT[] values = new INPUT[2];
41
+ values[0].kind=1; values[0].data.keyboard.key=key;
42
+ values[1].kind=1; values[1].data.keyboard.key=key; values[1].data.keyboard.flags=2;
43
+ return SendInput(2,values,Marshal.SizeOf(typeof(INPUT)));
44
+ }
45
+ private static void Pump(int milliseconds)
46
+ {
47
+ Stopwatch watch=Stopwatch.StartNew();
48
+ while(watch.ElapsedMilliseconds < milliseconds){ Application.DoEvents(); Thread.Sleep(5); }
49
+ }
50
+ private static int Markers(Bitmap image)
51
+ {
52
+ int count=0;
53
+ for(int x=0;x<image.Width;x+=4) for(int y=0;y<image.Height;y+=4)
54
+ { Color c=image.GetPixel(x,y); if(c.R==1 && c.G==254 && c.B==71) count++; }
55
+ return count;
56
+ }
57
+ private static bool RenderMarker(IntPtr window, int width, int height)
58
+ {
59
+ using(Bitmap bitmap=new Bitmap(width,height)) using(Graphics graphics=Graphics.FromImage(bitmap))
60
+ {
61
+ IntPtr dc=graphics.GetHdc();
62
+ try { PrintWindow(window,dc,0); } finally { graphics.ReleaseHdc(dc); }
63
+ return Markers(bitmap)>32;
64
+ }
65
+ }
66
+ private static bool DeniedDesktop(string name,uint access)
67
+ {
68
+ IntPtr desktop=OpenDesktop(name,0,false,access);
69
+ if(desktop!=IntPtr.Zero){CloseDesktop(desktop);return false;}
70
+ return Marshal.GetLastWin32Error()==5;
71
+ }
72
+ private static void Put(List<string> results,string key,object value){results.Add(key+"="+value);}
73
+
74
+ [STAThread] public static int Main(string[] args)
75
+ {
76
+ List<string> results=new List<string>();
77
+ IntPtr hook=IntPtr.Zero;
78
+ try
79
+ {
80
+ bool medium=!WindowsIdentity.GetCurrent().IsSystem && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
81
+ Put(results,"nonElevated",medium);
82
+ if(!medium)throw new InvalidOperationException("Non-elevated caller required");
83
+ hook=SetWindowsHookEx(13,callback,GetModuleHandle(null),0);
84
+ if(hook==IntPtr.Zero)throw new InvalidOperationException("Hook positive control unavailable");
85
+ bool captureControl;
86
+ using(Form control=new Form())
87
+ {
88
+ control.Text="U-Chan disposable positive control";
89
+ control.ClientSize=new Size(96,96); control.BackColor=Color.FromArgb(1,254,71); control.TopMost=true;
90
+ control.Show(); ShowWindow(control.Handle,5); control.Activate(); control.Update(); Pump(200);
91
+ captureControl=RenderMarker(control.Handle,128,128);
92
+ Key(0x87); Pump(300);
93
+ control.Close();
94
+ }
95
+ Put(results,"hookPositiveControl",canaries>0); Put(results,"capturePositiveControl",captureControl);
96
+ if(canaries==0 || !captureControl)throw new InvalidOperationException("Positive control failed");
97
+ canaries=0;
98
+ File.WriteAllText(args[1]+".armed","ready");
99
+ Stopwatch watch=Stopwatch.StartNew();
100
+ while(!File.Exists(args[0]+".metadata") && watch.ElapsedMilliseconds<120000)Pump(20);
101
+ string[] metadata=File.ReadAllLines(args[0]+".metadata");
102
+ if(metadata.Length!=6 || metadata[4]!=args[2])throw new InvalidOperationException("Stale fixture metadata");
103
+ Put(results,"runId",args[2]);
104
+ Put(results,"desktopReadDenied",DeniedDesktop(metadata[0],1));
105
+ Put(results,"desktopHookDenied",DeniedDesktop(metadata[0],8));
106
+ Put(results,"desktopSwitchDenied",DeniedDesktop(metadata[0],0x100));
107
+ bool pipeDenied=false,spoofDenied=false,connected=false;
108
+ try {using(NamedPipeClientStream pipe=new NamedPipeClientStream(".",metadata[5],PipeDirection.Out)){pipe.Connect(300);connected=true;}}
109
+ catch(UnauthorizedAccessException){pipeDenied=true;}
110
+ catch(IOException e){pipeDenied=(e.HResult&0xffff)==5;Put(results,"replyPipeErrorCode",e.HResult&0xffff);}
111
+ catch(TimeoutException){Put(results,"replyPipeErrorCode","timeout");}
112
+ Put(results,"replyPipeConnectDenied",pipeDenied);
113
+ Put(results,"replyPipeConnected",connected);
114
+ try {using(NamedPipeServerStream pipe=new NamedPipeServerStream(metadata[1],PipeDirection.In,1)){}}
115
+ catch(IOException){spoofDenied=true;} catch(UnauthorizedAccessException){spoofDenied=true;}
116
+ Put(results,"replyPipeSpoofDenied",spoofDenied);
117
+ IntPtr form=new IntPtr(long.Parse(metadata[2])), button=new IntPtr(long.Parse(metadata[3]));
118
+ Put(results,"postClickAccepted",PostMessage(button,0xF5,IntPtr.Zero,IntPtr.Zero));
119
+ Put(results,"sendInputCount",Key(0x0D));
120
+ Put(results,"printWindowSawMarker",RenderMarker(form,500,420));
121
+ bool screenMarker=false;
122
+ try {
123
+ Rectangle bounds=Screen.PrimaryScreen.Bounds;
124
+ using(Bitmap image=new Bitmap(bounds.Width,bounds.Height)) using(Graphics graphics=Graphics.FromImage(image))
125
+ { graphics.CopyFromScreen(bounds.Location,Point.Empty,bounds.Size); screenMarker=Markers(image)>32; }
126
+ } catch(System.ComponentModel.Win32Exception) { }
127
+ Put(results,"screenCaptureSawMarker",screenMarker);
128
+ watch.Restart();
129
+ while(!File.Exists(args[0]+".done") && watch.ElapsedMilliseconds<10000)Pump(20);
130
+ Pump(100); Put(results,"protectedCanaryEvents",canaries);
131
+ }
132
+ catch(Exception e){Put(results,"errorType",e.GetType().Name);Put(results,"errorCode",e.HResult&0xffff);}
133
+ finally {if(hook!=IntPtr.Zero)UnhookWindowsHookEx(hook);}
134
+ File.WriteAllLines(args[1]+".attacker",results);
135
+ return 0;
136
+ }
137
+ }
@@ -0,0 +1,125 @@
1
+ // SYSTEM-only disposable browser fixture. Random credentials stay in this process's memory.
2
+ import fs from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { createServer } from 'node:http'
6
+ import { randomUUID } from 'node:crypto'
7
+ import { execFile, spawn } from 'node:child_process'
8
+ import { promisify } from 'node:util'
9
+ import { moduleAuditSucceeded } from './result-policy.mjs'
10
+ const exec = promisify(execFile), root = path.resolve(process.argv[2])
11
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
12
+ if (!root.startsWith(path.resolve(home, '#0 backup') + path.sep)) throw new Error('Unsafe fixture path')
13
+ const result = { ok: false }, started = Date.now()
14
+ const load = relative => import(pathToFileURL(path.join(home, 'program', relative)).href)
15
+ const { stdout: identity } = await exec('whoami.exe', ['/user'], { windowsHide: true })
16
+ if (!identity.includes('S-1-5-18')) throw new Error('SYSTEM runner required')
17
+ const username = randomUUID() + '@example.invalid', password = randomUUID() + randomUUID(), cookie = 'u_chan_probe_' + randomUUID().replaceAll('-', '')
18
+ let server, shared, userPage, browser, provider, bitwarden, bwTimer
19
+ let receivedCredentials = false
20
+ try {
21
+ const { chromium } = await load('node_modules/playwright/index.mjs')
22
+ const { IsolatedLogin } = await load('dist/service/isolated-login.js')
23
+ const { CdpBrowserProvider, CdpSessionTarget } = await load('dist/core/browser.js')
24
+ const { privateBrowserPath } = await load('dist/platform/runtime-assets.js')
25
+ const form = '<!doctype html><title>U-Chan disposable security fixture</title><form method="post" action="/login"><input type="email" name="email"><input type="password" name="password"><button type="submit">Login</button></form>'
26
+ server = createServer((req, res) => {
27
+ if (req.method === 'POST') {
28
+ let body = ''; req.on('data', part => { body += part }); req.on('end', () => {
29
+ const fields = new URLSearchParams(body)
30
+ receivedCredentials = fields.get('email') === username && fields.get('password') === password
31
+ res.writeHead(303, { location: '/account', 'set-cookie': cookie + '=fixture; Path=/; HttpOnly; SameSite=Lax' }); res.end()
32
+ }); return
33
+ }
34
+ res.writeHead(200, { 'content-type': 'text/html' })
35
+ res.end(req.url === '/account' && req.headers.cookie?.includes(cookie + '=fixture') ? '<h1 id="signed-in">Disposable signed-in fixture</h1>' : form)
36
+ })
37
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
38
+ const origin = 'http://127.0.0.1:' + server.address().port
39
+ shared = await chromium.connectOverCDP('http://127.0.0.1:9222')
40
+ userPage = await shared.contexts()[0].newPage()
41
+ await userPage.addInitScript(() => { window.__uChanProbeInputCount = 0; document.addEventListener('input', () => { window.__uChanProbeInputCount++ }, true) })
42
+ await userPage.goto(origin)
43
+ await userPage.locator('input[type=email]').fill('positive-control@example.invalid')
44
+ result.initScriptPositiveControl = await userPage.evaluate(() => window.__uChanProbeInputCount > 0)
45
+ await userPage.goto(origin)
46
+ browser = await chromium.launch({ headless: true, executablePath: await privateBrowserPath(home) })
47
+ provider = new CdpBrowserProvider('http://127.0.0.1:9222')
48
+ const target = new CdpSessionTarget(provider)
49
+ const login = new IsolatedLogin({ launch: async () => browser, target, timeoutMs: 15000, hostCheck: url => new URL(url).hostname === '127.0.0.1' })
50
+ const outcome = await login.run({ domain: '127.0.0.1', hosts: ['127.0.0.1'], item: 'disposable', loginUrl: origin + '/login', success: { selector: '#signed-in' } }, async () => ({ username, password }))
51
+ result.confirmedFixtureLogin = outcome.status === 'logged_in' && receivedCredentials
52
+ result.sharedInputEvents = await userPage.evaluate(() => window.__uChanProbeInputCount)
53
+ await userPage.goto(origin + '/account')
54
+ result.transferredFixtureSession = await userPage.locator('#signed-in').isVisible()
55
+ const { CoreService } = await load('dist/core/service.js')
56
+ const { AllowlistStore } = await load('dist/core/allowlist.js')
57
+ const { GrantCoordinator } = await load('dist/service/grants.js')
58
+ const grantPath = path.join(root, 'fixture-grants.json'), store = new AllowlistStore(grantPath)
59
+ const once = { domain: '127.0.0.1', hosts: ['127.0.0.1'], item: 'disposable', loginUrl: origin + '/login', success: { selector: '#signed-in' }, grantId: randomUUID(), remainingUses: 1 }
60
+ await store.upsert(once)
61
+ let credentialReads = 0
62
+ const deps = { vault: { status: async () => 'unlocked', getLogin: async () => { credentialReads++; return { username, password } } }, allowlist: store, approver: { ask: async () => false }, audit: { record: async () => {} }, login, mode: async () => 'easy' }
63
+ const core = new CoreService(deps), attempts = await Promise.all([core.login(once.domain), core.login(once.domain)])
64
+ const restarted = new CoreService({ ...deps, allowlist: new AllowlistStore(grantPath) })
65
+ const replay = await restarted.login(once.domain)
66
+ result.onceGrantConcurrency = attempts.filter(r => r.ok).length === 1 && attempts.filter(r => r.code === 'grant_exhausted').length === 1 && credentialReads === 1 && replay.code === 'grant_exhausted'
67
+ let wall = Date.now(), mono = 0
68
+ const timed = { ...once, grantId: randomUUID(), remainingUses: undefined, expiresAt: new Date(wall + 60000).toISOString() }
69
+ await store.upsert(timed)
70
+ const clock = new GrantCoordinator(store, { now: () => wall, monotonic: () => mono })
71
+ wall -= 5000; mono += 1000
72
+ await clock.run(async () => {})
73
+ result.clockRollbackPersisted = (await new AllowlistStore(grantPath).get(timed.domain)).expiresAt === new Date(0).toISOString()
74
+ await fs.mkdir(path.join(root, 'bw-fixture'))
75
+ bitwarden = spawn(path.join(home, 'bin', 'bw.exe'), ['status'], { windowsHide: true, stdio: 'ignore', env: { ...process.env, BITWARDENCLI_APPDATA_DIR: path.join(root, 'bw-fixture'), BW_NOINTERACTION: 'true' } })
76
+ const bwFinished = new Promise(resolve => bitwarden.once('exit', resolve))
77
+ bwTimer = setTimeout(() => bitwarden.kill(), 15000)
78
+ const body = await fs.readFile(new URL('./module-audit.ps1', import.meta.url), 'utf8')
79
+ const encodedHome = Buffer.from(home, 'utf8').toString('base64')
80
+ const script = '$auditProcessId=' + process.pid + ';$auditServiceHome=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(' + JSON.stringify(encodedHome) + '));\n' + body
81
+ const audit = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { windowsHide: true, timeout: 20000 })
82
+ result.nativeModules = JSON.parse(audit.stdout)
83
+ await bwFinished; clearTimeout(bwTimer)
84
+ let scanned = 0, matches = 0
85
+ for (const file of [path.join(home, 'audit.log'), path.join(home, 'service.log')]) {
86
+ const text = await fs.readFile(file, 'utf8').catch(() => '')
87
+ if (text) { scanned++; if (text.includes(password) || text.includes(username)) matches++ }
88
+ }
89
+ // SYSTEM's USERPROFILE differs; the trusted administrator passes the desktop transcript directory.
90
+ for (const session of process.argv[3] ? [process.argv[3]] : []) {
91
+ for (const file of await fs.readdir(session, { recursive: true }).catch(() => [])) {
92
+ if (!file.endsWith('.jsonl')) continue
93
+ const full = path.join(session, file), stat = await fs.stat(full).catch(() => null)
94
+ if (!stat || stat.mtimeMs < started || stat.size > 100 * 1024 * 1024) continue
95
+ const text = await fs.readFile(full, 'utf8'); scanned++; if (text.includes(password) || text.includes(username)) matches++
96
+ }
97
+ }
98
+ result.testSecretScan = { artifacts: scanned, matches }
99
+ const { NpmPackageVerifier } = await load('dist/service/update/verifier.js')
100
+ const { ServiceUpdater } = await load('dist/service/update/updater.js')
101
+ const verifier = new NpmPackageVerifier(), verified = await verifier.verify('3.0.2')
102
+ result.realRegistryVerification = verified.ok
103
+ if (verified.ok) {
104
+ const updateHome = path.join(root, 'update-fixture'), programDir = path.join(updateHome, 'program')
105
+ await fs.mkdir(path.join(programDir, 'node_modules'), { recursive: true })
106
+ const pkg = JSON.parse(await fs.readFile(path.join(home, 'program', 'package.json'), 'utf8'))
107
+ await fs.writeFile(path.join(programDir, 'package.json'), JSON.stringify({ ...pkg, version: '3.0.1' }))
108
+ await fs.writeFile(path.join(programDir, 'node_modules', 'private-marker'), 'preserve-private-dependencies')
109
+ const state = JSON.stringify({ protectedFixture: randomUUID() })
110
+ await fs.writeFile(path.join(updateHome, 'settings.json'), state)
111
+ let restarts = 0
112
+ const update = new ServiceUpdater({ programDir, stagingDir: path.join(updateHome, 'staging'), currentVersion: '3.0.1', verifier: { checkLatest: async () => ({ isNewer: true, latestVersion: '3.0.2' }), verify: async () => verified }, restarter: { restart: async () => { restarts++ } } })
113
+ const updated = await update.update()
114
+ await new Promise(resolve => setImmediate(resolve))
115
+ result.signedUpdateRehearsal = updated.ok && JSON.parse(await fs.readFile(path.join(programDir, 'package.json'), 'utf8')).version === '3.0.2' && await fs.readFile(path.join(programDir, 'node_modules', 'private-marker'), 'utf8') === 'preserve-private-dependencies' && await fs.readFile(path.join(updateHome, 'settings.json'), 'utf8') === state && restarts === 1
116
+ }
117
+ result.ok = result.initScriptPositiveControl && result.confirmedFixtureLogin && result.sharedInputEvents === 0 && result.transferredFixtureSession && result.onceGrantConcurrency && result.clockRollbackPersisted && moduleAuditSucceeded(result.nativeModules) && result.realRegistryVerification && result.signedUpdateRehearsal && matches === 0
118
+ } catch (e) { result.errorType = e.name }
119
+ finally {
120
+ clearTimeout(bwTimer); if (bitwarden && bitwarden.exitCode === null) bitwarden.kill()
121
+ if (shared) await shared.contexts()[0].clearCookies({ name: cookie }).catch(() => {})
122
+ await userPage?.close().catch(() => {}); await provider?.disconnect().catch(() => {}); await browser?.close().catch(() => {}); await shared?.close().catch(() => {})
123
+ if (server) await new Promise(resolve => { server.close(resolve); server.closeAllConnections() })
124
+ }
125
+ await fs.writeFile(path.join(root, 'browser-result.json'), JSON.stringify(result, null, 2))
@@ -0,0 +1,16 @@
1
+ // SYSTEM-side fixture: emits only the non-text F24 canary, never password data.
2
+ public static class UChanProbeCanary
3
+ {
4
+ [System.Runtime.InteropServices.DllImport("user32.dll",SetLastError=true)] private static extern uint SendInput(uint count, INPUT[] values,int size);
5
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct MOUSE { public int x,y; public uint data,flags,time; public System.IntPtr extra; }
6
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct KEY { public ushort key,scan; public uint flags,time; public System.IntPtr extra; }
7
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)] private struct UNION { [System.Runtime.InteropServices.FieldOffset(0)] public MOUSE mouse; [System.Runtime.InteropServices.FieldOffset(0)] public KEY keyboard; }
8
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct INPUT { public uint kind; public UNION data; }
9
+ public static uint Send()
10
+ {
11
+ INPUT[] values=new INPUT[2];
12
+ values[0].kind=1;values[0].data.keyboard.key=0x87;
13
+ values[1].kind=1;values[1].data.keyboard.key=0x87;values[1].data.keyboard.flags=2;
14
+ return SendInput(2,values,System.Runtime.InteropServices.Marshal.SizeOf(typeof(INPUT)));
15
+ }
16
+ }