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.
- package/README.md +38 -3
- package/dist/cli.js +79 -2
- package/dist/client/allow.js +64 -0
- package/dist/client/codex-setup.js +140 -0
- package/dist/client/setup.js +67 -0
- package/dist/core/doctor.js +45 -7
- package/dist/index.js +2 -1
- package/dist/mcp/server.js +2 -2
- package/dist/platform/darwin/manage-script.js +30 -30
- package/dist/platform/windows/assets/auth-src.js +190 -190
- package/dist/platform/windows/assets/service-src.js +147 -147
- package/dist/platform/windows/doctor.js +2 -0
- package/dist/service/control.js +1 -1
- package/dist/service/doctor.js +6 -4
- package/dist/service/main.js +5 -4
- package/dist/service/manage.js +9 -2
- package/dist/service/update/verifier.js +13 -10
- package/package.json +1 -1
- package/scripts/start-chrome.ps1 +8 -2
- package/scripts/verify-windows-adversarial.mjs +142 -0
- package/scripts/verify-windows-prompt-desktop.mjs +50 -50
- package/scripts/verify-windows-user-boundary.mjs +56 -0
- package/scripts/windows-security/attacker.cs +137 -0
- package/scripts/windows-security/browser-worker.mjs +125 -0
- package/scripts/windows-security/canary.cs +16 -0
- package/scripts/windows-security/module-audit.ps1 +36 -0
- package/scripts/windows-security/public-channel.ps1 +31 -0
- package/scripts/windows-security/result-policy.d.mts +2 -0
- package/scripts/windows-security/result-policy.mjs +9 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Internal SYSTEM fixture. The caller supplies auditProcessId and auditServiceHome.
|
|
2
|
+
$ErrorActionPreference='Stop'
|
|
3
|
+
Import-Module "$PSHOME\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1"
|
|
4
|
+
$all=@(Get-CimInstance Win32_Process)
|
|
5
|
+
$ids=@($auditProcessId)
|
|
6
|
+
for($i=0;$i -lt $ids.Count;$i++) {
|
|
7
|
+
$parent=$ids[$i]
|
|
8
|
+
$ids+=@($all|Where-Object{$_.ParentProcessId -eq $parent}|ForEach-Object{[int]$_.ProcessId})
|
|
9
|
+
}
|
|
10
|
+
$paths=@();$exited=0;$unreadableLive=@()
|
|
11
|
+
foreach($id in $ids) {
|
|
12
|
+
try {$paths+=@((Get-Process -Id $id -ErrorAction Stop).Modules|ForEach-Object{$_.FileName})}
|
|
13
|
+
catch {if(Get-Process -Id $id -ErrorAction SilentlyContinue){$unreadableLive+=$id}else{$exited++}}
|
|
14
|
+
}
|
|
15
|
+
$paths=@($paths|Sort-Object -Unique)
|
|
16
|
+
$servicePrefix=[IO.Path]::GetFullPath($auditServiceHome).TrimEnd('\')+'\'
|
|
17
|
+
$windowsPrefix=[IO.Path]::GetFullPath($env:windir).TrimEnd('\')+'\'
|
|
18
|
+
$outside=@($paths|Where-Object{-not $_.StartsWith($servicePrefix,[StringComparison]::OrdinalIgnoreCase)-and -not $_.StartsWith($windowsPrefix,[StringComparison]::OrdinalIgnoreCase)})
|
|
19
|
+
$defender=0;$unsafe=@()
|
|
20
|
+
foreach($file in $outside) {
|
|
21
|
+
$item=Get-Item -LiteralPath $file -Force
|
|
22
|
+
$signature=Get-AuthenticodeSignature -LiteralPath $file
|
|
23
|
+
$acl=$item.GetAccessControl()
|
|
24
|
+
$owner=$acl.GetOwner([Security.Principal.SecurityIdentifier]).Value
|
|
25
|
+
$trustedOwner=$owner -in @('S-1-5-18','S-1-5-32-544') -or $owner.StartsWith('S-1-5-80-')
|
|
26
|
+
$bad=@($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])|Where-Object{
|
|
27
|
+
$_.AccessControlType -eq 'Allow' -and ($_.FileSystemRights -band 0xD0156) -and $_.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $_.IdentityReference.Value.StartsWith('S-1-5-80-')
|
|
28
|
+
})
|
|
29
|
+
if($file.StartsWith('C:\ProgramData\Microsoft\Windows Defender\Platform\',[StringComparison]::OrdinalIgnoreCase)-and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)-and $trustedOwner -and $signature.Status -eq 'Valid' -and $signature.SignerCertificate.Subject -match 'O=Microsoft Corporation' -and $bad.Count -eq 0){$defender++}else{$unsafe+=$file}
|
|
30
|
+
}
|
|
31
|
+
@{
|
|
32
|
+
processes=$ids.Count;modules=$paths.Count;outside=$unsafe;verifiedDefenderModules=$defender
|
|
33
|
+
unreadableLive=$unreadableLive.Count;exitedBeforeInspection=$exited
|
|
34
|
+
bitwardenObserved=($paths -contains (Join-Path $auditServiceHome 'bin\bw.exe'))
|
|
35
|
+
browserObserved=(@($paths|Where-Object{$_.StartsWith((Join-Path $servicePrefix 'bin\browser\'),[StringComparison]::OrdinalIgnoreCase)}).Count -gt 0)
|
|
36
|
+
}|ConvertTo-Json -Compress
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Internal admin fixture. Inputs are a fixed ProgramData root and a validated run UUID directory.
|
|
2
|
+
$ErrorActionPreference='Stop'
|
|
3
|
+
$admins=New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
|
4
|
+
$acl=New-Object Security.AccessControl.DirectorySecurity
|
|
5
|
+
$acl.SetOwner($admins)
|
|
6
|
+
$acl.SetAccessRuleProtection($true,$false)
|
|
7
|
+
foreach($sid in @('S-1-5-18','S-1-5-32-544','S-1-5-32-545')) {
|
|
8
|
+
$rights=if($sid -eq 'S-1-5-32-545'){'ReadAndExecute'}else{'FullControl'}
|
|
9
|
+
$id=New-Object Security.Principal.SecurityIdentifier($sid)
|
|
10
|
+
$acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($id,$rights,'ContainerInherit,ObjectInherit','None','Allow')))
|
|
11
|
+
}
|
|
12
|
+
function Assert-ProbeChannel([string]$target) {
|
|
13
|
+
$item=Get-Item -LiteralPath $target -Force
|
|
14
|
+
if(-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)){throw 'Public channel is redirected'}
|
|
15
|
+
$current=$item.GetAccessControl()
|
|
16
|
+
if($current.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel has untrusted owner'}
|
|
17
|
+
foreach($rule in $current.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
|
|
18
|
+
if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0xD0156) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel is writable by an untrusted identity'}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
# Parent delete-child access could otherwise override the protected root's DACL.
|
|
22
|
+
$parent=(Get-Item -LiteralPath ([IO.Path]::GetDirectoryName($probePublicRoot)) -Force).GetAccessControl()
|
|
23
|
+
foreach($rule in $parent.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
|
|
24
|
+
if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0x40) -and -not ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $rule.IdentityReference.Value.StartsWith('S-1-5-80-')){throw 'Public channel parent permits untrusted deletion'}
|
|
25
|
+
}
|
|
26
|
+
if(-not [IO.Directory]::Exists($probePublicRoot)){[IO.Directory]::CreateDirectory($probePublicRoot,$acl)|Out-Null}
|
|
27
|
+
# Validate after creation as CreateDirectory may return an existing directory after a race.
|
|
28
|
+
Assert-ProbeChannel $probePublicRoot
|
|
29
|
+
if([IO.Directory]::Exists($probeRunDirectory)-or [IO.File]::Exists($probeRunDirectory)){throw 'Run UUID has already been used'}
|
|
30
|
+
[IO.Directory]::CreateDirectory($probeRunDirectory,$acl)|Out-Null
|
|
31
|
+
Assert-ProbeChannel $probeRunDirectory
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
|
2
|
+
export function accepted(native, attacker, runId) {
|
|
3
|
+
if (!uuid.test(runId ?? '') || !native || !attacker) return false
|
|
4
|
+
const expected = ['nonElevated', 'hookPositiveControl', 'capturePositiveControl', 'desktopReadDenied', 'desktopHookDenied', 'desktopSwitchDenied', 'replyPipeConnectDenied', 'replyPipeSpoofDenied']
|
|
5
|
+
return native.ok === true && native.runId === runId && attacker.runId === runId && expected.every(k => attacker[k] === 'True') && attacker.postClickAccepted === 'False' && attacker.replyPipeConnected === 'False' && attacker.printWindowSawMarker === 'False' && attacker.screenCaptureSawMarker === 'False' && attacker.protectedCanaryEvents === '0'
|
|
6
|
+
}
|
|
7
|
+
export function moduleAuditSucceeded(audit) {
|
|
8
|
+
return !!audit && Number.isInteger(audit.modules) && audit.modules > 0 && audit.unreadableLive === 0 && Array.isArray(audit.outside) && audit.outside.length === 0 && audit.bitwardenObserved === true && audit.browserObserved === true
|
|
9
|
+
}
|