@sysid/sandbox-runtime-improved 0.0.55-sysid.1 → 0.0.61-sysid.1

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.
Files changed (118) hide show
  1. package/README.md +43 -2
  2. package/dist/cli.js +8 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +6 -6
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/sandbox/credential-mask-files.d.ts +72 -0
  9. package/dist/sandbox/credential-mask-files.d.ts.map +1 -0
  10. package/dist/sandbox/credential-mask-files.js +146 -0
  11. package/dist/sandbox/credential-mask-files.js.map +1 -0
  12. package/dist/sandbox/credential-sentinel.d.ts +72 -0
  13. package/dist/sandbox/credential-sentinel.d.ts.map +1 -0
  14. package/dist/sandbox/credential-sentinel.js +130 -0
  15. package/dist/sandbox/credential-sentinel.js.map +1 -0
  16. package/dist/sandbox/domain-pattern.d.ts +36 -0
  17. package/dist/sandbox/domain-pattern.d.ts.map +1 -0
  18. package/dist/sandbox/domain-pattern.js +60 -0
  19. package/dist/sandbox/domain-pattern.js.map +1 -0
  20. package/dist/sandbox/http-proxy.d.ts +32 -1
  21. package/dist/sandbox/http-proxy.d.ts.map +1 -1
  22. package/dist/sandbox/http-proxy.js +10 -2
  23. package/dist/sandbox/http-proxy.js.map +1 -1
  24. package/dist/sandbox/linux-sandbox-utils.d.ts +17 -0
  25. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
  26. package/dist/sandbox/linux-sandbox-utils.js +155 -47
  27. package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
  28. package/dist/sandbox/listen-in-range.d.ts +12 -0
  29. package/dist/sandbox/listen-in-range.d.ts.map +1 -0
  30. package/dist/sandbox/listen-in-range.js +43 -0
  31. package/dist/sandbox/listen-in-range.js.map +1 -0
  32. package/dist/sandbox/macos-sandbox-utils.d.ts +13 -0
  33. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
  34. package/dist/sandbox/macos-sandbox-utils.js +76 -14
  35. package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
  36. package/dist/sandbox/mitm-ca.d.ts +18 -2
  37. package/dist/sandbox/mitm-ca.d.ts.map +1 -1
  38. package/dist/sandbox/mitm-ca.js +49 -9
  39. package/dist/sandbox/mitm-ca.js.map +1 -1
  40. package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
  41. package/dist/sandbox/mitm-leaf.js +24 -6
  42. package/dist/sandbox/mitm-leaf.js.map +1 -1
  43. package/dist/sandbox/mux-proxy.d.ts +59 -0
  44. package/dist/sandbox/mux-proxy.d.ts.map +1 -0
  45. package/dist/sandbox/mux-proxy.js +159 -0
  46. package/dist/sandbox/mux-proxy.js.map +1 -0
  47. package/dist/sandbox/request-filter.d.ts +11 -1
  48. package/dist/sandbox/request-filter.d.ts.map +1 -1
  49. package/dist/sandbox/request-filter.js.map +1 -1
  50. package/dist/sandbox/sandbox-config.d.ts +383 -1
  51. package/dist/sandbox/sandbox-config.d.ts.map +1 -1
  52. package/dist/sandbox/sandbox-config.js +252 -1
  53. package/dist/sandbox/sandbox-config.js.map +1 -1
  54. package/dist/sandbox/sandbox-manager.d.ts +4 -0
  55. package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
  56. package/dist/sandbox/sandbox-manager.js +618 -168
  57. package/dist/sandbox/sandbox-manager.js.map +1 -1
  58. package/dist/sandbox/sandbox-schemas.d.ts +27 -0
  59. package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
  60. package/dist/sandbox/sandbox-utils.d.ts +42 -6
  61. package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
  62. package/dist/sandbox/sandbox-utils.js +115 -27
  63. package/dist/sandbox/sandbox-utils.js.map +1 -1
  64. package/dist/sandbox/socks-proxy.d.ts +11 -5
  65. package/dist/sandbox/socks-proxy.d.ts.map +1 -1
  66. package/dist/sandbox/socks-proxy.js +13 -81
  67. package/dist/sandbox/socks-proxy.js.map +1 -1
  68. package/dist/sandbox/tls-terminate-proxy.d.ts +2 -2
  69. package/dist/sandbox/tls-terminate-proxy.d.ts.map +1 -1
  70. package/dist/sandbox/tls-terminate-proxy.js +31 -5
  71. package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
  72. package/dist/sandbox/windows-sandbox-utils.d.ts +347 -11
  73. package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -1
  74. package/dist/sandbox/windows-sandbox-utils.js +437 -45
  75. package/dist/sandbox/windows-sandbox-utils.js.map +1 -1
  76. package/package.json +7 -4
  77. package/vendor/seccomp/build.ts +8 -30
  78. package/vendor/srt-win/build.ts +21 -0
  79. package/dist/vendor/seccomp/Dockerfile.build +0 -6
  80. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  81. package/dist/vendor/seccomp/build.ts +0 -91
  82. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  83. package/dist/vendor/seccomp-src/apply-seccomp.c +0 -280
  84. package/dist/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  85. package/dist/vendor/srt-win/Cargo.lock +0 -361
  86. package/dist/vendor/srt-win/Cargo.toml +0 -46
  87. package/dist/vendor/srt-win/ci/cleanup.ps1 +0 -49
  88. package/dist/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  89. package/dist/vendor/srt-win/ci/smoke.ps1 +0 -307
  90. package/dist/vendor/srt-win/src/job.rs +0 -102
  91. package/dist/vendor/srt-win/src/launch.rs +0 -732
  92. package/dist/vendor/srt-win/src/lib.rs +0 -20
  93. package/dist/vendor/srt-win/src/main.rs +0 -669
  94. package/dist/vendor/srt-win/src/self_protect.rs +0 -169
  95. package/dist/vendor/srt-win/src/sid.rs +0 -296
  96. package/dist/vendor/srt-win/src/token.rs +0 -341
  97. package/dist/vendor/srt-win/src/util.rs +0 -42
  98. package/dist/vendor/srt-win/src/wfp.rs +0 -992
  99. package/dist/vendor/srt-win/src/winsta.rs +0 -209
  100. package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
  101. package/vendor/seccomp-src/apply-seccomp.c +0 -280
  102. package/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  103. package/vendor/srt-win/Cargo.lock +0 -361
  104. package/vendor/srt-win/Cargo.toml +0 -46
  105. package/vendor/srt-win/ci/cleanup.ps1 +0 -49
  106. package/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  107. package/vendor/srt-win/ci/smoke.ps1 +0 -307
  108. package/vendor/srt-win/src/job.rs +0 -102
  109. package/vendor/srt-win/src/launch.rs +0 -732
  110. package/vendor/srt-win/src/lib.rs +0 -20
  111. package/vendor/srt-win/src/main.rs +0 -669
  112. package/vendor/srt-win/src/self_protect.rs +0 -169
  113. package/vendor/srt-win/src/sid.rs +0 -296
  114. package/vendor/srt-win/src/token.rs +0 -341
  115. package/vendor/srt-win/src/util.rs +0 -42
  116. package/vendor/srt-win/src/wfp.rs +0 -992
  117. package/vendor/srt-win/src/winsta.rs +0 -209
  118. package/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
@@ -1,307 +0,0 @@
1
- <#
2
- srt-win end-to-end smoke test.
3
-
4
- Exercises the full group + WFP lifecycle against a built srt-win.exe.
5
- Throws on any assertion failure. Requires elevation (NetLocalGroup*
6
- and Fwpm* both need admin).
7
-
8
- Usage (local dev machine):
9
- pwsh vendor/srt-win/ci/smoke.ps1 .\target\release\srt-win.exe
10
-
11
- Usage (CI — workflow passes the path):
12
- pwsh vendor/srt-win/ci/smoke.ps1 vendor\srt-win\target\release\srt-win.exe
13
-
14
- All WFP operations target $TestSublayer (a fixed test GUID), NOT
15
- the production default sublayer — safe to run on a dev machine
16
- that has real sandbox-runtime filters installed; the idempotent
17
- install's purge-then-re-add only touches the test sublayer.
18
-
19
- When running under GitHub Actions, the random alt-sublayer GUID is
20
- also written to $env:GITHUB_ENV so the always()-gated cleanup step
21
- can remove those filters even if this script throws midway.
22
- #>
23
- param(
24
- [Parameter(Mandatory = $true)]
25
- [string]$Exe,
26
- [string]$GroupName = 'srt-ci-test',
27
- # Distinct from wfp::DEFAULT_SUBLAYER_GUID so local runs never
28
- # touch a production install.
29
- [string]$TestSublayer = 'a91b6f12-4c0e-4e30-b1f7-3d52890ce117',
30
- # Second sublayer for the single-install convenience-subcommand
31
- # row, so it doesn't perturb the lifecycle section's $TestSublayer.
32
- [string]$InstallSublayer = 'b2e8a6c4-1f73-4d09-9e25-c7b0d3a48f61'
33
- )
34
-
35
- $ErrorActionPreference = 'Stop'
36
-
37
- if (-not (Test-Path $Exe)) {
38
- throw "srt-win.exe not found at '$Exe'"
39
- }
40
-
41
- # user_sid logged for debug context only (whose TokenGroups the
42
- # group-status / fence assertions are evaluating against).
43
- $me = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
44
- Write-Host "srt-win smoke: exe=$Exe group=$GroupName sublayer=$TestSublayer user_sid=$me"
45
- $sl = @('--sublayer-guid', $TestSublayer)
46
- # Explicit so the assertions below are deterministic even if the
47
- # compiled-in default changes.
48
- $pr = @('--proxy-port-range', '60080-60089')
49
-
50
- function Run([string[]]$argv) {
51
- & $Exe @argv
52
- if ($LASTEXITCODE -ne 0) {
53
- throw "srt-win $($argv -join ' ') exited $LASTEXITCODE"
54
- }
55
- }
56
- function J([string[]]$argv) { Run $argv | ConvertFrom-Json }
57
- function MustFail([string[]]$argv, [string]$why) {
58
- & $Exe @argv 2>$null
59
- if ($LASTEXITCODE -eq 0) {
60
- throw "expected non-zero ($why): srt-win $($argv -join ' ')"
61
- }
62
- }
63
-
64
- # The admin mutators below (group create|delete, wfp install|uninstall)
65
- # self-elevate via maybe_self_elevate (batch 02b). The CI runner is
66
- # already elevated, so the helper returns None and they run in-process,
67
- # same as before; the UAC self-elevate relaunch path is interactive-only
68
- # (see 02b) and is not exercised here.
69
- # ── group create (idempotent) ────────────────────────────────────────
70
- Run @('group', 'create', '--name', $GroupName)
71
- Run @('group', 'create', '--name', $GroupName) # second call must succeed
72
-
73
- $gs = J @('group', 'status', '--name', $GroupName)
74
- Write-Host "group status (--name): $($gs | ConvertTo-Json -Compress)"
75
- # In CI there's no logout, so the new group SID is not yet on the
76
- # runner's token: `created-not-on-token` is expected. On a dev box
77
- # that already has the group from a prior run, `ready` is fine too.
78
- if ($gs.state -notin 'created-not-on-token', 'ready') {
79
- throw "unexpected group state: $($gs.state)"
80
- }
81
- if (-not $gs.sid -or -not $gs.sid.StartsWith('S-1-')) {
82
- throw "group status did not return a SID"
83
- }
84
-
85
- # --group-sid path returns the same SID.
86
- $gs2 = J @('group', 'status', '--group-sid', $gs.sid)
87
- if ($gs2.sid -ne $gs.sid) {
88
- throw "--group-sid status sid mismatch: $($gs2.sid) vs $($gs.sid)"
89
- }
90
- # Unmapped SID via --group-sid reports absent (not created-not-on-token).
91
- $gsBad = J @('group', 'status', '--group-sid', 'S-1-5-21-1-2-3-9999999')
92
- if ($gsBad.state -ne 'absent') {
93
- throw "unmapped --group-sid expected absent, got $($gsBad.state)"
94
- }
95
-
96
- # ── negative input ──────────────────────────────────────────────────
97
- MustFail @('wfp', 'install', '--group-sid', 'not-a-sid') 'invalid --group-sid'
98
- MustFail (@('wfp', 'install', '--name', $GroupName, '--proxy-port-range', '100-50') + $sl) 'low>high'
99
- MustFail (@('wfp', 'install', '--name', $GroupName, '--proxy-port-range', '1-1000') + $sl) 'range too wide'
100
- MustFail (@('wfp', 'install', '--name', $GroupName, '--proxy-port-range', '60080') + $sl) 'missing dash'
101
-
102
- # ═════════════════════════════════════════════════════════════════════
103
- # WFP lifecycle test — uses $GroupName.
104
- #
105
- # The WFP filters are machine-wide and keyed on the group SID. We
106
- # can install/enumerate/uninstall them regardless of whether the
107
- # group is on the current token, so $GroupName works fine for the
108
- # *lifecycle* assertions below. It does NOT work for asserting
109
- # "the broker gets through" — that needs a group already enabled
110
- # on this token, which $GroupName isn't (no logout in CI). The
111
- # fence-behaviour section further down uses BUILTIN\Administrators
112
- # instead.
113
- # ═════════════════════════════════════════════════════════════════════
114
-
115
- # ── pre-install absent ───────────────────────────────────────────────
116
- $pre = J (@('wfp', 'status') + $sl)
117
- if ($pre.state -ne 'absent') {
118
- throw "pre-install wfp status expected absent, got $($pre.state)"
119
- }
120
-
121
- # First install via --name.
122
- Run (@('wfp', 'install', '--name', $GroupName) + $sl + $pr)
123
- $ws = J (@('wfp', 'status') + $sl)
124
- Write-Host "wfp status: $($ws | ConvertTo-Json -Compress)"
125
- if ($ws.state -ne 'installed') { throw "expected installed, got $($ws.state)" }
126
- if ($ws.filters -lt 8) { throw "expected >=8 filters, got $($ws.filters)" }
127
- if ($ws.port_range[0] -ne 60080 -or $ws.port_range[1] -ne 60089) {
128
- throw "expected port_range [60080,60089], got [$($ws.port_range -join ',')]"
129
- }
130
-
131
- # Idempotency: second install via --group-sid path leaves the same
132
- # filter count.
133
- Run (@('wfp', 'install', '--group-sid', $gs.sid) + $sl + $pr)
134
- $ws2 = J (@('wfp', 'status') + $sl)
135
- if ($ws2.filters -ne $ws.filters) {
136
- throw "idempotency: filter count changed $($ws.filters) -> $($ws2.filters)"
137
- }
138
-
139
- # ── --proxy-port-range override round-trips through status ─────────
140
- Run (@('wfp', 'install', '--name', $GroupName, '--proxy-port-range', '50000-50001') + $sl)
141
- $wsR = J (@('wfp', 'status') + $sl)
142
- if ($wsR.port_range[0] -ne 50000 -or $wsR.port_range[1] -ne 50001) {
143
- throw "expected port_range [50000,50001], got [$($wsR.port_range -join ',')]"
144
- }
145
- # No-flag install: assert the compiled-in DEFAULT_PROXY_PORT_RANGE.
146
- Run (@('wfp', 'install', '--name', $GroupName) + $sl)
147
- $wsD = J (@('wfp', 'status') + $sl)
148
- if ($wsD.port_range[0] -ne 60080 -or $wsD.port_range[1] -ne 60089) {
149
- throw "no-flag default expected [60080,60089], got [$($wsD.port_range -join ',')]"
150
- }
151
-
152
- # ── --sublayer-guid isolation ────────────────────────────────────────
153
- # Persist the alt GUID so an always()-gated cleanup step can remove
154
- # its filters even if this script throws midway.
155
- $altGuid = [guid]::NewGuid().ToString()
156
- if ($env:GITHUB_ENV) {
157
- Add-Content $env:GITHUB_ENV "SRT_ALT_GUID=$altGuid"
158
- }
159
- Run (@('wfp', 'install', '--name', $GroupName, '--sublayer-guid', $altGuid) + $pr)
160
- $alt = J @('wfp', 'status', '--sublayer-guid', $altGuid)
161
- if ($alt.state -ne 'installed') {
162
- throw "alt sublayer expected installed, got $($alt.state)"
163
- }
164
- # Test sublayer is still its own thing.
165
- $stillTest = J (@('wfp', 'status') + $sl)
166
- if ($stillTest.filters -ne $ws.filters) {
167
- throw "test sublayer perturbed by alt install"
168
- }
169
- Run @('wfp', 'uninstall', '--sublayer-guid', $altGuid)
170
- $altGone = J @('wfp', 'status', '--sublayer-guid', $altGuid)
171
- if ($altGone.state -ne 'absent') {
172
- throw "alt sublayer expected absent after uninstall, got $($altGone.state)"
173
- }
174
-
175
- # ── teardown: uninstall on test sublayer ────────────────────────────
176
- Run (@('wfp', 'uninstall') + $sl)
177
- $post = J (@('wfp', 'status') + $sl)
178
- if ($post.state -ne 'absent') {
179
- throw "post-uninstall expected absent, got $($post.state)"
180
- }
181
- # Idempotent no-op: second uninstall must also exit 0.
182
- Run (@('wfp', 'uninstall') + $sl)
183
-
184
- # ═════════════════════════════════════════════════════════════════════
185
- # WFP fence-behaviour test — uses BUILTIN\Administrators (S-1-5-32-544).
186
- #
187
- # Why a different group: the fence relies on AccessCheck against the
188
- # connecting token, so to assert "broker passes filter 1" we need a
189
- # group that's *already enabled* on this token. $GroupName was just
190
- # created and won't be in TokenGroups until a fresh logon. Admins is
191
- # reliably enabled on the GHA runner.
192
- # ═════════════════════════════════════════════════════════════════════
193
-
194
- $adminsSid = 'S-1-5-32-544'
195
- Run (@('wfp', 'install', '--group-sid', $adminsSid) + $sl + $pr)
196
- try {
197
- # Broker-side: this process has Admins enabled, so filter 1
198
- # (PERMIT group-enabled) should let the connect through.
199
- $r = curl.exe -s -m 10 -o NUL -w "%{http_code}" https://example.com
200
- if ($LASTEXITCODE -ne 0 -or $r -ne '200') {
201
- throw "broker egress through filter 1 expected 200, got exit=$LASTEXITCODE code='$r'"
202
- }
203
- Write-Host "fence: broker egress OK ($r)"
204
-
205
- # Child-side (group deny-only) assertion lands in batch 02 once
206
- # `srt-win exec` exists; that batch's smoke-exec.ps1 will run
207
- # srt-win exec --group-sid S-1-5-32-544 -- curl https://example.com
208
- # and assert it is BLOCKED.
209
- }
210
- finally {
211
- Run (@('wfp', 'uninstall') + $sl)
212
- }
213
-
214
- # ── group teardown ───────────────────────────────────────────────────
215
- Run @('group', 'delete', '--name', $GroupName)
216
- $gd = J @('group', 'status', '--name', $GroupName)
217
- if ($gd.state -ne 'absent') {
218
- throw "post-delete group expected absent, got $($gd.state)"
219
- }
220
- # Idempotent no-op: second delete must also exit 0.
221
- Run @('group', 'delete', '--name', $GroupName)
222
-
223
- # ═════════════════════════════════════════════════════════════════════
224
- # Single-step `install` / `uninstall` convenience subcommands.
225
- #
226
- # Distinct group name + sublayer so this section is isolated from the
227
- # lifecycle assertions above. Verifies that one `install` call leaves
228
- # both the group present AND the WFP filters installed, and that one
229
- # `uninstall` call removes both.
230
- # ═════════════════════════════════════════════════════════════════════
231
-
232
- $instGrp = "$GroupName-inst"
233
- $isl = @('--sublayer-guid', $InstallSublayer)
234
-
235
- Run (@('install', '--name', $instGrp) + $isl + $pr)
236
- $ig = J @('group', 'status', '--name', $instGrp)
237
- if ($ig.state -notin 'created-not-on-token', 'ready') {
238
- throw "install: group expected created-not-on-token/ready, got $($ig.state)"
239
- }
240
- $iw = J (@('wfp', 'status') + $isl)
241
- if ($iw.state -ne 'installed' -or $iw.filters -lt 8) {
242
- throw "install: wfp expected installed/>=8, got $($iw.state)/$($iw.filters)"
243
- }
244
-
245
- # Idempotency, same range: second install with identical flags is
246
- # a no-op (exit 0, "already installed").
247
- $out = & $Exe @(@('install', '--name', $instGrp) + $isl + $pr) 2>&1 | Out-String
248
- if ($LASTEXITCODE -ne 0) {
249
- throw "install idempotency: same-range expected exit 0, got $LASTEXITCODE"
250
- }
251
- if ($out -notmatch 'already installed') {
252
- throw "install idempotency: expected 'already installed' in output, got '$out'"
253
- }
254
- $iw2 = J (@('wfp', 'status') + $isl)
255
- if ($iw2.filters -ne $iw.filters) {
256
- throw "install idempotency: filters $($iw.filters) -> $($iw2.filters)"
257
- }
258
-
259
- # Conflict, different range without --force: exits 13.
260
- & $Exe @(@('install', '--name', $instGrp, '--proxy-port-range', '50000-50001') + $isl) 2>$null
261
- if ($LASTEXITCODE -ne 13) {
262
- throw "install conflict: different-range without --force expected exit 13, got $LASTEXITCODE"
263
- }
264
- # Original range still in place.
265
- $iw3 = J (@('wfp', 'status') + $isl)
266
- if ($iw3.port_range[0] -ne 60080) {
267
- throw "install conflict: range was overwritten without --force"
268
- }
269
-
270
- # --force replaces.
271
- Run (@('install', '--name', $instGrp, '--proxy-port-range', '50000-50001', '--force') + $isl)
272
- $iwF = J (@('wfp', 'status') + $isl)
273
- if ($iwF.port_range[0] -ne 50000 -or $iwF.port_range[1] -ne 50001) {
274
- throw "install --force: expected port_range [50000,50001], got [$($iwF.port_range -join ',')]"
275
- }
276
-
277
- # --group-sid path (group externally managed; here it already
278
- # exists from the --name install above). With matching range
279
- # this is the same-range no-op.
280
- Run (@('install', '--group-sid', $ig.sid, '--proxy-port-range', '50000-50001') + $isl)
281
-
282
- # Distinct exit code 11: group step fails on a malformed
283
- # --group-sid (canonicalize_sid rejects). --force so the
284
- # pre-check (which would otherwise fire on the existing
285
- # install above) is skipped and we reach the group step.
286
- & $Exe @(@('install', '--group-sid', 'not-a-sid', '--force') + $isl) 2>$null
287
- if ($LASTEXITCODE -ne 11) {
288
- throw "install: invalid --group-sid expected exit 11, got $LASTEXITCODE"
289
- }
290
-
291
- # Uninstall removes filters only — group remains.
292
- Run (@('uninstall') + $isl)
293
- $uw = J (@('wfp', 'status') + $isl)
294
- if ($uw.state -ne 'absent') {
295
- throw "uninstall: wfp expected absent, got $($uw.state)"
296
- }
297
- $ug = J @('group', 'status', '--name', $instGrp)
298
- if ($ug.state -notin 'created-not-on-token', 'ready') {
299
- throw "uninstall: group should be left intact, got $($ug.state)"
300
- }
301
- # Idempotent no-op: second uninstall must also exit 0.
302
- Run (@('uninstall') + $isl)
303
-
304
- # Explicit group teardown.
305
- Run @('group', 'delete', '--name', $instGrp)
306
-
307
- Write-Host 'srt-win smoke: OK'
@@ -1,102 +0,0 @@
1
- //! Job-object wrapper for the sandbox child.
2
- //!
3
- //! Two roles:
4
- //! 1. `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` containment — the sandboxed
5
- //! process tree dies with the broker.
6
- //! 2. `JOBOBJECT_BASIC_UI_RESTRICTIONS` — block clipboard, global
7
- //! atoms, system-parameter writes, display-settings changes,
8
- //! desktop switching, ExitWindows, and cross-job USER/GDI handle
9
- //! access. The bits are listed individually rather than relying
10
- //! on a hypothetical `_ALL` so the enforced surface is auditable
11
- //! from this call site.
12
- //!
13
- //! Both must be set BEFORE `AssignProcessToJobObject` so they're in
14
- //! effect from the moment the suspended child is assigned (the caller
15
- //! resumes the thread only after `assign`).
16
-
17
- use anyhow::{Context, Result};
18
- use std::ffi::c_void;
19
- use std::mem::{size_of, zeroed};
20
- use windows::Win32::Foundation::{CloseHandle, HANDLE};
21
- use windows::Win32::System::JobObjects::{
22
- AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject,
23
- JobObjectBasicUIRestrictions, JobObjectExtendedLimitInformation,
24
- JOBOBJECT_BASIC_UI_RESTRICTIONS, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
25
- JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOB_OBJECT_UILIMIT_DESKTOP,
26
- JOB_OBJECT_UILIMIT_DISPLAYSETTINGS, JOB_OBJECT_UILIMIT_EXITWINDOWS,
27
- JOB_OBJECT_UILIMIT_GLOBALATOMS, JOB_OBJECT_UILIMIT_HANDLES,
28
- JOB_OBJECT_UILIMIT_READCLIPBOARD, JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS,
29
- JOB_OBJECT_UILIMIT_WRITECLIPBOARD,
30
- };
31
-
32
- /// RAII job object. `Drop` closes the handle; with
33
- /// `KILL_ON_JOB_CLOSE` set, that terminates every process still in
34
- /// the job.
35
- pub struct Job(HANDLE);
36
-
37
- impl Job {
38
- /// Create an unnamed job with kill-on-close + full UI lockdown.
39
- pub fn new() -> Result<Self> {
40
- // Wrap the raw handle in `Self` immediately so a `?` from
41
- // either `SetInformationJobObject` below still closes it.
42
- let job = unsafe {
43
- Self(CreateJobObjectW(None, None).context("CreateJobObjectW")?)
44
- };
45
- unsafe {
46
- let mut ext: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = zeroed();
47
- ext.BasicLimitInformation.LimitFlags =
48
- JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
49
- SetInformationJobObject(
50
- job.0,
51
- JobObjectExtendedLimitInformation,
52
- &ext as *const _ as *const c_void,
53
- size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
54
- )
55
- .context("SetInformationJobObject(KILL_ON_JOB_CLOSE)")?;
56
-
57
- // READCLIPBOARD — block OpenClipboard for read
58
- // WRITECLIPBOARD — block SetClipboardData
59
- // HANDLES — block USER/GDI handles from outside the job
60
- // GLOBALATOMS — block GlobalAddAtom (atom-table IPC)
61
- // SYSTEMPARAMETERS — block SystemParametersInfoW(SPI_SET*)
62
- // DISPLAYSETTINGS — block ChangeDisplaySettings
63
- // DESKTOP — block SwitchDesktop / SetThreadDesktop
64
- // EXITWINDOWS — block sandbox-initiated logoff/shutdown
65
- let ui_bits = JOB_OBJECT_UILIMIT_READCLIPBOARD
66
- | JOB_OBJECT_UILIMIT_WRITECLIPBOARD
67
- | JOB_OBJECT_UILIMIT_HANDLES
68
- | JOB_OBJECT_UILIMIT_GLOBALATOMS
69
- | JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS
70
- | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS
71
- | JOB_OBJECT_UILIMIT_DESKTOP
72
- | JOB_OBJECT_UILIMIT_EXITWINDOWS;
73
- let ui = JOBOBJECT_BASIC_UI_RESTRICTIONS {
74
- UIRestrictionsClass: ui_bits,
75
- };
76
- SetInformationJobObject(
77
- job.0,
78
- JobObjectBasicUIRestrictions,
79
- &ui as *const _ as *const c_void,
80
- size_of::<JOBOBJECT_BASIC_UI_RESTRICTIONS>() as u32,
81
- )
82
- .context("SetInformationJobObject(BasicUIRestrictions)")?;
83
- }
84
- Ok(job)
85
- }
86
-
87
- /// Assign a (suspended) process to the job.
88
- pub fn assign(&self, proc: HANDLE) -> Result<()> {
89
- unsafe {
90
- AssignProcessToJobObject(self.0, proc)
91
- .context("AssignProcessToJobObject")
92
- }
93
- }
94
- }
95
-
96
- impl Drop for Job {
97
- fn drop(&mut self) {
98
- unsafe {
99
- let _ = CloseHandle(self.0);
100
- }
101
- }
102
- }