workbite 0.1.18 → 0.1.20

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.
@@ -1,116 +1,116 @@
1
- 'use strict';
2
- // Private Windows state for the v2 execution UI: the native TUI intent directory and the
3
- // deploy journal must be owned by the current user and grant mutating access only to that
4
- // user, SYSTEM and Administrators (the Work Stack TUI's private-parent check), but a folder
5
- // created under %LOCALAPPDATA% inherits whatever its parents grant (sandbox groups included).
6
- // One bounded Windows PowerShell child replaces exactly the named directories' DACLs with a
7
- // protected private one; owner, contents and file identities are kept. Linux keeps 0700.
8
- const path = require('node:path');
9
- const { execFile } = require('node:child_process');
10
- const { fail } = require('./common');
11
-
12
- const RESULT_SCHEMA = 'workbite.private-directories.v1';
13
- const MAXIMUM = 8;
14
- // Constant script, no interpolation: paths arrive only as WORKBITE_PRIVATE_DIRECTORY_<n>.
15
- // .NET types only (no cmdlets), so a foreign PSModulePath cannot change what runs.
16
- const SCRIPT = String.raw`
17
- $ErrorActionPreference = 'Stop'
18
- $ProgressPreference = 'SilentlyContinue'
19
- try {
20
- $count = [int]$env:WORKBITE_PRIVATE_DIRECTORY_COUNT
21
- if ($count -lt 1 -or $count -gt ${MAXIMUM}) { throw 'invalid directory count' }
22
- $me = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
23
- $allowed = @($me.Value, 'S-1-5-18', 'S-1-5-32-544')
24
- $sid = [System.Security.Principal.SecurityIdentifier]
25
- $sections = [System.Security.AccessControl.AccessControlSections]'Access, Owner'
26
- $results = @()
27
- for ($index = 0; $index -lt $count; $index++) {
28
- $target = [Environment]::GetEnvironmentVariable('WORKBITE_PRIVATE_DIRECTORY_' + $index)
29
- if (-not $target -or -not [System.IO.Path]::IsPathRooted($target)) { throw ('directory ' + $index + ' is not an absolute path') }
30
- $info = [System.IO.DirectoryInfo]::new($target)
31
- $info.Refresh()
32
- if (-not $info.Exists) { throw ('directory ' + $index + ' is missing or not a directory') }
33
- if (($info.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw ('directory ' + $index + ' is a link or reparse point') }
34
- $current = $info.GetAccessControl($sections)
35
- if ($current.GetOwner($sid).Value -ne $me.Value) { throw ('directory ' + $index + ' is not owned by the current Windows user') }
36
- $rules = @($current.GetAccessRules($true, $true, $sid))
37
- $private = $current.AreAccessRulesProtected -and $rules.Count -gt 0
38
- $mine = $false
39
- foreach ($rule in $rules) {
40
- if ($rule.IsInherited -or $rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $rule.IdentityReference.Value) { $private = $false }
41
- if ($rule.IdentityReference.Value -eq $me.Value -and ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl) { $mine = $true }
42
- }
43
- if ($private -and $mine) { $results += 'kept'; continue }
44
- $security = [System.Security.AccessControl.DirectorySecurity]::new()
45
- $security.SetAccessRuleProtection($true, $false)
46
- foreach ($principal in $allowed) {
47
- $security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($sid::new($principal),
48
- [System.Security.AccessControl.FileSystemRights]::FullControl,
49
- [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit',
50
- [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow))
51
- }
52
- # Only the DACL section is written; the owner is left as verified above.
53
- $info.SetAccessControl($security)
54
- $info.Refresh()
55
- if (($info.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw ('directory ' + $index + ' changed while protecting it') }
56
- $after = $info.GetAccessControl($sections)
57
- if ($after.GetOwner($sid).Value -ne $me.Value -or -not $after.AreAccessRulesProtected) { throw ('directory ' + $index + ' did not keep a protected private DACL') }
58
- foreach ($rule in @($after.GetAccessRules($true, $true, $sid))) {
59
- if ($rule.IsInherited -or $rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $rule.IdentityReference.Value) { throw ('directory ' + $index + ' still grants another principal access') }
60
- }
61
- $results += 'protected'
62
- }
63
- [Console]::Out.Write('{"schema":"${RESULT_SCHEMA}","results":["' + ($results -join '","') + '"]}')
64
- exit 0
65
- } catch {
66
- $message = ($_.Exception.Message -replace '[\x00-\x1f\x7f]', ' ')
67
- if ($message.Length -gt 300) { $message = $message.Substring(0, 300) }
68
- [Console]::Error.Write($message)
69
- exit 3
70
- }
71
- `;
72
- const ENCODED = Buffer.from(SCRIPT, 'utf16le').toString('base64');
73
-
74
- function windowsPowerShell(environment) {
75
- const root = environment.SystemRoot || environment.SYSTEMROOT || environment.windir || 'C:\\Windows';
76
- if (!/^[A-Za-z]:\\[^\x00-\x1f"*?<>|]*$/.test(root)) fail('Cannot locate Windows PowerShell: SystemRoot is not an absolute local path.');
77
- return path.win32.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
78
- }
79
- const bounded = text => String(text || '').replace(/[\x00-\x1f\x7f]/g, ' ').trim().slice(0, 300);
80
-
81
- // Windows only: each directory must already exist (created by safeDirectory). Returns 'kept'
82
- // or 'protected' per directory; anything else refuses and nothing that uses them is started.
83
- // No-op elsewhere: Linux directories are created 0700 and checked by the native TUI itself.
84
- async function protectPrivateDirectories(directories, { platform = process.platform, environment = process.env,
85
- execImpl = execFile, timeout = 60000 } = {}) {
86
- if (platform !== 'win32') return null;
87
- if (!Array.isArray(directories) || !directories.length || directories.length > MAXIMUM
88
- || directories.some(value => typeof value !== 'string' || !path.win32.isAbsolute(value) || /^[\\/]{2}|[\x00-\x1f]/.test(value))) {
89
- fail('WorkBite private state directories must be absolute local paths.');
90
- }
91
- const childEnvironment = {};
92
- for (const [key, value] of Object.entries(environment)) {
93
- // Windows PowerShell must not load another PowerShell's modules; earlier helper values are replaced.
94
- if (value !== undefined && key.toUpperCase() !== 'PSMODULEPATH' && !/^WORKBITE_PRIVATE_DIRECTORY_/i.test(key)) childEnvironment[key] = value;
95
- }
96
- childEnvironment.WORKBITE_PRIVATE_DIRECTORY_COUNT = String(directories.length);
97
- directories.forEach((value, index) => { childEnvironment[`WORKBITE_PRIVATE_DIRECTORY_${index}`] = path.win32.resolve(value); });
98
- const file = windowsPowerShell(environment);
99
- const result = await new Promise(resolve => {
100
- execImpl(file, ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', ENCODED],
101
- { env: childEnvironment, shell: false, windowsHide: true, timeout, maxBuffer: 64 * 1024, encoding: 'utf8' },
102
- (error, stdout, stderr) => resolve({ error, stdout: stdout || '', stderr: stderr || '' }));
103
- });
104
- let record = null;
105
- try { record = JSON.parse(result.stdout.trim()); } catch { record = null; }
106
- if (result.error || record === null || typeof record !== 'object' || Array.isArray(record)
107
- || Object.keys(record).length !== 2 || record.schema !== RESULT_SCHEMA || !Array.isArray(record.results)
108
- || record.results.length !== directories.length || record.results.some(value => value !== 'kept' && value !== 'protected')) {
109
- fail(`WorkBite could not make its private execution state private (${bounded(result.stderr) || 'no result'}); `
110
- + `nothing that uses ${directories.join(', ')} was started. Each must be a real directory owned by the current `
111
- + 'Windows user; WorkBite does not take ownership or follow links.');
112
- }
113
- return record.results;
114
- }
115
-
116
- module.exports = { protectPrivateDirectories, RESULT_SCHEMA };
1
+ 'use strict';
2
+ // Private Windows state for the v2 execution UI: the native TUI intent directory and the
3
+ // deploy journal must be owned by the current user and grant mutating access only to that
4
+ // user, SYSTEM and Administrators (the Work Stack TUI's private-parent check), but a folder
5
+ // created under %LOCALAPPDATA% inherits whatever its parents grant (sandbox groups included).
6
+ // One bounded Windows PowerShell child replaces exactly the named directories' DACLs with a
7
+ // protected private one; owner, contents and file identities are kept. Linux keeps 0700.
8
+ const path = require('node:path');
9
+ const { execFile } = require('node:child_process');
10
+ const { fail } = require('./common');
11
+
12
+ const RESULT_SCHEMA = 'workbite.private-directories.v1';
13
+ const MAXIMUM = 8;
14
+ // Constant script, no interpolation: paths arrive only as WORKBITE_PRIVATE_DIRECTORY_<n>.
15
+ // .NET types only (no cmdlets), so a foreign PSModulePath cannot change what runs.
16
+ const SCRIPT = String.raw`
17
+ $ErrorActionPreference = 'Stop'
18
+ $ProgressPreference = 'SilentlyContinue'
19
+ try {
20
+ $count = [int]$env:WORKBITE_PRIVATE_DIRECTORY_COUNT
21
+ if ($count -lt 1 -or $count -gt ${MAXIMUM}) { throw 'invalid directory count' }
22
+ $me = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
23
+ $allowed = @($me.Value, 'S-1-5-18', 'S-1-5-32-544')
24
+ $sid = [System.Security.Principal.SecurityIdentifier]
25
+ $sections = [System.Security.AccessControl.AccessControlSections]'Access, Owner'
26
+ $results = @()
27
+ for ($index = 0; $index -lt $count; $index++) {
28
+ $target = [Environment]::GetEnvironmentVariable('WORKBITE_PRIVATE_DIRECTORY_' + $index)
29
+ if (-not $target -or -not [System.IO.Path]::IsPathRooted($target)) { throw ('directory ' + $index + ' is not an absolute path') }
30
+ $info = [System.IO.DirectoryInfo]::new($target)
31
+ $info.Refresh()
32
+ if (-not $info.Exists) { throw ('directory ' + $index + ' is missing or not a directory') }
33
+ if (($info.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw ('directory ' + $index + ' is a link or reparse point') }
34
+ $current = $info.GetAccessControl($sections)
35
+ if ($current.GetOwner($sid).Value -ne $me.Value) { throw ('directory ' + $index + ' is not owned by the current Windows user') }
36
+ $rules = @($current.GetAccessRules($true, $true, $sid))
37
+ $private = $current.AreAccessRulesProtected -and $rules.Count -gt 0
38
+ $mine = $false
39
+ foreach ($rule in $rules) {
40
+ if ($rule.IsInherited -or $rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $rule.IdentityReference.Value) { $private = $false }
41
+ if ($rule.IdentityReference.Value -eq $me.Value -and ($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl) { $mine = $true }
42
+ }
43
+ if ($private -and $mine) { $results += 'kept'; continue }
44
+ $security = [System.Security.AccessControl.DirectorySecurity]::new()
45
+ $security.SetAccessRuleProtection($true, $false)
46
+ foreach ($principal in $allowed) {
47
+ $security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($sid::new($principal),
48
+ [System.Security.AccessControl.FileSystemRights]::FullControl,
49
+ [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit',
50
+ [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow))
51
+ }
52
+ # Only the DACL section is written; the owner is left as verified above.
53
+ $info.SetAccessControl($security)
54
+ $info.Refresh()
55
+ if (($info.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw ('directory ' + $index + ' changed while protecting it') }
56
+ $after = $info.GetAccessControl($sections)
57
+ if ($after.GetOwner($sid).Value -ne $me.Value -or -not $after.AreAccessRulesProtected) { throw ('directory ' + $index + ' did not keep a protected private DACL') }
58
+ foreach ($rule in @($after.GetAccessRules($true, $true, $sid))) {
59
+ if ($rule.IsInherited -or $rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $rule.IdentityReference.Value) { throw ('directory ' + $index + ' still grants another principal access') }
60
+ }
61
+ $results += 'protected'
62
+ }
63
+ [Console]::Out.Write('{"schema":"${RESULT_SCHEMA}","results":["' + ($results -join '","') + '"]}')
64
+ exit 0
65
+ } catch {
66
+ $message = ($_.Exception.Message -replace '[\x00-\x1f\x7f]', ' ')
67
+ if ($message.Length -gt 300) { $message = $message.Substring(0, 300) }
68
+ [Console]::Error.Write($message)
69
+ exit 3
70
+ }
71
+ `;
72
+ const ENCODED = Buffer.from(SCRIPT, 'utf16le').toString('base64');
73
+
74
+ function windowsPowerShell(environment) {
75
+ const root = environment.SystemRoot || environment.SYSTEMROOT || environment.windir || 'C:\\Windows';
76
+ if (!/^[A-Za-z]:\\[^\x00-\x1f"*?<>|]*$/.test(root)) fail('Cannot locate Windows PowerShell: SystemRoot is not an absolute local path.');
77
+ return path.win32.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
78
+ }
79
+ const bounded = text => String(text || '').replace(/[\x00-\x1f\x7f]/g, ' ').trim().slice(0, 300);
80
+
81
+ // Windows only: each directory must already exist (created by safeDirectory). Returns 'kept'
82
+ // or 'protected' per directory; anything else refuses and nothing that uses them is started.
83
+ // No-op elsewhere: Linux directories are created 0700 and checked by the native TUI itself.
84
+ async function protectPrivateDirectories(directories, { platform = process.platform, environment = process.env,
85
+ execImpl = execFile, timeout = 60000 } = {}) {
86
+ if (platform !== 'win32') return null;
87
+ if (!Array.isArray(directories) || !directories.length || directories.length > MAXIMUM
88
+ || directories.some(value => typeof value !== 'string' || !path.win32.isAbsolute(value) || /^[\\/]{2}|[\x00-\x1f]/.test(value))) {
89
+ fail('WorkBite private state directories must be absolute local paths.');
90
+ }
91
+ const childEnvironment = {};
92
+ for (const [key, value] of Object.entries(environment)) {
93
+ // Windows PowerShell must not load another PowerShell's modules; earlier helper values are replaced.
94
+ if (value !== undefined && key.toUpperCase() !== 'PSMODULEPATH' && !/^WORKBITE_PRIVATE_DIRECTORY_/i.test(key)) childEnvironment[key] = value;
95
+ }
96
+ childEnvironment.WORKBITE_PRIVATE_DIRECTORY_COUNT = String(directories.length);
97
+ directories.forEach((value, index) => { childEnvironment[`WORKBITE_PRIVATE_DIRECTORY_${index}`] = path.win32.resolve(value); });
98
+ const file = windowsPowerShell(environment);
99
+ const result = await new Promise(resolve => {
100
+ execImpl(file, ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', ENCODED],
101
+ { env: childEnvironment, shell: false, windowsHide: true, timeout, maxBuffer: 64 * 1024, encoding: 'utf8' },
102
+ (error, stdout, stderr) => resolve({ error, stdout: stdout || '', stderr: stderr || '' }));
103
+ });
104
+ let record = null;
105
+ try { record = JSON.parse(result.stdout.trim()); } catch { record = null; }
106
+ if (result.error || record === null || typeof record !== 'object' || Array.isArray(record)
107
+ || Object.keys(record).length !== 2 || record.schema !== RESULT_SCHEMA || !Array.isArray(record.results)
108
+ || record.results.length !== directories.length || record.results.some(value => value !== 'kept' && value !== 'protected')) {
109
+ fail(`WorkBite could not make its private execution state private (${bounded(result.stderr) || 'no result'}); `
110
+ + `nothing that uses ${directories.join(', ')} was started. Each must be a real directory owned by the current `
111
+ + 'Windows user; WorkBite does not take ownership or follow links.');
112
+ }
113
+ return record.results;
114
+ }
115
+
116
+ module.exports = { protectPrivateDirectories, RESULT_SCHEMA };