u-chan-secrets 4.0.0 → 5.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.
- package/README.md +30 -6
- package/dist/cli.js +30 -23
- package/dist/client/browser-choice.js +49 -0
- package/dist/client/codex-setup.js +11 -4
- package/dist/client/setup.js +17 -5
- package/dist/core/browser.js +90 -22
- package/dist/core/config.js +13 -0
- package/dist/core/doctor.js +26 -5
- package/dist/core/login-filler.js +25 -25
- package/dist/core/login-success.js +24 -0
- package/dist/core/service.js +6 -4
- package/dist/core/types.js +1 -1
- package/dist/index.js +2 -2
- package/dist/ipc/client.js +20 -1
- package/dist/ipc/server.js +1 -1
- package/dist/mcp/server.js +4 -4
- package/dist/platform/darwin/manage-script.js +30 -30
- package/dist/platform/windows/desktop-browser.js +14 -0
- package/dist/platform/windows/doctor.js +1 -1
- package/dist/service/control.js +1 -1
- package/dist/service/doctor.js +5 -4
- package/dist/service/isolated-login.js +33 -10
- package/dist/service/main.js +9 -3
- package/dist/service/manage.js +3 -2
- package/dist/service/private-network.js +97 -0
- package/package.json +1 -1
- package/scripts/debug-install.bat +69 -69
- package/scripts/desktop-browser.ps1 +103 -0
- package/scripts/login-bw.bat +21 -21
- package/scripts/migrate.bat +4 -4
- package/scripts/start-browser.ps1 +33 -0
- package/scripts/start-chrome.ps1 +4 -38
- package/scripts/verify-windows-adversarial.mjs +11 -1
- package/scripts/windows-security/browser-worker.mjs +50 -10
- package/scripts/windows-security/desktop-matrix.mjs +54 -0
|
@@ -10,13 +10,18 @@ import { moduleAuditSucceeded } from './result-policy.mjs'
|
|
|
10
10
|
const exec = promisify(execFile), root = path.resolve(process.argv[2])
|
|
11
11
|
const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
|
|
12
12
|
if (!root.startsWith(path.resolve(home, '#0 backup') + path.sep)) throw new Error('Unsafe fixture path')
|
|
13
|
+
const program = process.argv[4] ? path.resolve(process.argv[4]) : path.join(home, 'program')
|
|
14
|
+
if (program !== path.join(home, 'program') && !program.startsWith(path.resolve(home, '#0 backup') + path.sep)) throw new Error('Unsafe candidate path')
|
|
15
|
+
const endpoint = process.argv[5] ?? 'http://127.0.0.1:9222', matrix = process.argv[6] === 'matrix'
|
|
13
16
|
const result = { ok: false }, started = Date.now()
|
|
14
|
-
const load = relative => import(pathToFileURL(path.join(
|
|
17
|
+
const load = relative => import(pathToFileURL(path.join(program, relative)).href)
|
|
15
18
|
const { stdout: identity } = await exec('whoami.exe', ['/user'], { windowsHide: true })
|
|
16
19
|
if (!identity.includes('S-1-5-18')) throw new Error('SYSTEM runner required')
|
|
17
20
|
const username = randomUUID() + '@example.invalid', password = randomUUID() + randomUUID(), cookie = 'u_chan_probe_' + randomUUID().replaceAll('-', '')
|
|
18
21
|
let server, shared, userPage, browser, provider, bitwarden, bwTimer
|
|
19
22
|
let receivedCredentials = false
|
|
23
|
+
let sharedInputCount = 0
|
|
24
|
+
let matrixMetadata
|
|
20
25
|
try {
|
|
21
26
|
const { chromium } = await load('node_modules/playwright/index.mjs')
|
|
22
27
|
const { IsolatedLogin } = await load('dist/service/isolated-login.js')
|
|
@@ -36,20 +41,41 @@ try {
|
|
|
36
41
|
})
|
|
37
42
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
|
38
43
|
const origin = 'http://127.0.0.1:' + server.address().port
|
|
39
|
-
shared = await chromium.connectOverCDP(
|
|
44
|
+
shared = await chromium.connectOverCDP(endpoint)
|
|
45
|
+
if (matrix) {
|
|
46
|
+
const binding = '__uChanMatrixInput' + randomUUID().replaceAll('-', '')
|
|
47
|
+
await shared.contexts()[0].exposeBinding(binding, () => { sharedInputCount++ })
|
|
48
|
+
await shared.contexts()[0].addInitScript(name => document.addEventListener('input', () => { void window[name]().catch(() => {}) }, true), binding)
|
|
49
|
+
const { inspectServiceDesktopBrowser } = await load('dist/platform/windows/desktop-browser.js')
|
|
50
|
+
matrixMetadata = JSON.parse(await fs.readFile(path.join(root, 'matrix-context.json'), 'utf8'))
|
|
51
|
+
const { observeCdpBrowser } = await load('dist/core/doctor.js')
|
|
52
|
+
result.fixtureBrowser = await observeCdpBrowser(endpoint)
|
|
53
|
+
result.desktopIdentity = await inspectServiceDesktopBrowser(endpoint, matrixMetadata.ownerSid, { ...process.env, U_CHAN_DESKTOP_HOME: matrixMetadata.desktopHome }, result.fixtureBrowser)
|
|
54
|
+
result.wrongBrowserRejected = await inspectServiceDesktopBrowser(endpoint, matrixMetadata.ownerSid, { ...process.env, U_CHAN_DESKTOP_HOME: matrixMetadata.desktopHome }, result.fixtureBrowser === 'edge' ? 'chrome' : 'edge') !== 'pass'
|
|
55
|
+
}
|
|
40
56
|
userPage = await shared.contexts()[0].newPage()
|
|
41
57
|
await userPage.addInitScript(() => { window.__uChanProbeInputCount = 0; document.addEventListener('input', () => { window.__uChanProbeInputCount++ }, true) })
|
|
42
58
|
await userPage.goto(origin)
|
|
43
59
|
await userPage.locator('input[type=email]').fill('positive-control@example.invalid')
|
|
44
60
|
result.initScriptPositiveControl = await userPage.evaluate(() => window.__uChanProbeInputCount > 0)
|
|
61
|
+
if (matrix) { await new Promise(resolve => setTimeout(resolve, 150)); result.allPageInputPositiveControl = sharedInputCount > 0; sharedInputCount = 0 }
|
|
45
62
|
await userPage.goto(origin)
|
|
46
|
-
browser = await chromium.launch({ headless: true, executablePath: await privateBrowserPath(home) })
|
|
47
|
-
provider = new CdpBrowserProvider(
|
|
48
|
-
|
|
63
|
+
browser = await chromium.launch({ headless: true, executablePath: await privateBrowserPath(home), args: ['--disable-http2', '--disable-quic'] })
|
|
64
|
+
provider = new CdpBrowserProvider(endpoint, matrixMetadata ? { validate: async requested => {
|
|
65
|
+
const { inspectServiceDesktopBrowser } = await load('dist/platform/windows/desktop-browser.js')
|
|
66
|
+
return await inspectServiceDesktopBrowser(endpoint, matrixMetadata.ownerSid, { ...process.env, U_CHAN_DESKTOP_HOME: matrixMetadata.desktopHome }, requested) === 'pass'
|
|
67
|
+
} } : {})
|
|
68
|
+
const target = new CdpSessionTarget(provider, { hostCheck: (_entry, url) => new URL(url).hostname === '127.0.0.1' })
|
|
69
|
+
if (matrix) {
|
|
70
|
+
await provider.withPage(async page => { await page.goto(origin); await page.locator('input[type=email]').fill('cross-connection-control@example.invalid') }, undefined, result.fixtureBrowser)
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, 150))
|
|
72
|
+
result.crossConnectionInputPositiveControl = sharedInputCount > 0
|
|
73
|
+
sharedInputCount = 0
|
|
74
|
+
}
|
|
49
75
|
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 }))
|
|
76
|
+
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 }), { browser: result.fixtureBrowser ?? 'chrome' })
|
|
51
77
|
result.confirmedFixtureLogin = outcome.status === 'logged_in' && receivedCredentials
|
|
52
|
-
result.sharedInputEvents = await userPage.evaluate(() => window.__uChanProbeInputCount)
|
|
78
|
+
result.sharedInputEvents = matrix ? sharedInputCount : await userPage.evaluate(() => window.__uChanProbeInputCount)
|
|
53
79
|
await userPage.goto(origin + '/account')
|
|
54
80
|
result.transferredFixtureSession = await userPage.locator('#signed-in').isVisible()
|
|
55
81
|
const { CoreService } = await load('dist/core/service.js')
|
|
@@ -60,9 +86,9 @@ try {
|
|
|
60
86
|
await store.upsert(once)
|
|
61
87
|
let credentialReads = 0
|
|
62
88
|
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)])
|
|
89
|
+
const core = new CoreService(deps), attempts = await Promise.all([core.login(once.domain, result.fixtureBrowser ?? 'chrome'), core.login(once.domain, result.fixtureBrowser ?? 'chrome')])
|
|
64
90
|
const restarted = new CoreService({ ...deps, allowlist: new AllowlistStore(grantPath) })
|
|
65
|
-
const replay = await restarted.login(once.domain)
|
|
91
|
+
const replay = await restarted.login(once.domain, result.fixtureBrowser ?? 'chrome')
|
|
66
92
|
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
93
|
let wall = Date.now(), mono = 0
|
|
68
94
|
const timed = { ...once, grantId: randomUUID(), remainingUses: undefined, expiresAt: new Date(wall + 60000).toISOString() }
|
|
@@ -71,6 +97,19 @@ try {
|
|
|
71
97
|
wall -= 5000; mono += 1000
|
|
72
98
|
await clock.run(async () => {})
|
|
73
99
|
result.clockRollbackPersisted = (await new AllowlistStore(grantPath).get(timed.domain)).expiresAt === new Date(0).toISOString()
|
|
100
|
+
if (matrix) {
|
|
101
|
+
const { createPrivateNetwork } = await load('dist/service/private-network.js')
|
|
102
|
+
const network = await createPrivateNetwork(url => new URL(url).protocol === 'https:' && new URL(url).hostname === 'example.com', AbortSignal.timeout(20000))
|
|
103
|
+
const context = await browser.newContext({ serviceWorkers: 'block', proxy: network.proxy })
|
|
104
|
+
try {
|
|
105
|
+
const page = await context.newPage()
|
|
106
|
+
const response = await page.goto('https://example.com/', { waitUntil: 'domcontentloaded', timeout: 15000 })
|
|
107
|
+
const security = await response?.securityDetails()
|
|
108
|
+
result.privateHttpsHttpStatus = response?.status()
|
|
109
|
+
result.privateHttpsProtocol = security?.protocol?.startsWith('TLS') ? security.protocol : 'unknown'
|
|
110
|
+
result.privateHttpsCertificateValidated = !!response && new URL(response.url()).origin === 'https://example.com' && new URL(page.url()).origin === 'https://example.com' && result.privateHttpsProtocol !== 'unknown'
|
|
111
|
+
} finally { await context.close(); await network.close() }
|
|
112
|
+
}
|
|
74
113
|
await fs.mkdir(path.join(root, 'bw-fixture'))
|
|
75
114
|
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
115
|
const bwFinished = new Promise(resolve => bitwarden.once('exit', resolve))
|
|
@@ -103,7 +142,7 @@ try {
|
|
|
103
142
|
if (verified.ok) {
|
|
104
143
|
const updateHome = path.join(root, 'update-fixture'), programDir = path.join(updateHome, 'program')
|
|
105
144
|
await fs.mkdir(path.join(programDir, 'node_modules'), { recursive: true })
|
|
106
|
-
const pkg = JSON.parse(await fs.readFile(path.join(
|
|
145
|
+
const pkg = JSON.parse(await fs.readFile(path.join(program, 'package.json'), 'utf8'))
|
|
107
146
|
await fs.writeFile(path.join(programDir, 'package.json'), JSON.stringify({ ...pkg, version: '3.0.1' }))
|
|
108
147
|
await fs.writeFile(path.join(programDir, 'node_modules', 'private-marker'), 'preserve-private-dependencies')
|
|
109
148
|
const state = JSON.stringify({ protectedFixture: randomUUID() })
|
|
@@ -115,6 +154,7 @@ try {
|
|
|
115
154
|
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
155
|
}
|
|
117
156
|
result.ok = result.initScriptPositiveControl && result.confirmedFixtureLogin && result.sharedInputEvents === 0 && result.transferredFixtureSession && result.onceGrantConcurrency && result.clockRollbackPersisted && moduleAuditSucceeded(result.nativeModules) && result.realRegistryVerification && result.signedUpdateRehearsal && matches === 0
|
|
157
|
+
if (matrix) result.ok &&= result.allPageInputPositiveControl && result.crossConnectionInputPositiveControl && result.desktopIdentity === 'pass' && result.wrongBrowserRejected && result.privateHttpsCertificateValidated && ['chrome','edge'].includes(result.fixtureBrowser)
|
|
118
158
|
} catch (e) { result.errorType = e.name }
|
|
119
159
|
finally {
|
|
120
160
|
clearTimeout(bwTimer); if (bitwarden && bitwarden.exitCode === null) bitwarden.kill()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// User-side half of the disposable Windows browser matrix. The administrator runs the SYSTEM fixture separately.
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { createServer } from 'node:net'
|
|
7
|
+
import { execFile } from 'node:child_process'
|
|
8
|
+
import { promisify } from 'node:util'
|
|
9
|
+
import { chromium } from 'playwright'
|
|
10
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
11
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
12
|
+
import { isElevated } from '../../dist/client/codex-setup.js'
|
|
13
|
+
import { inspectDesktopBrowser } from '../../dist/platform/windows/desktop-browser.js'
|
|
14
|
+
const [command, browser, runId] = process.argv.slice(2)
|
|
15
|
+
if (process.platform !== 'win32' || command !== '--prepare' || !['chrome','edge'].includes(browser) || !/^[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('Use --prepare chrome|edge <fresh UUID> on Windows')
|
|
16
|
+
if (await isElevated()) throw new Error('Prepare must run as the non-elevated desktop owner')
|
|
17
|
+
const root = path.join(os.tmpdir(), 'u-chan-v5-matrix-' + runId), home = path.join(root, browser)
|
|
18
|
+
await fs.mkdir(root) // A fresh UUID must never reuse another fixture or personal profile.
|
|
19
|
+
await fs.mkdir(home)
|
|
20
|
+
const port = await new Promise((resolve, reject) => { const server = createServer(); server.once('error', reject); server.listen(0, '127.0.0.1', () => { const selected = server.address().port; server.close(() => resolve(selected)) }) })
|
|
21
|
+
const endpoint = 'http://127.0.0.1:' + port, environment = { ...process.env, U_CHAN_HOME: home, U_CHAN_CDP: endpoint, U_CHAN_BROWSER: browser }
|
|
22
|
+
const report = { runId, browser, ok: false }, exec = promisify(execFile)
|
|
23
|
+
let connection
|
|
24
|
+
try {
|
|
25
|
+
await exec('powershell.exe', ['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',fileURLToPath(new URL('../start-browser.ps1',import.meta.url)),'-Browser',browser,'-Background'], { env: environment, windowsHide: true, timeout: 30000 })
|
|
26
|
+
report.desktopIdentity = await inspectDesktopBrowser({ endpoint, browser, profile: path.join(home,browser+'-profile') },environment)
|
|
27
|
+
if (report.desktopIdentity !== 'pass') throw new Error('Disposable browser identity failed')
|
|
28
|
+
connection = await chromium.connectOverCDP(endpoint)
|
|
29
|
+
await fs.writeFile(path.join(root,'prepared.json'),JSON.stringify({runId,browser,home,endpoint}),{flag:'wx'})
|
|
30
|
+
console.log(JSON.stringify({prepared:true,runId,browser,endpoint}))
|
|
31
|
+
const receipt = path.join(process.env.ProgramData ?? 'C:\\ProgramData','UChanSecurityChecks',runId,'report.json'), deadline=Date.now()+600000
|
|
32
|
+
while(Date.now()<deadline&&!await fs.stat(receipt).catch(()=>null))await new Promise(resolve=>setTimeout(resolve,500))
|
|
33
|
+
const native = JSON.parse(await fs.readFile(receipt,'utf8'))
|
|
34
|
+
report.systemFixturePassed = native.ok === true && native.fixtureBrowser === browser && native.desktopIdentity === 'pass'
|
|
35
|
+
const client = new Client({name:'u-chan-browser-matrix',version:'1.0.0'})
|
|
36
|
+
const transport = new StdioClientTransport({command:process.execPath,args:[path.join(path.dirname(process.execPath),'node_modules/npm/bin/npx-cli.js'),'@playwright/mcp@latest','--cdp-endpoint',endpoint],stderr:'pipe'})
|
|
37
|
+
try {
|
|
38
|
+
await client.connect(transport)
|
|
39
|
+
report.playwrightTools=(await client.listTools()).tools.length
|
|
40
|
+
report.sharedBrowserListing=!(await client.callTool({name:'browser_tabs',arguments:{action:'list'}})).isError
|
|
41
|
+
} finally {await client.close().catch(()=>{});await transport.close().catch(()=>{})}
|
|
42
|
+
report.ok=report.systemFixturePassed&&report.playwrightTools>0&&report.sharedBrowserListing
|
|
43
|
+
}catch(error){report.errorType=error.name}
|
|
44
|
+
finally{
|
|
45
|
+
// Only this verified disposable instance may be closed; never enumerate or terminate other browsers.
|
|
46
|
+
if(connection&&await inspectDesktopBrowser({endpoint,browser,profile:path.join(home,browser+'-profile')},environment)==='pass') {
|
|
47
|
+
const session=await connection.newBrowserCDPSession().catch(()=>null)
|
|
48
|
+
await session?.send('Browser.close').catch(()=>{})
|
|
49
|
+
}
|
|
50
|
+
await connection?.close().catch(()=>{})
|
|
51
|
+
await fs.writeFile(path.join(root,'complete.json'),JSON.stringify(report,null,2),{flag:'wx'})
|
|
52
|
+
}
|
|
53
|
+
console.log(JSON.stringify(report))
|
|
54
|
+
if(!report.ok)process.exitCode=1
|