@tetsuo-ai/agenc 0.16.0 → 0.17.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/generated/agenc-runtime-manifest-v2.json +79 -79
- package/lib/runtime-manager.mjs +174 -18
- package/lib/sqlite-lock.mjs +211 -31
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 2,
|
|
3
|
-
"runtimeVersion": "0.
|
|
3
|
+
"runtimeVersion": "0.17.0",
|
|
4
4
|
"releaseRepository": "tetsuo-ai/agenc-releases",
|
|
5
|
-
"releaseTag": "agenc-v0.
|
|
5
|
+
"releaseTag": "agenc-v0.17.0",
|
|
6
6
|
"build": {
|
|
7
|
-
"sourceCommit": "
|
|
8
|
-
"sourceRef": "refs/tags/agenc-v0.
|
|
9
|
-
"sourceDateEpoch":
|
|
10
|
-
"lockfileSha256": "
|
|
7
|
+
"sourceCommit": "76811dd598b1d6ccbcd17ad9492eaa68d3f8db48",
|
|
8
|
+
"sourceRef": "refs/tags/agenc-v0.17.0",
|
|
9
|
+
"sourceDateEpoch": 1787189935,
|
|
10
|
+
"lockfileSha256": "bfa73122635a2b41c6087e8aa5fef75b3899bb65dfc78835d29498b081b3e091",
|
|
11
11
|
"nodeVersion": "v26.5.0",
|
|
12
12
|
"nodeMajor": 26,
|
|
13
13
|
"nodeModuleAbi": "147",
|
|
@@ -16,32 +16,32 @@
|
|
|
16
16
|
"artifactProfile": "release",
|
|
17
17
|
"releaseCandidate": {
|
|
18
18
|
"workflow": "release-runtime.yml",
|
|
19
|
-
"runId":
|
|
19
|
+
"runId": 32322959733,
|
|
20
20
|
"runAttempt": 1,
|
|
21
|
-
"runUrl": "https://github.com/tetsuo-ai/agenc-core/actions/runs/
|
|
21
|
+
"runUrl": "https://github.com/tetsuo-ai/agenc-core/actions/runs/32322959733",
|
|
22
22
|
"phase": "candidate",
|
|
23
23
|
"sourceRef": "refs/heads/main",
|
|
24
|
-
"evidenceSha256": "
|
|
24
|
+
"evidenceSha256": "4b812d7e855d991077c162f48a20a4e8363d697a92aed1a732302d725fd52084"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"artifacts": [
|
|
28
28
|
{
|
|
29
29
|
"platform": "darwin",
|
|
30
30
|
"arch": "arm64",
|
|
31
|
-
"runtimeVersion": "0.
|
|
31
|
+
"runtimeVersion": "0.17.0",
|
|
32
32
|
"nodeMajor": 26,
|
|
33
33
|
"nodeModuleAbi": "147",
|
|
34
34
|
"nodeApiVersion": "10",
|
|
35
|
-
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
36
|
-
"sha256": "
|
|
37
|
-
"bytes":
|
|
38
|
-
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
39
|
-
"attestationSha256": "
|
|
40
|
-
"attestationBytes":
|
|
41
|
-
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
42
|
-
"buildProvenanceSha256": "
|
|
43
|
-
"buildProvenanceBytes":
|
|
44
|
-
"metadataSha256": "
|
|
35
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-arm64-node26-abi147.tar.gz",
|
|
36
|
+
"sha256": "2dfa6d4e7b83e2cc617d81e8b6a41c5ff7cd725ef0eb7dd19fbc21707afb5a49",
|
|
37
|
+
"bytes": 115247777,
|
|
38
|
+
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-arm64-node26-abi147.tar.gz.sigstore.json",
|
|
39
|
+
"attestationSha256": "fced669b312e8931da45787d660764e1e9a7514c601314cebb192b28baa3aebf",
|
|
40
|
+
"attestationBytes": 11456,
|
|
41
|
+
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-arm64-node26-abi147.tar.gz.build.sigstore.json",
|
|
42
|
+
"buildProvenanceSha256": "703d5989c3588c79db86fc13687878eb733d920bb402971ac2e18c96bab24c79",
|
|
43
|
+
"buildProvenanceBytes": 11154,
|
|
44
|
+
"metadataSha256": "bab7b8dcf07e352cb41fd2a18a7ed7be25272cd17a5a79d5d62f39ae4b3d3b1e",
|
|
45
45
|
"nativeToolchain": {
|
|
46
46
|
"schemaVersion": 1,
|
|
47
47
|
"builder": "github-hosted:macos-15:macos15:20260727.0256.1:ARM64",
|
|
@@ -71,13 +71,13 @@
|
|
|
71
71
|
"xcode": "Xcode 16.4\nBuild version 16F6",
|
|
72
72
|
"sdk": "15.5"
|
|
73
73
|
},
|
|
74
|
-
"dependencyTreeSha256": "
|
|
74
|
+
"dependencyTreeSha256": "a7f679b076d3c5cd52992d969127502fcb5ea0252c93789e94f2968281224754",
|
|
75
75
|
"dependencyPackages": 467,
|
|
76
76
|
"archiveFormat": "tar+gzip; portable; utf8-byte-order; normalized-mtime",
|
|
77
77
|
"archiveValidation": {
|
|
78
78
|
"policy": "agenc-runtime-archive-v1",
|
|
79
|
-
"entries":
|
|
80
|
-
"uncompressedBytes":
|
|
79
|
+
"entries": 29427,
|
|
80
|
+
"uncompressedBytes": 470290944
|
|
81
81
|
},
|
|
82
82
|
"minimumMacosVersion": "13.5",
|
|
83
83
|
"bins": {
|
|
@@ -88,20 +88,20 @@
|
|
|
88
88
|
{
|
|
89
89
|
"platform": "darwin",
|
|
90
90
|
"arch": "x64",
|
|
91
|
-
"runtimeVersion": "0.
|
|
91
|
+
"runtimeVersion": "0.17.0",
|
|
92
92
|
"nodeMajor": 26,
|
|
93
93
|
"nodeModuleAbi": "147",
|
|
94
94
|
"nodeApiVersion": "10",
|
|
95
|
-
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
96
|
-
"sha256": "
|
|
97
|
-
"bytes":
|
|
98
|
-
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
99
|
-
"attestationSha256": "
|
|
100
|
-
"attestationBytes":
|
|
101
|
-
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
102
|
-
"buildProvenanceSha256": "
|
|
103
|
-
"buildProvenanceBytes":
|
|
104
|
-
"metadataSha256": "
|
|
95
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-x64-node26-abi147.tar.gz",
|
|
96
|
+
"sha256": "7d39aaa0c54782d45fe41a08ba6fe78f7f6616bb6489795db339ff831a7ed5f1",
|
|
97
|
+
"bytes": 116974284,
|
|
98
|
+
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-x64-node26-abi147.tar.gz.sigstore.json",
|
|
99
|
+
"attestationSha256": "ff935d3aa02dbffcb9bda4618fc41ce8822c968b06abd3eb75c71ebdbeb7b98b",
|
|
100
|
+
"attestationBytes": 11499,
|
|
101
|
+
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-darwin-x64-node26-abi147.tar.gz.build.sigstore.json",
|
|
102
|
+
"buildProvenanceSha256": "37aa04c34e10a43350d97db0913cf8239ff05610d2c84fa23c70e30bbc975a7d",
|
|
103
|
+
"buildProvenanceBytes": 11248,
|
|
104
|
+
"metadataSha256": "663c47381c12c967d19f5aa80a8deb758a431fa7a5ed4b281cc09496666826c8",
|
|
105
105
|
"nativeToolchain": {
|
|
106
106
|
"schemaVersion": 1,
|
|
107
107
|
"builder": "github-hosted:macos-15-intel:macos15:20260727.0377.1:X64",
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"xcode": "Xcode 16.4\nBuild version 16F6",
|
|
132
132
|
"sdk": "15.5"
|
|
133
133
|
},
|
|
134
|
-
"dependencyTreeSha256": "
|
|
134
|
+
"dependencyTreeSha256": "b75c1de51c77679e4431cee663b3dbcf3140c8efc070f9a45129fac2ff64215e",
|
|
135
135
|
"dependencyPackages": 467,
|
|
136
136
|
"archiveFormat": "tar+gzip; portable; utf8-byte-order; normalized-mtime",
|
|
137
137
|
"archiveValidation": {
|
|
138
138
|
"policy": "agenc-runtime-archive-v1",
|
|
139
|
-
"entries":
|
|
140
|
-
"uncompressedBytes":
|
|
139
|
+
"entries": 29427,
|
|
140
|
+
"uncompressedBytes": 474286080
|
|
141
141
|
},
|
|
142
142
|
"minimumMacosVersion": "13.5",
|
|
143
143
|
"bins": {
|
|
@@ -148,20 +148,20 @@
|
|
|
148
148
|
{
|
|
149
149
|
"platform": "linux",
|
|
150
150
|
"arch": "arm64",
|
|
151
|
-
"runtimeVersion": "0.
|
|
151
|
+
"runtimeVersion": "0.17.0",
|
|
152
152
|
"nodeMajor": 26,
|
|
153
153
|
"nodeModuleAbi": "147",
|
|
154
154
|
"nodeApiVersion": "10",
|
|
155
|
-
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
156
|
-
"sha256": "
|
|
157
|
-
"bytes":
|
|
158
|
-
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
159
|
-
"attestationSha256": "
|
|
160
|
-
"attestationBytes":
|
|
161
|
-
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
162
|
-
"buildProvenanceSha256": "
|
|
163
|
-
"buildProvenanceBytes":
|
|
164
|
-
"metadataSha256": "
|
|
155
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-arm64-node26-abi147.tar.gz",
|
|
156
|
+
"sha256": "0f6fb61bc3a8efeb3b02e02f0db6226c5c7918fcaa455f622f6012dacfe89149",
|
|
157
|
+
"bytes": 128833789,
|
|
158
|
+
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-arm64-node26-abi147.tar.gz.sigstore.json",
|
|
159
|
+
"attestationSha256": "7f6b1bbddf349570f0f25f2ce86c53d132872acbadee077de82bfaed5e15423d",
|
|
160
|
+
"attestationBytes": 11452,
|
|
161
|
+
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-arm64-node26-abi147.tar.gz.build.sigstore.json",
|
|
162
|
+
"buildProvenanceSha256": "265e5e5199fe9ce847376f635ed38d745109daf199b628a3058a33a8852c25cf",
|
|
163
|
+
"buildProvenanceBytes": 11181,
|
|
164
|
+
"metadataSha256": "db891bafcbf27fd039581dd6646f62b4d960c3739e954e5bf9ad3c0cfeb2d8fb",
|
|
165
165
|
"nativeToolchain": {
|
|
166
166
|
"schemaVersion": 1,
|
|
167
167
|
"builder": "rockylinux@sha256:9794037624aaa6212aeada1d28861ef5e0a935adaf93e4ef79837119f2a2d04c+rpm-content-sha256:98d1259fca66152fb91e739a15a4fa7f76b0a222369cb34ba853d7d72f02239f",
|
|
@@ -202,13 +202,13 @@
|
|
|
202
202
|
"tar-1.30-11.el8_10"
|
|
203
203
|
]
|
|
204
204
|
},
|
|
205
|
-
"dependencyTreeSha256": "
|
|
205
|
+
"dependencyTreeSha256": "d94fa45434fa59b0a3e84178e213294519a918185275bc8e3a5ca5b8555fcfe6",
|
|
206
206
|
"dependencyPackages": 469,
|
|
207
207
|
"archiveFormat": "tar+gzip; portable; utf8-byte-order; normalized-mtime",
|
|
208
208
|
"archiveValidation": {
|
|
209
209
|
"policy": "agenc-runtime-archive-v1",
|
|
210
|
-
"entries":
|
|
211
|
-
"uncompressedBytes":
|
|
210
|
+
"entries": 29448,
|
|
211
|
+
"uncompressedBytes": 492716544
|
|
212
212
|
},
|
|
213
213
|
"libcFamily": "glibc",
|
|
214
214
|
"minimumGlibcVersion": "2.28",
|
|
@@ -223,20 +223,20 @@
|
|
|
223
223
|
{
|
|
224
224
|
"platform": "linux",
|
|
225
225
|
"arch": "x64",
|
|
226
|
-
"runtimeVersion": "0.
|
|
226
|
+
"runtimeVersion": "0.17.0",
|
|
227
227
|
"nodeMajor": 26,
|
|
228
228
|
"nodeModuleAbi": "147",
|
|
229
229
|
"nodeApiVersion": "10",
|
|
230
|
-
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
231
|
-
"sha256": "
|
|
232
|
-
"bytes":
|
|
233
|
-
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
234
|
-
"attestationSha256": "
|
|
235
|
-
"attestationBytes":
|
|
236
|
-
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
237
|
-
"buildProvenanceSha256": "
|
|
238
|
-
"buildProvenanceBytes":
|
|
239
|
-
"metadataSha256": "
|
|
230
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-x64-node26-abi147.tar.gz",
|
|
231
|
+
"sha256": "9e6c1f8fea9595b50d0006207577f2a7b7114f6f334ad635f3dc5447c2394230",
|
|
232
|
+
"bytes": 128448586,
|
|
233
|
+
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-x64-node26-abi147.tar.gz.sigstore.json",
|
|
234
|
+
"attestationSha256": "3f78fde9027430ea447b759f9ab91551a9ed333fc7626c80d35a9f3753c1fc5f",
|
|
235
|
+
"attestationBytes": 11440,
|
|
236
|
+
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-linux-x64-node26-abi147.tar.gz.build.sigstore.json",
|
|
237
|
+
"buildProvenanceSha256": "678fe11567083319acf6fcde41f65e977b24abb6f0b36e827dcd760dc951e0df",
|
|
238
|
+
"buildProvenanceBytes": 11177,
|
|
239
|
+
"metadataSha256": "397ee08d3109c0ec799d82ee335444b0cb6264270330dbc5eb9c6684bbdd6ebc",
|
|
240
240
|
"nativeToolchain": {
|
|
241
241
|
"schemaVersion": 1,
|
|
242
242
|
"builder": "rockylinux@sha256:9794037624aaa6212aeada1d28861ef5e0a935adaf93e4ef79837119f2a2d04c+rpm-content-sha256:20da0fd525fe8dbe596068bb896b949fe84c5ede3c2829e9481f6c2c97de0a5e",
|
|
@@ -277,13 +277,13 @@
|
|
|
277
277
|
"tar-1.30-11.el8_10"
|
|
278
278
|
]
|
|
279
279
|
},
|
|
280
|
-
"dependencyTreeSha256": "
|
|
280
|
+
"dependencyTreeSha256": "b6121643638a0c98ed7a668243f834ea962ff88ea1b02997ab110fd73d434f17",
|
|
281
281
|
"dependencyPackages": 469,
|
|
282
282
|
"archiveFormat": "tar+gzip; portable; utf8-byte-order; normalized-mtime",
|
|
283
283
|
"archiveValidation": {
|
|
284
284
|
"policy": "agenc-runtime-archive-v1",
|
|
285
|
-
"entries":
|
|
286
|
-
"uncompressedBytes":
|
|
285
|
+
"entries": 29448,
|
|
286
|
+
"uncompressedBytes": 493779968
|
|
287
287
|
},
|
|
288
288
|
"libcFamily": "glibc",
|
|
289
289
|
"minimumGlibcVersion": "2.28",
|
|
@@ -298,20 +298,20 @@
|
|
|
298
298
|
{
|
|
299
299
|
"platform": "win",
|
|
300
300
|
"arch": "x64",
|
|
301
|
-
"runtimeVersion": "0.
|
|
301
|
+
"runtimeVersion": "0.17.0",
|
|
302
302
|
"nodeMajor": 26,
|
|
303
303
|
"nodeModuleAbi": "147",
|
|
304
304
|
"nodeApiVersion": "10",
|
|
305
|
-
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
306
|
-
"sha256": "
|
|
307
|
-
"bytes":
|
|
308
|
-
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
309
|
-
"attestationSha256": "
|
|
310
|
-
"attestationBytes":
|
|
311
|
-
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.
|
|
312
|
-
"buildProvenanceSha256": "
|
|
313
|
-
"buildProvenanceBytes":
|
|
314
|
-
"metadataSha256": "
|
|
305
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-win-x64-node26-abi147.tar.gz",
|
|
306
|
+
"sha256": "d11885737c6696198a1b396df31203938f4c4b07c00919401a6280d4be1f5423",
|
|
307
|
+
"bytes": 111364531,
|
|
308
|
+
"attestationUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-win-x64-node26-abi147.tar.gz.sigstore.json",
|
|
309
|
+
"attestationSha256": "751be37fff0bbdf7481954e26978ebc8745424336396bc2c72ff9b2b3fec3b56",
|
|
310
|
+
"attestationBytes": 11585,
|
|
311
|
+
"buildProvenanceUrl": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.17.0/agenc-runtime-0.17.0-win-x64-node26-abi147.tar.gz.build.sigstore.json",
|
|
312
|
+
"buildProvenanceSha256": "7904a4cada4dcebff05e2aa7e593ce8795b6b34c988073c9384a397102270feb",
|
|
313
|
+
"buildProvenanceBytes": 11268,
|
|
314
|
+
"metadataSha256": "97ba9768422b104228eb16089c35d9c4c77188feff1c318ee864a62a9273c5e4",
|
|
315
315
|
"nativeToolchain": {
|
|
316
316
|
"schemaVersion": 1,
|
|
317
317
|
"builder": "github-hosted:windows-2025-vs2026:win25-vs2026:20260810.198.2:X64",
|
|
@@ -353,13 +353,13 @@
|
|
|
353
353
|
"msvcCompilerSha256": "c94cdac6a780142920110e5cb8b7339817029eead696e0e97700b45e03216a00",
|
|
354
354
|
"msvcLinkerSha256": "f233b8e337cec96a69868a8cde676808bfa81152493968d0b27b7cd0daac15be"
|
|
355
355
|
},
|
|
356
|
-
"dependencyTreeSha256": "
|
|
356
|
+
"dependencyTreeSha256": "874ac4e08df60e2649dc5b9a9e6d96f01c6e161aa7463329d6f355ce1d2fd5de",
|
|
357
357
|
"dependencyPackages": 466,
|
|
358
358
|
"archiveFormat": "tar+gzip; portable; utf8-byte-order; normalized-mtime",
|
|
359
359
|
"archiveValidation": {
|
|
360
360
|
"policy": "agenc-runtime-archive-v1",
|
|
361
|
-
"entries":
|
|
362
|
-
"uncompressedBytes":
|
|
361
|
+
"entries": 29466,
|
|
362
|
+
"uncompressedBytes": 434596864
|
|
363
363
|
},
|
|
364
364
|
"bins": {
|
|
365
365
|
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc",
|
package/lib/runtime-manager.mjs
CHANGED
|
@@ -75,6 +75,141 @@ const MANIFEST_PATH = resolve(
|
|
|
75
75
|
const DEFAULT_RUNTIME_FETCH_TIMEOUT_MS = 120_000;
|
|
76
76
|
const DEFAULT_RUNTIME_EXTRACTION_TIMEOUT_MS = 120_000;
|
|
77
77
|
const WINDOWS_SYSTEM_ROOT = String.raw`\\?\GLOBALROOT\SystemRoot`;
|
|
78
|
+
const WINDOWS_INVALID_FILE_ID = 0xffff_ffff_ffff_ffffn;
|
|
79
|
+
const WINDOWS_RESERVED_DEVICE_BASENAME =
|
|
80
|
+
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
|
|
81
|
+
const WINDOWS_EXECUTABLE_FILESYSTEM = {
|
|
82
|
+
lstat: (path) => lstatSync(path, { bigint: true }),
|
|
83
|
+
open: (path) => openSync(
|
|
84
|
+
path,
|
|
85
|
+
fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0),
|
|
86
|
+
),
|
|
87
|
+
fstat: (descriptor) => fstatSync(descriptor, { bigint: true }),
|
|
88
|
+
close: closeSync,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function trustedWindowsSystemPaths(canonicalize = realpathSync.native) {
|
|
92
|
+
const systemRoot = canonicalize(WINDOWS_SYSTEM_ROOT);
|
|
93
|
+
if (!/^[a-z]:\\/iu.test(systemRoot) || win32.normalize(systemRoot) !== systemRoot) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"agenc: trusted Windows SystemRoot did not resolve to a canonical local DOS path",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const system32 = win32.join(systemRoot, "System32");
|
|
99
|
+
const powershellRoot = win32.join(system32, "WindowsPowerShell", "v1.0");
|
|
100
|
+
return {
|
|
101
|
+
systemRoot,
|
|
102
|
+
system32,
|
|
103
|
+
powershellRoot,
|
|
104
|
+
namespaceSystemRoot: WINDOWS_SYSTEM_ROOT,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function trustedWindowsSystemExecutable(
|
|
109
|
+
paths,
|
|
110
|
+
relativeSegments,
|
|
111
|
+
filesystem = WINDOWS_EXECUTABLE_FILESYSTEM,
|
|
112
|
+
) {
|
|
113
|
+
if (
|
|
114
|
+
relativeSegments.length === 0 ||
|
|
115
|
+
relativeSegments.some((segment) => !isSafeWindowsExecutableSegment(segment))
|
|
116
|
+
) {
|
|
117
|
+
throw new TypeError("trusted Windows executable segments are invalid");
|
|
118
|
+
}
|
|
119
|
+
const executable = win32.join(paths.systemRoot, ...relativeSegments);
|
|
120
|
+
const namespaceExecutable = win32.join(
|
|
121
|
+
paths.namespaceSystemRoot,
|
|
122
|
+
...relativeSegments,
|
|
123
|
+
);
|
|
124
|
+
verifyWindowsExecutableAliases(namespaceExecutable, executable, filesystem);
|
|
125
|
+
return executable;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isSafeWindowsExecutableSegment(segment) {
|
|
129
|
+
return segment.length > 0 && segment !== "." && segment !== ".." &&
|
|
130
|
+
win32.basename(segment) === segment &&
|
|
131
|
+
!/[\u0000-\u001f<>:"/\\|?*]/u.test(segment) && !/[ .]$/u.test(segment) &&
|
|
132
|
+
!WINDOWS_RESERVED_DEVICE_BASENAME.test(segment);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function verifyWindowsExecutableAliases(namespaceExecutable, executable, filesystem) {
|
|
136
|
+
let namespaceDescriptor;
|
|
137
|
+
let candidateDescriptor;
|
|
138
|
+
let operationError;
|
|
139
|
+
try {
|
|
140
|
+
const namespaceBefore = filesystem.lstat(namespaceExecutable);
|
|
141
|
+
const candidateBefore = filesystem.lstat(executable);
|
|
142
|
+
assertRegularWindowsExecutable(namespaceBefore, "GLOBALROOT path");
|
|
143
|
+
assertRegularWindowsExecutable(candidateBefore, "DOS path");
|
|
144
|
+
namespaceDescriptor = filesystem.open(namespaceExecutable);
|
|
145
|
+
candidateDescriptor = filesystem.open(executable);
|
|
146
|
+
const namespaceOpened = filesystem.fstat(namespaceDescriptor);
|
|
147
|
+
const candidateOpened = filesystem.fstat(candidateDescriptor);
|
|
148
|
+
const namespaceAfter = filesystem.lstat(namespaceExecutable);
|
|
149
|
+
const candidateAfter = filesystem.lstat(executable);
|
|
150
|
+
assertRegularWindowsExecutable(namespaceOpened, "GLOBALROOT descriptor");
|
|
151
|
+
assertRegularWindowsExecutable(candidateOpened, "DOS descriptor");
|
|
152
|
+
assertRegularWindowsExecutable(namespaceAfter, "GLOBALROOT path");
|
|
153
|
+
assertRegularWindowsExecutable(candidateAfter, "DOS path");
|
|
154
|
+
for (const identity of [
|
|
155
|
+
namespaceBefore,
|
|
156
|
+
candidateBefore,
|
|
157
|
+
namespaceOpened,
|
|
158
|
+
candidateOpened,
|
|
159
|
+
namespaceAfter,
|
|
160
|
+
candidateAfter,
|
|
161
|
+
]) {
|
|
162
|
+
if (
|
|
163
|
+
identity.dev <= 0n || identity.ino <= 0n ||
|
|
164
|
+
identity.dev === WINDOWS_INVALID_FILE_ID || identity.ino === WINDOWS_INVALID_FILE_ID
|
|
165
|
+
) {
|
|
166
|
+
throw new Error("trusted Windows system executable identity is unavailable");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (
|
|
170
|
+
!sameWindowsExecutableIdentity(namespaceBefore, namespaceOpened) ||
|
|
171
|
+
!sameWindowsExecutableIdentity(namespaceOpened, namespaceAfter) ||
|
|
172
|
+
!sameWindowsExecutableIdentity(candidateBefore, candidateOpened) ||
|
|
173
|
+
!sameWindowsExecutableIdentity(candidateOpened, candidateAfter) ||
|
|
174
|
+
!sameWindowsExecutableIdentity(namespaceOpened, candidateOpened)
|
|
175
|
+
) {
|
|
176
|
+
throw new Error("trusted Windows system executable identity mismatch");
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
operationError = error;
|
|
180
|
+
}
|
|
181
|
+
const closeErrors = [];
|
|
182
|
+
for (const descriptor of [candidateDescriptor, namespaceDescriptor]) {
|
|
183
|
+
if (descriptor === undefined) continue;
|
|
184
|
+
try { filesystem.close(descriptor); } catch (error) { closeErrors.push(error); }
|
|
185
|
+
}
|
|
186
|
+
if (operationError !== undefined) {
|
|
187
|
+
if (closeErrors.length > 0) {
|
|
188
|
+
throw new AggregateError(
|
|
189
|
+
[operationError, ...closeErrors],
|
|
190
|
+
"trusted Windows executable validation and cleanup both failed",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
throw operationError;
|
|
194
|
+
}
|
|
195
|
+
if (closeErrors.length === 1) throw closeErrors[0];
|
|
196
|
+
if (closeErrors.length > 1) {
|
|
197
|
+
throw new AggregateError(
|
|
198
|
+
closeErrors,
|
|
199
|
+
"trusted Windows executable descriptor cleanup failed",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function assertRegularWindowsExecutable(identity, spelling) {
|
|
205
|
+
if (!identity.isFile() || identity.isSymbolicLink()) {
|
|
206
|
+
throw new Error(`trusted Windows ${spelling} executable is not a regular non-link file`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function sameWindowsExecutableIdentity(left, right) {
|
|
211
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
212
|
+
}
|
|
78
213
|
|
|
79
214
|
export function resolveAgenCHome(env = process.env, userHome = homedir()) {
|
|
80
215
|
const configured = env.AGENC_HOME;
|
|
@@ -1057,9 +1192,19 @@ async function download(
|
|
|
1057
1192
|
function assertRootOwnedSystemExecutable(path) {
|
|
1058
1193
|
const canonical = realpathSync.native(path);
|
|
1059
1194
|
const executable = lstatSync(canonical, { bigint: true });
|
|
1195
|
+
// Deliberately no link-count requirement, matching the shell installer's
|
|
1196
|
+
// resolve_system_tool. Stock system tools legitimately ship as several names
|
|
1197
|
+
// for one inode (macOS and Ubuntu both hardlink unzip/zipinfo; some
|
|
1198
|
+
// distributions link tar/gtar), and no user can change that under SIP.
|
|
1199
|
+
// Root ownership plus the fully root-owned, non-group/other-writable
|
|
1200
|
+
// ancestor chain below is what buys the guarantee: a non-root user can
|
|
1201
|
+
// neither add a link inside such a tree nor alter the root-owned inode
|
|
1202
|
+
// through a link made elsewhere. The nlink !== 1n checks on files this
|
|
1203
|
+
// launcher downloads into its own private tree stay, because there a second
|
|
1204
|
+
// link is a genuine TOCTOU signal.
|
|
1060
1205
|
if (
|
|
1061
1206
|
!executable.isFile() || executable.isSymbolicLink() ||
|
|
1062
|
-
executable.
|
|
1207
|
+
executable.uid !== 0n ||
|
|
1063
1208
|
(executable.mode & 0o022n) !== 0n
|
|
1064
1209
|
) {
|
|
1065
1210
|
throw new Error(`agenc: operating-system tar is not a trusted regular file: ${canonical}`);
|
|
@@ -1096,31 +1241,39 @@ export function resolveTrustedSystemTar(platform = process.platform) {
|
|
|
1096
1241
|
throw new Error(`agenc: trusted operating-system tar is unsupported on ${platform}`);
|
|
1097
1242
|
}
|
|
1098
1243
|
|
|
1099
|
-
//
|
|
1100
|
-
//
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1103
|
-
const tarPath =
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1244
|
+
// Derive CreateProcess-compatible DOS spellings from GLOBALROOT and prove
|
|
1245
|
+
// each executable still aliases its namespace path immediately before use.
|
|
1246
|
+
const windowsPaths = trustedWindowsSystemPaths();
|
|
1247
|
+
const { systemRoot, system32, powershellRoot } = windowsPaths;
|
|
1248
|
+
const tarPath = trustedWindowsSystemExecutable(windowsPaths, [
|
|
1249
|
+
"System32",
|
|
1250
|
+
"tar.exe",
|
|
1251
|
+
]);
|
|
1252
|
+
const powershell = trustedWindowsSystemExecutable(windowsPaths, [
|
|
1253
|
+
"System32",
|
|
1254
|
+
"WindowsPowerShell",
|
|
1255
|
+
"v1.0",
|
|
1256
|
+
"powershell.exe",
|
|
1257
|
+
]);
|
|
1111
1258
|
const env = {
|
|
1112
1259
|
APPDATA: "",
|
|
1113
1260
|
COMSPEC: win32.join(system32, "cmd.exe"),
|
|
1114
1261
|
HOME: "",
|
|
1262
|
+
HOMEDRIVE: "",
|
|
1263
|
+
HOMEPATH: "",
|
|
1115
1264
|
LOCALAPPDATA: "",
|
|
1265
|
+
LOGONSERVER: "",
|
|
1116
1266
|
PATH: `${system32};${powershellRoot}`,
|
|
1117
1267
|
PATHEXT: ".COM;.EXE",
|
|
1118
1268
|
PSModulePath: win32.join(powershellRoot, "Modules"),
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1269
|
+
SYSTEMDRIVE: "",
|
|
1270
|
+
SystemRoot: systemRoot,
|
|
1271
|
+
TEMP: win32.join(systemRoot, "Temp"),
|
|
1272
|
+
TMP: win32.join(systemRoot, "Temp"),
|
|
1273
|
+
USERDOMAIN: "",
|
|
1274
|
+
USERNAME: "",
|
|
1122
1275
|
USERPROFILE: powershellRoot,
|
|
1123
|
-
WINDIR:
|
|
1276
|
+
WINDIR: systemRoot,
|
|
1124
1277
|
};
|
|
1125
1278
|
const signature = spawnSync(
|
|
1126
1279
|
powershell,
|
|
@@ -1149,7 +1302,10 @@ export function resolveTrustedSystemTar(platform = process.platform) {
|
|
|
1149
1302
|
if (signature.error !== undefined || signature.status !== 0) {
|
|
1150
1303
|
throw new Error("agenc: Windows operating-system tar failed Authenticode validation");
|
|
1151
1304
|
}
|
|
1152
|
-
return {
|
|
1305
|
+
return {
|
|
1306
|
+
path: trustedWindowsSystemExecutable(windowsPaths, ["System32", "tar.exe"]),
|
|
1307
|
+
env,
|
|
1308
|
+
};
|
|
1153
1309
|
}
|
|
1154
1310
|
|
|
1155
1311
|
function extractTarGz(archive, destDir) {
|
package/lib/sqlite-lock.mjs
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
// contention is retried asynchronously against one monotonic deadline.
|
|
8
8
|
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
|
+
import {
|
|
11
|
+
closeSync,
|
|
12
|
+
constants as fsConstants,
|
|
13
|
+
fstatSync,
|
|
14
|
+
lstatSync,
|
|
15
|
+
openSync,
|
|
16
|
+
realpathSync,
|
|
17
|
+
} from "node:fs";
|
|
10
18
|
import {
|
|
11
19
|
chmod,
|
|
12
20
|
lstat,
|
|
@@ -33,7 +41,18 @@ const REGISTRY_SYMBOL = Symbol.for("@tetsuo-ai/agenc.sqlite-lock-registry");
|
|
|
33
41
|
const MAX_BUSY_RETRY_MS = 50;
|
|
34
42
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
35
43
|
const UNSUPPORTED_FILE_ID_64 = 0xffff_ffff_ffff_ffffn;
|
|
44
|
+
const WINDOWS_PATH_TRANSPORT_MAX_CHARS = 16_384;
|
|
45
|
+
const WINDOWS_PATH_TRANSPORT_MAX_ENTRIES = 128;
|
|
36
46
|
const WINDOWS_SYSTEM_ROOT = String.raw`\\?\GLOBALROOT\SystemRoot`;
|
|
47
|
+
const WINDOWS_EXECUTABLE_FILESYSTEM = {
|
|
48
|
+
lstat: (path) => lstatSync(path, { bigint: true }),
|
|
49
|
+
open: (path) => openSync(
|
|
50
|
+
path,
|
|
51
|
+
fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0),
|
|
52
|
+
),
|
|
53
|
+
fstat: (descriptor) => fstatSync(descriptor, { bigint: true }),
|
|
54
|
+
close: closeSync,
|
|
55
|
+
};
|
|
37
56
|
const LOCAL_FILESYSTEM_TYPES = new Set([
|
|
38
57
|
"apfs", "bcachefs", "btrfs", "exfat", "ext2", "ext3", "ext4", "f2fs",
|
|
39
58
|
"hfs", "hfsplus", "jfs", "msdos", "nilfs2", "ntfs", "ntfs3", "overlay",
|
|
@@ -57,7 +76,15 @@ const DARWIN_ACL_KNOWN_TOKENS = new Set([
|
|
|
57
76
|
|
|
58
77
|
const WINDOWS_SECURITY_SCRIPT = String.raw`
|
|
59
78
|
$ErrorActionPreference = 'Stop'
|
|
60
|
-
|
|
79
|
+
# Keep this direct-.NET only: module autoload can exhaust the shared lock deadline.
|
|
80
|
+
$transport = [string]$env:AGENC_LOCK_PATHS
|
|
81
|
+
if ([string]::IsNullOrEmpty($transport) -or $transport.Length -gt ${WINDOWS_PATH_TRANSPORT_MAX_CHARS}) {
|
|
82
|
+
throw 'invalid protected-path transport'
|
|
83
|
+
}
|
|
84
|
+
$entries = [string[]]$transport.Split([char]10)
|
|
85
|
+
if ($entries.Count -lt 1 -or $entries.Count -gt ${WINDOWS_PATH_TRANSPORT_MAX_ENTRIES}) {
|
|
86
|
+
throw 'invalid protected-path transport'
|
|
87
|
+
}
|
|
61
88
|
$currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
62
89
|
$trusted = @(
|
|
63
90
|
$currentSid,
|
|
@@ -65,27 +92,50 @@ $trusted = @(
|
|
|
65
92
|
'S-1-5-32-544',
|
|
66
93
|
'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'
|
|
67
94
|
)
|
|
68
|
-
|
|
69
|
-
$
|
|
95
|
+
# Specific mutation rights plus GENERIC_WRITE and GENERIC_ALL.
|
|
96
|
+
$leafMutationMask = [int64]1343029590
|
|
97
|
+
$ancestorMutationMask = [int64]1343029586
|
|
70
98
|
foreach ($entry in $entries) {
|
|
71
|
-
$
|
|
72
|
-
$
|
|
99
|
+
$separator = $entry.IndexOf([char]58)
|
|
100
|
+
if ($separator -lt 1 -or $separator -eq ($entry.Length - 1)) {
|
|
101
|
+
throw 'invalid protected-path transport'
|
|
102
|
+
}
|
|
103
|
+
$role = $entry.Substring(0, $separator)
|
|
73
104
|
if (@('leafDirectory', 'ancestorDirectory', 'file') -notcontains $role) {
|
|
74
105
|
throw "invalid protected-path role: $role"
|
|
75
106
|
}
|
|
107
|
+
$encodedPath = $entry.Substring($separator + 1)
|
|
108
|
+
if (($encodedPath.Length % 4) -ne 0) { throw 'invalid protected-path transport' }
|
|
109
|
+
$pathBytes = [System.Convert]::FromBase64String($encodedPath)
|
|
110
|
+
if (($pathBytes.Length % 2) -ne 0) { throw 'invalid protected-path transport' }
|
|
111
|
+
if ([System.Convert]::ToBase64String($pathBytes) -cne $encodedPath) {
|
|
112
|
+
throw 'non-canonical protected-path transport'
|
|
113
|
+
}
|
|
114
|
+
$pathCharacters = [char[]]::new([int]($pathBytes.Length / 2))
|
|
115
|
+
for ($index = 0; $index -lt $pathCharacters.Length; $index += 1) {
|
|
116
|
+
$byteOffset = $index * 2
|
|
117
|
+
$lowByte = [int]$pathBytes[$byteOffset]
|
|
118
|
+
$highByte = [int]$pathBytes[$byteOffset + 1]
|
|
119
|
+
$pathCharacters[$index] = [char]($lowByte -bor ($highByte -shl 8))
|
|
120
|
+
}
|
|
121
|
+
$requested = [string]::new($pathCharacters)
|
|
76
122
|
$mutationMask = if ($role -eq 'ancestorDirectory') {
|
|
77
123
|
$ancestorMutationMask
|
|
78
124
|
} else {
|
|
79
125
|
$leafMutationMask
|
|
80
126
|
}
|
|
81
|
-
$full = [System.IO.Path]::GetFullPath(
|
|
127
|
+
$full = [System.IO.Path]::GetFullPath($requested)
|
|
82
128
|
if ($full.StartsWith('\\') -or $full.StartsWith('\\?\') -or $full.StartsWith('\\.\')) {
|
|
83
129
|
throw "network and device paths are unsupported: $full"
|
|
84
130
|
}
|
|
85
|
-
$
|
|
86
|
-
if (($
|
|
131
|
+
$attributes = [System.IO.File]::GetAttributes($full)
|
|
132
|
+
if (($attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
|
|
87
133
|
throw "reparse points are unsupported: $full"
|
|
88
134
|
}
|
|
135
|
+
$isDirectory = ($attributes -band [System.IO.FileAttributes]::Directory) -ne 0
|
|
136
|
+
if ($isDirectory -ne ($role -ne 'file')) {
|
|
137
|
+
throw "protected-path role does not match its type: $full"
|
|
138
|
+
}
|
|
89
139
|
$drive = [System.IO.DriveInfo]::new([System.IO.Path]::GetPathRoot($full))
|
|
90
140
|
if (@(2, 3, 6) -notcontains [int]$drive.DriveType) {
|
|
91
141
|
throw "non-local drive is unsupported: $full"
|
|
@@ -93,12 +143,16 @@ foreach ($entry in $entries) {
|
|
|
93
143
|
if ($drive.DriveFormat -ne 'NTFS') {
|
|
94
144
|
throw "filesystem cannot enforce the required ACL contract: $full"
|
|
95
145
|
}
|
|
96
|
-
$
|
|
146
|
+
$aclSections = [System.Security.AccessControl.AccessControlSections]::Owner -bor [System.Security.AccessControl.AccessControlSections]::Access
|
|
147
|
+
if ($isDirectory) {
|
|
148
|
+
$acl = [System.IO.Directory]::GetAccessControl($full, $aclSections)
|
|
149
|
+
} else {
|
|
150
|
+
$acl = [System.IO.File]::GetAccessControl($full, $aclSections)
|
|
151
|
+
}
|
|
97
152
|
if (-not $acl.AreAccessRulesCanonical) {
|
|
98
153
|
throw "non-canonical ACL is unsupported: $full"
|
|
99
154
|
}
|
|
100
|
-
$bytes =
|
|
101
|
-
$acl.GetSecurityDescriptorBinaryForm($bytes, 0)
|
|
155
|
+
$bytes = $acl.GetSecurityDescriptorBinaryForm()
|
|
102
156
|
$raw = [System.Security.AccessControl.RawSecurityDescriptor]::new($bytes, 0)
|
|
103
157
|
if ($null -eq $raw.DiscretionaryAcl) {
|
|
104
158
|
throw "null DACL is unsupported: $full"
|
|
@@ -125,7 +179,8 @@ foreach ($entry in $entries) {
|
|
|
125
179
|
}
|
|
126
180
|
}
|
|
127
181
|
$sid = $rule.IdentityReference.Value
|
|
128
|
-
|
|
182
|
+
$rights = ([int64]$rule.FileSystemRights) -band [int64]4294967295
|
|
183
|
+
if ($trusted -notcontains $sid -and (($rights -band $mutationMask) -ne 0)) {
|
|
129
184
|
throw "untrusted mutation ACE on lock path: $full"
|
|
130
185
|
}
|
|
131
186
|
}
|
|
@@ -378,31 +433,124 @@ async function assertDarwinPathSecurity(path, role, context) {
|
|
|
378
433
|
throwIfExpired(context, path);
|
|
379
434
|
}
|
|
380
435
|
|
|
381
|
-
function trustedWindowsPowerShellPath(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
436
|
+
function trustedWindowsPowerShellPath(
|
|
437
|
+
canonicalize = realpathSync.native,
|
|
438
|
+
filesystem = WINDOWS_EXECUTABLE_FILESYSTEM,
|
|
439
|
+
) {
|
|
440
|
+
// Derive a CreateProcess-compatible DOS spelling from GLOBALROOT, then prove
|
|
441
|
+
// both spellings still name the same regular system file before launch.
|
|
442
|
+
const systemRoot = canonicalize(WINDOWS_SYSTEM_ROOT);
|
|
443
|
+
if (!/^[a-z]:\\/iu.test(systemRoot) || win32.normalize(systemRoot) !== systemRoot) {
|
|
444
|
+
throw new Error("trusted Windows SystemRoot did not resolve to a canonical local DOS path");
|
|
445
|
+
}
|
|
446
|
+
const workingDirectory = win32.join(systemRoot, "System32");
|
|
447
|
+
const executable = win32.join(
|
|
448
|
+
workingDirectory,
|
|
449
|
+
"WindowsPowerShell",
|
|
450
|
+
"v1.0",
|
|
451
|
+
"powershell.exe",
|
|
452
|
+
);
|
|
453
|
+
const namespaceExecutable = win32.join(
|
|
454
|
+
WINDOWS_SYSTEM_ROOT,
|
|
455
|
+
"System32",
|
|
456
|
+
"WindowsPowerShell",
|
|
457
|
+
"v1.0",
|
|
458
|
+
"powershell.exe",
|
|
459
|
+
);
|
|
460
|
+
verifyWindowsExecutableAliases(namespaceExecutable, executable, filesystem);
|
|
386
461
|
return {
|
|
387
|
-
systemRoot
|
|
388
|
-
workingDirectory
|
|
389
|
-
executable
|
|
390
|
-
WINDOWS_SYSTEM_ROOT,
|
|
391
|
-
"System32",
|
|
392
|
-
"WindowsPowerShell",
|
|
393
|
-
"v1.0",
|
|
394
|
-
"powershell.exe",
|
|
395
|
-
),
|
|
462
|
+
systemRoot,
|
|
463
|
+
workingDirectory,
|
|
464
|
+
executable,
|
|
396
465
|
};
|
|
397
466
|
}
|
|
398
467
|
|
|
399
|
-
function
|
|
400
|
-
|
|
468
|
+
function verifyWindowsExecutableAliases(namespaceExecutable, executable, filesystem) {
|
|
469
|
+
let namespaceDescriptor;
|
|
470
|
+
let candidateDescriptor;
|
|
471
|
+
let operationError;
|
|
472
|
+
try {
|
|
473
|
+
const namespaceBefore = filesystem.lstat(namespaceExecutable);
|
|
474
|
+
const candidateBefore = filesystem.lstat(executable);
|
|
475
|
+
assertRegularWindowsExecutable(namespaceBefore, "GLOBALROOT path");
|
|
476
|
+
assertRegularWindowsExecutable(candidateBefore, "DOS path");
|
|
477
|
+
namespaceDescriptor = filesystem.open(namespaceExecutable);
|
|
478
|
+
candidateDescriptor = filesystem.open(executable);
|
|
479
|
+
const namespaceOpened = filesystem.fstat(namespaceDescriptor);
|
|
480
|
+
const candidateOpened = filesystem.fstat(candidateDescriptor);
|
|
481
|
+
const namespaceAfter = filesystem.lstat(namespaceExecutable);
|
|
482
|
+
const candidateAfter = filesystem.lstat(executable);
|
|
483
|
+
assertRegularWindowsExecutable(namespaceOpened, "GLOBALROOT descriptor");
|
|
484
|
+
assertRegularWindowsExecutable(candidateOpened, "DOS descriptor");
|
|
485
|
+
assertRegularWindowsExecutable(namespaceAfter, "GLOBALROOT path");
|
|
486
|
+
assertRegularWindowsExecutable(candidateAfter, "DOS path");
|
|
487
|
+
for (const identity of [
|
|
488
|
+
namespaceBefore,
|
|
489
|
+
candidateBefore,
|
|
490
|
+
namespaceOpened,
|
|
491
|
+
candidateOpened,
|
|
492
|
+
namespaceAfter,
|
|
493
|
+
candidateAfter,
|
|
494
|
+
]) {
|
|
495
|
+
if (
|
|
496
|
+
identity.dev <= 0n || identity.ino <= 0n ||
|
|
497
|
+
identity.dev === UNSUPPORTED_FILE_ID_64 || identity.ino === UNSUPPORTED_FILE_ID_64
|
|
498
|
+
) {
|
|
499
|
+
throw new Error("trusted Windows system executable identity is unavailable");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (
|
|
503
|
+
!sameFileIdentity(namespaceBefore, namespaceOpened) ||
|
|
504
|
+
!sameFileIdentity(namespaceOpened, namespaceAfter) ||
|
|
505
|
+
!sameFileIdentity(candidateBefore, candidateOpened) ||
|
|
506
|
+
!sameFileIdentity(candidateOpened, candidateAfter) ||
|
|
507
|
+
!sameFileIdentity(namespaceOpened, candidateOpened)
|
|
508
|
+
) {
|
|
509
|
+
throw new Error("trusted Windows system executable identity mismatch");
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
operationError = error;
|
|
513
|
+
}
|
|
514
|
+
const closeErrors = [];
|
|
515
|
+
for (const descriptor of [candidateDescriptor, namespaceDescriptor]) {
|
|
516
|
+
if (descriptor === undefined) continue;
|
|
517
|
+
try { filesystem.close(descriptor); } catch (error) { closeErrors.push(error); }
|
|
518
|
+
}
|
|
519
|
+
if (operationError !== undefined) {
|
|
520
|
+
if (closeErrors.length > 0) {
|
|
521
|
+
throw new AggregateError(
|
|
522
|
+
[operationError, ...closeErrors],
|
|
523
|
+
"trusted Windows executable validation and cleanup both failed",
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
throw operationError;
|
|
527
|
+
}
|
|
528
|
+
if (closeErrors.length === 1) throw closeErrors[0];
|
|
529
|
+
if (closeErrors.length > 1) {
|
|
530
|
+
throw new AggregateError(
|
|
531
|
+
closeErrors,
|
|
532
|
+
"trusted Windows executable descriptor cleanup failed",
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function assertRegularWindowsExecutable(identity, spelling) {
|
|
538
|
+
if (!identity.isFile() || identity.isSymbolicLink()) {
|
|
539
|
+
throw new Error(`trusted Windows ${spelling} executable is not a regular non-link file`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function sameFileIdentity(left, right) {
|
|
544
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function windowsPowerShellEnvironment(paths, trustedPaths = trustedWindowsPowerShellPath()) {
|
|
548
|
+
const { systemRoot, workingDirectory } = trustedPaths;
|
|
401
549
|
// libuv fills a fixed set of "required" Windows variables from the parent
|
|
402
550
|
// when they are absent. Define every one so poisoned caller state cannot be
|
|
403
551
|
// silently inherited into the validation helper.
|
|
404
552
|
return {
|
|
405
|
-
|
|
553
|
+
AGENC_LOCK_PATHS: windowsPathTransport(paths),
|
|
406
554
|
APPDATA: "",
|
|
407
555
|
COMSPEC: "",
|
|
408
556
|
HOMEDRIVE: "",
|
|
@@ -423,10 +571,42 @@ function windowsPowerShellEnvironment(paths) {
|
|
|
423
571
|
};
|
|
424
572
|
}
|
|
425
573
|
|
|
574
|
+
function windowsPathTransport(entries) {
|
|
575
|
+
if (
|
|
576
|
+
!Array.isArray(entries) ||
|
|
577
|
+
entries.length < 1 ||
|
|
578
|
+
entries.length > WINDOWS_PATH_TRANSPORT_MAX_ENTRIES
|
|
579
|
+
) {
|
|
580
|
+
throw new Error("agenc: Windows protected-path transport has an invalid entry count");
|
|
581
|
+
}
|
|
582
|
+
const records = [];
|
|
583
|
+
let transportChars = 0;
|
|
584
|
+
for (const entry of entries) {
|
|
585
|
+
const path = entry?.path;
|
|
586
|
+
const role = entry?.role;
|
|
587
|
+
if (
|
|
588
|
+
typeof path !== "string" ||
|
|
589
|
+
path.length === 0 ||
|
|
590
|
+
path.length > WINDOWS_PATH_TRANSPORT_MAX_CHARS ||
|
|
591
|
+
(role !== "leafDirectory" && role !== "ancestorDirectory" && role !== "file")
|
|
592
|
+
) {
|
|
593
|
+
throw new Error("agenc: Windows protected-path transport entry is invalid");
|
|
594
|
+
}
|
|
595
|
+
const record = `${role}:${Buffer.from(path, "utf16le").toString("base64")}`;
|
|
596
|
+
transportChars += record.length + (records.length === 0 ? 0 : 1);
|
|
597
|
+
if (transportChars > WINDOWS_PATH_TRANSPORT_MAX_CHARS) {
|
|
598
|
+
throw new Error("agenc: Windows protected-path transport exceeds its limit");
|
|
599
|
+
}
|
|
600
|
+
records.push(record);
|
|
601
|
+
}
|
|
602
|
+
return records.join("\n");
|
|
603
|
+
}
|
|
604
|
+
|
|
426
605
|
async function assertWindowsPathSecurity(entries, context) {
|
|
427
606
|
const displayPath = entries.at(-1)?.path ?? "unknown";
|
|
428
607
|
throwIfExpired(context, displayPath);
|
|
429
|
-
const
|
|
608
|
+
const trustedPaths = trustedWindowsPowerShellPath();
|
|
609
|
+
const { workingDirectory, executable } = trustedPaths;
|
|
430
610
|
let result;
|
|
431
611
|
try {
|
|
432
612
|
result = await execFileUtf8(
|
|
@@ -440,7 +620,7 @@ async function assertWindowsPathSecurity(entries, context) {
|
|
|
440
620
|
],
|
|
441
621
|
{
|
|
442
622
|
cwd: workingDirectory,
|
|
443
|
-
env: windowsPowerShellEnvironment(entries),
|
|
623
|
+
env: windowsPowerShellEnvironment(entries, trustedPaths),
|
|
444
624
|
maxBuffer: 1024 * 1024,
|
|
445
625
|
windowsHide: true,
|
|
446
626
|
},
|