opensocrates 1.3.1 → 1.5.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,164 @@
1
+ # Paths are data in environment variables, never interpolated PowerShell code.
2
+ param([ValidateSet('entries','extract','private','check','check-tree','mkdir-private','seal-new','lease')][string]$Action)
3
+ $ErrorActionPreference = 'Stop'
4
+ [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
5
+ function Native-Path([string]$path) {
6
+ $absolute = [IO.Path]::GetFullPath($path)
7
+ # PowerShell 5.1's FileSystemInfo ACL methods otherwise fail beyond MAX_PATH,
8
+ # including an owned tree temporarily moved below a transaction backup.
9
+ # Convert an already absolute local drive path; caller path/alias policy and
10
+ # every ownership, DACL and reparse check remain in force.
11
+ if ($absolute -match '^[a-zA-Z]:\\') { return '\\?\' + $absolute }
12
+ return $absolute
13
+ }
14
+ $targetPath = Native-Path $env:OPENSOCRATES_WINDOWS_PATH
15
+ $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
16
+ $currentSid = $currentIdentity.User
17
+ $trustedSids = @($currentSid.Value,'S-1-5-18','S-1-5-32-544')
18
+ function Assert-Parents([string]$path) {
19
+ $cursor = [IO.Path]::GetFullPath($path)
20
+ while ($cursor) {
21
+ $item = Get-Item -LiteralPath $cursor -Force
22
+ if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Reparse ancestor refused' }
23
+ $cursor = [IO.Path]::GetDirectoryName($cursor)
24
+ }
25
+ }
26
+ function Private-Acl([bool]$directory) {
27
+ $acl = if ($directory) { [Security.AccessControl.DirectorySecurity]::new() } else { [Security.AccessControl.FileSecurity]::new() }
28
+ $acl.SetOwner($currentSid)
29
+ $acl.SetAccessRuleProtection($true,$false)
30
+ $inherit = if ($directory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
31
+ foreach ($identity in $trustedSids) {
32
+ $acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new([Security.Principal.SecurityIdentifier]::new($identity),'FullControl',$inherit,'None','Allow'))
33
+ }
34
+ return $acl
35
+ }
36
+ function Assert-Owned($item) {
37
+ if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Reparse point refused' }
38
+ $acl = $item.GetAccessControl()
39
+ if ($acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $currentSid.Value) { throw 'Foreign owner refused' }
40
+ $writes = [Security.AccessControl.FileSystemRights]::Write -bor [Security.AccessControl.FileSystemRights]::Delete -bor [Security.AccessControl.FileSystemRights]::DeleteSubdirectoriesAndFiles -bor [Security.AccessControl.FileSystemRights]::ChangePermissions -bor [Security.AccessControl.FileSystemRights]::TakeOwnership
41
+ foreach ($rule in $acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
42
+ if ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) { continue }
43
+ if ($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band $writes) -and $rule.IdentityReference.Value -notin $trustedSids) { throw 'Untrusted writable DACL refused' }
44
+ }
45
+ }
46
+ function Tree-Items($rootItem) {
47
+ $pending = [Collections.Generic.Queue[IO.FileSystemInfo]]::new()
48
+ $pending.Enqueue($rootItem)
49
+ $count = 0
50
+ while ($pending.Count) {
51
+ $item = $pending.Dequeue()
52
+ if (++$count -gt 20000) { throw 'Filesystem limit exceeded' }
53
+ if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Reparse point refused' }
54
+ $item
55
+ if ($item -is [IO.DirectoryInfo]) {
56
+ foreach ($child in $item.EnumerateFileSystemInfos()) { $pending.Enqueue($child) }
57
+ }
58
+ }
59
+ }
60
+ if ($Action -eq 'mkdir-private') {
61
+ if (Test-Path -LiteralPath $targetPath) { throw 'New private directory collision' }
62
+ Assert-Parents ([IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($targetPath)))
63
+ ([IO.DirectoryInfo]::new($targetPath)).Create((Private-Acl $true))
64
+ Assert-Owned (Get-Item -LiteralPath $targetPath -Force)
65
+ exit 0
66
+ }
67
+ if ($Action -in @('check','check-tree','seal-new','lease')) {
68
+ Assert-Parents $targetPath
69
+ $rootItem = Get-Item -LiteralPath $targetPath -Force
70
+ if ($Action -eq 'seal-new') {
71
+ # Caller uses this only for an exclusively created staging tree/file.
72
+ $items = @(Tree-Items $rootItem)
73
+ foreach ($item in $items) {
74
+ $owner = $item.GetAccessControl().GetOwner([Security.Principal.SecurityIdentifier]).Value
75
+ if ($owner -notin @($currentSid.Value,$currentIdentity.Owner.Value)) { throw 'Foreign staging owner refused' }
76
+ }
77
+ foreach ($item in $items) { $item.SetAccessControl((Private-Acl ($item -is [IO.DirectoryInfo]))) }
78
+ } elseif ($Action -eq 'check-tree') {
79
+ foreach ($item in (Tree-Items $rootItem)) { Assert-Owned $item }
80
+ } else { Assert-Owned $rootItem }
81
+ if ($Action -ne 'lease') { exit 0 }
82
+ # Pin ancestors without FILE_SHARE_DELETE while Node holds its operation lock.
83
+ Add-Type @'
84
+ using System;
85
+ using System.Runtime.InteropServices;
86
+ using Microsoft.Win32.SafeHandles;
87
+ public static class OpenSocratesDirectoryLease {
88
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
89
+ public static extern SafeFileHandle CreateFile(string name, uint access, uint share, IntPtr security, uint mode, uint flags, IntPtr template);
90
+ }
91
+ '@
92
+ $handles = [Collections.Generic.List[Microsoft.Win32.SafeHandles.SafeFileHandle]]::new()
93
+ try {
94
+ $cursor = [IO.Path]::GetFullPath($targetPath)
95
+ while ($cursor) {
96
+ # FILE_LIST_DIRECTORY establishes read sharing; access=0 does not
97
+ # participate in deletion sharing and cannot protect against rename.
98
+ $handle = [OpenSocratesDirectoryLease]::CreateFile($cursor,1,3,[IntPtr]::Zero,3,0x02200000,[IntPtr]::Zero)
99
+ if ($handle.IsInvalid) { throw 'Cannot pin managed ancestor' }
100
+ $handles.Add($handle)
101
+ $cursor = [IO.Path]::GetDirectoryName($cursor)
102
+ }
103
+ Assert-Parents $targetPath
104
+ Assert-Owned (Get-Item -LiteralPath $targetPath -Force)
105
+ [Console]::WriteLine('ready')
106
+ [Console]::Out.Flush()
107
+ [void][Console]::In.ReadLine()
108
+ } finally { foreach ($handle in $handles) { $handle.Dispose() } }
109
+ exit 0
110
+ }
111
+ if ($Action -eq 'private') {
112
+ $item = Get-Item -LiteralPath $targetPath -Force
113
+ if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Reparse point refused' }
114
+ $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User
115
+ $acl = $item.GetAccessControl()
116
+ if ($acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { throw 'Foreign owner refused' }
117
+ $acl.SetAccessRuleProtection($true, $false)
118
+ foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
119
+ $inherit = if ($item.PSIsContainer) { 'ContainerInherit,ObjectInherit' } else { 'None' }
120
+ foreach ($identity in @($sid.Value,'S-1-5-18','S-1-5-32-544')) {
121
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new([Security.Principal.SecurityIdentifier]::new($identity), 'FullControl', $inherit, 'None', 'Allow')
122
+ $acl.AddAccessRule($rule)
123
+ }
124
+ $item.SetAccessControl($acl)
125
+ exit 0
126
+ }
127
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
128
+ Assert-Parents $targetPath
129
+ $zip = [IO.Compression.ZipFile]::OpenRead($targetPath)
130
+ try {
131
+ $names = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
132
+ $directories = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
133
+ $total = 0L
134
+ foreach ($entry in $zip.Entries) {
135
+ $name = $entry.FullName
136
+ if ($name.EndsWith('/')) { [void]$directories.Add($name.TrimEnd('/')) }
137
+ $parts = $name.TrimEnd('/').Split('/')
138
+ if (!$name -or $name.Contains('\') -or $name.StartsWith('/') -or $name -match '[\x00-\x1f<>:"|?*]' -or
139
+ ($parts | Where-Object { !$_ -or $_ -eq '.' -or $_ -eq '..' -or $_ -match '[. ]$' -or $_ -match '^(?i:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)' }) -or !$names.Add($name.TrimEnd('/'))) { throw 'Unsafe or duplicate ZIP path' }
140
+ $kind = ($entry.ExternalAttributes -shr 16) -band 0xF000
141
+ if ($kind -notin @(0,0x4000,0x8000) -or ($entry.ExternalAttributes -band 0x400)) { throw 'ZIP non-regular entry refused' }
142
+ if ($kind -eq 0x4000 -and !$name.EndsWith('/')) { throw 'ZIP directory type mismatch' }
143
+ $total += $entry.Length
144
+ if ($total -gt 2147483648 -or $zip.Entries.Count -gt 20000) { throw 'ZIP limit exceeded' }
145
+ }
146
+ foreach ($entry in $zip.Entries) {
147
+ $parentName = $entry.FullName.TrimEnd('/')
148
+ while ($parentName.Contains('/')) {
149
+ $parentName = $parentName.Substring(0,$parentName.LastIndexOf('/'))
150
+ if ($names.Contains($parentName) -and !$directories.Contains($parentName)) { throw 'ZIP file-directory collision' }
151
+ }
152
+ }
153
+ if ($Action -eq 'entries') { ConvertTo-Json -InputObject @($zip.Entries | ForEach-Object { $_.FullName }) -Compress; exit 0 }
154
+ $destination = (Native-Path $env:OPENSOCRATES_WINDOWS_DESTINATION).TrimEnd('\') + '\'
155
+ Assert-Parents $destination
156
+ foreach ($entry in $zip.Entries) {
157
+ # Extended Win32 paths do not translate the ZIP's portable slash syntax.
158
+ $output = [IO.Path]::GetFullPath([IO.Path]::Combine($destination,$entry.FullName.Replace('/','\')))
159
+ if (!$output.StartsWith($destination,[StringComparison]::OrdinalIgnoreCase)) { throw 'ZIP path escape' }
160
+ if ($entry.FullName.EndsWith('/')) { [void][IO.Directory]::CreateDirectory($output); continue }
161
+ [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($output))
162
+ [IO.Compression.ZipFileExtensions]::ExtractToFile($entry,$output,$false)
163
+ }
164
+ } finally { $zip.Dispose() }
package/package.json CHANGED
@@ -1,22 +1,25 @@
1
1
  {
2
2
  "name": "opensocrates",
3
- "version": "1.3.1",
4
- "description": "Install and manage OpenSocrates for Antigravity, Claude Code, Cowork, Codex, Cursor, Grok Build, and OpenCode.",
3
+ "version": "1.5.0",
4
+ "description": "Original reasoning assistance with Codex, Claude account skills and Antigravity delivery.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opensocrates": "installer/opensocrates.mjs"
8
8
  },
9
9
  "files": [
10
10
  "installer/opensocrates.mjs",
11
+ "installer/managed-hosts.mjs",
12
+ "installer/windows.ps1",
11
13
  "CHANGELOG.md",
12
14
  "SECURITY.md",
13
15
  "VERSION"
14
16
  ],
15
17
  "scripts": {
16
- "test": "node --test installer/opensocrates.test.mjs installer/lifecycle.test.mjs tools/clean_machine_acceptance.test.mjs tools/reinstall_cycle_acceptance.test.mjs tools/opencode_bridge.test.mjs tools/release_gate.test.mjs && npm run test:npx",
18
+ "test": "node --test installer/opensocrates.test.mjs installer/lifecycle.test.mjs installer/managed-hosts.test.mjs tools/clean_machine_acceptance.test.mjs tools/reinstall_cycle_acceptance.test.mjs tools/release_gate.test.mjs && npm run test:npx",
17
19
  "test:npx": "node installer/package-smoke.mjs",
18
20
  "pack:check": "npm pack --dry-run",
19
- "prepublishOnly": "npm test"
21
+ "prepublishOnly": "npm test",
22
+ "test:windows": "node --test installer/windows.test.mjs installer/managed-windows.test.mjs"
20
23
  },
21
24
  "engines": {
22
25
  "node": ">=20"
@@ -36,17 +39,9 @@
36
39
  },
37
40
  "keywords": [
38
41
  "ai",
39
- "claude",
40
- "claude-code",
41
42
  "codex",
42
- "grok",
43
- "grok-build",
44
- "cowork",
45
- "cursor",
46
- "opencode",
47
43
  "reasoning",
48
44
  "agent",
49
- "antigravity",
50
45
  "installer",
51
46
  "critical-thinking",
52
47
  "socratic"