mcp-pacemaker 2.0.2 → 2.1.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.
- package/CHANGELOG.md +171 -1
- package/CONTRIBUTING.md +46 -13
- package/README.md +109 -524
- package/THIRD_PARTY_NOTICES.txt +1 -28
- package/autostart/windows/account-task.ps1 +167 -0
- package/autostart/windows/inspect-task.ps1 +42 -0
- package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
- package/autostart/windows/legacy-access.ps1 +359 -0
- package/autostart/windows/legacy-task.ps1 +301 -0
- package/autostart/windows/register-task.ps1 +11 -2
- package/bin/account-task-adapter.mjs +47 -0
- package/bin/account-upgrade-controller.mjs +198 -0
- package/bin/account-upgrade-worker.mjs +141 -0
- package/bin/account-worker-session.mjs +215 -0
- package/bin/cli-dispatch.mjs +154 -0
- package/bin/cli-main.mjs +1063 -0
- package/bin/cli-selection.mjs +43 -0
- package/bin/cli.mjs +7 -876
- package/bin/dependency-lock.json +642 -0
- package/bin/legacy-1.3.json +32 -0
- package/bin/legacy-access.mjs +73 -0
- package/bin/legacy-installation.mjs +273 -0
- package/bin/legacy-task.mjs +76 -0
- package/bin/legacy-upgrade.mjs +527 -0
- package/bin/managed-runtime.mjs +170 -0
- package/bin/managed-state.mjs +257 -0
- package/bin/managed-upgrade.mjs +581 -0
- package/bin/mcp-bridge.mjs +150 -4
- package/bin/pooling-batches.mjs +13 -0
- package/bin/pooling-writer.mjs +4 -0
- package/bin/prewarm.mjs +59 -25
- package/bin/runtime-context.mjs +100 -0
- package/bin/service-control.mjs +32 -4
- package/bin/stable-launcher.mjs +54 -0
- package/bin/task-only-controller.mjs +226 -0
- package/bin/task-scope.mjs +65 -0
- package/bin/task-transaction-protocol.mjs +173 -0
- package/bin/top.mjs +22 -10
- package/bin/update-check.mjs +13 -2
- package/bin/upgrade-autostart.mjs +47 -0
- package/bin/upgrade-capability.json +9 -0
- package/bin/upgrade-dependencies.mjs +183 -0
- package/bin/upgrade-package.mjs +205 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
- package/bin/windows-legacy/src/LegacyNative.cs +832 -0
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +961 -0
- package/bin/windows-legacy-process.mjs +248 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
- package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
- package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
- package/bin/windows-process-lifetime.mjs +218 -0
- package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
- package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
- package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
- package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
- package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
- package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
- package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
- package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
- package/bin/windows-task-channel.mjs +335 -0
- package/docs/account-upgrades.md +218 -0
- package/docs/compatibility.md +39 -0
- package/docs/configuration.md +146 -0
- package/docs/dashboards.md +125 -0
- package/docs/execution-context.md +119 -0
- package/docs/http-api.md +47 -0
- package/docs/install.md +52 -0
- package/docs/operations.md +99 -0
- package/docs/upgrades.md +324 -0
- package/package.json +19 -5
- package/supervisor/supervise.mjs +226 -40
- package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
- package/tools/windows-legacy/README.md +251 -0
- package/tools/windows-legacy/build.ps1 +122 -0
- package/tools/windows-legacy/inventory.mjs +92 -0
- package/tools/windows-process-lifetime/README.md +119 -0
- package/tools/windows-process-lifetime/build.ps1 +122 -0
- package/tools/windows-process-lifetime/inventory.mjs +86 -0
- package/tools/windows-task-channel/README.md +329 -0
- package/tools/windows-task-channel/build.ps1 +107 -0
- package/tools/windows-task-channel/inventory.mjs +117 -0
- package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
- package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
- package/ui/dist/assets/index-PVcuDNrN.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/third-party-manifest.json +37 -25
- package/ui/dist/assets/index-CaLPg0c-.css +0 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Windows task channel guard
|
|
2
|
+
|
|
3
|
+
This is a separate, **query-only** boundary for one task-hosted original-account
|
|
4
|
+
worker. It does not stop processes, change tokens, impersonate, enable privileges,
|
|
5
|
+
register tasks, install accounts, elevate, repair ACLs, or enumerate processes.
|
|
6
|
+
The existing same-user legacy broker and modern lifetime helper are unchanged.
|
|
7
|
+
|
|
8
|
+
## Interface
|
|
9
|
+
|
|
10
|
+
`bin/windows-task-channel.mjs` verifies the packaged executable hash before **every
|
|
11
|
+
role**, then directly starts the helper on private inherited standard streams.
|
|
12
|
+
The helper derives and holds its actual parent; a request cannot select a PID.
|
|
13
|
+
It first emits a separate `started` event with its **own** PID/FILETIME for
|
|
14
|
+
exit tracking. That event is not readiness, caller authorization or worker proof.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const caller = await queryTaskChannelCaller();
|
|
18
|
+
const trust = await inspectTrustedCodeRoot(controllerPackageRoot);
|
|
19
|
+
const channel = await createTaskChannel({
|
|
20
|
+
operationId, targetSid, sessionId, manifestParent,
|
|
21
|
+
bootstrap: { nodePath, nodeSha256, scriptPath, scriptSha256 },
|
|
22
|
+
manifest: { /* public canonical operation binding and ephemeral public key */ },
|
|
23
|
+
});
|
|
24
|
+
const peer = await channel.awaitWorker();
|
|
25
|
+
const { peer: observed, frame } = await channel.receive();
|
|
26
|
+
await channel.send(response);
|
|
27
|
+
const authorization = await channel.authorize();
|
|
28
|
+
await channel.close();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`channel` exposes `manifestPath`, `endpoint`, `controllerIdentity`, `actorFacts`,
|
|
32
|
+
`guardIdentity`, `targetSession`, `workerSession`, `pid`, `awaitWorker`, `send`, `receive`, `authorize`, `closed`,
|
|
33
|
+
and `close`. Returned objects are detached copies, never native state.
|
|
34
|
+
`closed` resolves with native process exit code/signal/reason. Every pending
|
|
35
|
+
operation fails when the channel becomes invalid. `close` releases proof only:
|
|
36
|
+
it never signals either parent or worker. The caller retains responsibility
|
|
37
|
+
for journal-driven removal of its exact, newly-created operation directory.
|
|
38
|
+
Query failures include `guardIdentity`, `helperExit` when observed, and
|
|
39
|
+
`cleanupUnverified`. If exit cannot be verified, the error keeps that fact and
|
|
40
|
+
the close error; it is never silently replaced by a generic readiness timeout.
|
|
41
|
+
|
|
42
|
+
The helper exclusively creates `<manifestParent>/<operationId>/manifest.json`.
|
|
43
|
+
An existing operation directory is refused, never reused or repaired.
|
|
44
|
+
Default exact worker arguments, after the native Node executable, are:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
<absolute scriptPath> --manifest <absolute manifestPath> --operation <UUID> --controller-root <manifestParent>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Optional `bootstrap.argv` must equal that sequence. Native script/executable
|
|
51
|
+
read handles pin the verified bytes for the operation. Only local drive-absolute
|
|
52
|
+
paths are supported; relative, device, network and reparse paths are refused.
|
|
53
|
+
`expectedCreationTime`, when supplied, further restricts the observed peer birth;
|
|
54
|
+
it never selects a PID or permits reconnecting.
|
|
55
|
+
|
|
56
|
+
The manifest contains `protocol:1`, `operationId`, `endpoint`, `targetSid`,
|
|
57
|
+
`sessionId`, `targetSession`, `bootstrap`, `controllerIdentity`, `actorFacts`, and `document`
|
|
58
|
+
(the supplied public `manifest`). **No private key belongs in this document.**
|
|
59
|
+
|
|
60
|
+
The worker calls:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const { document, readerIdentity, protection } =
|
|
64
|
+
await readProtectedManifest(manifestPath, { operationId, manifestParent });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The reader validates the exact operation path, OS file owner, restrictive
|
|
68
|
+
directory/file security and no-redirect path chain, and its **own actual parent
|
|
69
|
+
Node** image, argv, SID, session and FILETIME. It also freshly verifies that the
|
|
70
|
+
bound session still belongs to that SID and the manifest's logon generation,
|
|
71
|
+
returning `targetSession` separately from the protected manifest snapshot.
|
|
72
|
+
It does not query the elevated
|
|
73
|
+
controller's token or threads: an ordinary other-user reader usually lacks
|
|
74
|
+
those rights. Its trust anchor is the controller-owned protected manifest and
|
|
75
|
+
bootstrap, not a self-reported admin field. `protection.actorOwnerSid` comes from
|
|
76
|
+
the OS descriptor. Own-user operation explicitly reports
|
|
77
|
+
`same-user-not-self-tamper-proof`; it does not protect against its own user.
|
|
78
|
+
|
|
79
|
+
## Identity and authorization
|
|
80
|
+
|
|
81
|
+
The helper owns exactly one local pipe with `FILE_FLAG_FIRST_PIPE_INSTANCE`,
|
|
82
|
+
`PIPE_REJECT_REMOTE_CLIENTS` and maximum one instance. A **direct Node worker**
|
|
83
|
+
connects: no proxy is treated as the worker. `GetNamedPipeClientProcessId` and
|
|
84
|
+
`GetNamedPipeClientSessionId` supply the peer, then query/synchronize process and
|
|
85
|
+
query-token handles remain open. The identity contains:
|
|
86
|
+
|
|
87
|
+
- `pid`, exact decimal FILETIME `creationTime`, `ownerSid`, `sessionId`
|
|
88
|
+
- `imagePath`, `imageSha256`, and `argvSha256`
|
|
89
|
+
|
|
90
|
+
Generation-bound WMI queries inspect only the guard's parent link or the
|
|
91
|
+
selected process's argv. Parent-thread IDs come from process-scoped PSS
|
|
92
|
+
snapshots, not WMI. There is no name/PID sweep or generic PID RPC.
|
|
93
|
+
Received JSON remains under `frame`; peer-supplied identity/type fields cannot
|
|
94
|
+
replace the separately generated `peer` envelope. Disconnection, peer exit,
|
|
95
|
+
parent exit, identity ambiguity or a deadline invalidates the operation.
|
|
96
|
+
No reconnect or replacement peer is accepted.
|
|
97
|
+
|
|
98
|
+
Controller facts come from the actual held parent's primary token: elevation,
|
|
99
|
+
enabled (not deny-only) Administrators SID, restrictions, AppContainer and
|
|
100
|
+
SID/session. Two process-scoped `PSS_CAPTURE_THREADS` (`0x80`, context flags zero)
|
|
101
|
+
captures retain a complete 1–128 unique-entry set, including PSS termination
|
|
102
|
+
metadata. Each thread is separately held with query/synchronize rights. Live
|
|
103
|
+
threads require matching owner PID, birth, liveness and token checks on both
|
|
104
|
+
observations. A PSS-terminated entry is accepted as already ended only when
|
|
105
|
+
supported flags and both snapshots agree with its held owner/birth/exit, its
|
|
106
|
+
exit strictly predates the first observation, and its held handle is signaled.
|
|
107
|
+
Ended handles remain held through the second observation. At least one live
|
|
108
|
+
thread is required. A live thread ending during observation still refuses.
|
|
109
|
+
The raw count/bounds include ended entries; no missing or partial set is accepted.
|
|
110
|
+
The ID sets, classifications and held
|
|
111
|
+
generations must remain unchanged. Missing/inaccessible primary or thread facts,
|
|
112
|
+
churn and observed thread impersonation refuse full caller/serve/authorize roles,
|
|
113
|
+
including same-user calls; they cannot become partial successful authorization.
|
|
114
|
+
`observed-none` is a **bounded observation, not an atomic
|
|
115
|
+
promise about future thread impersonation**. The guard's own thread fact is
|
|
116
|
+
separate. No controller thread ID is guessed from Node or Task Scheduler.
|
|
117
|
+
|
|
118
|
+
Only `cli-context`, `caller` and `serve` open their actual controller parent with
|
|
119
|
+
`PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE`
|
|
120
|
+
(`0x101400`), as required by the existing PSS capture. Authorization reuses that
|
|
121
|
+
held parent handle but repeats the complete observations, not cached facts.
|
|
122
|
+
Peer, manifest-reader, inspect-root and other process opens retain `0x101000`.
|
|
123
|
+
Denied query access refuses without fallback, privilege changes or permission
|
|
124
|
+
repair. The ordinary CLI and full-facts roles share ID capture, not eligibility
|
|
125
|
+
or administrator policy.
|
|
126
|
+
|
|
127
|
+
A different target SID requires actual elevated/enabled-admin, unrestricted,
|
|
128
|
+
non-AppContainer and nonimpersonating observations. Own-user query-only operation
|
|
129
|
+
requires no elevation. `authorize()` repeats actual parent/token/thread
|
|
130
|
+
observations; the task controller must call it and enforce its role immediately
|
|
131
|
+
before **each** permitted task mutation. It is available before the peer connects
|
|
132
|
+
for the preapproved bootstrap task operations, without opening a second helper.
|
|
133
|
+
Persisted facts and supplied booleans
|
|
134
|
+
never confer authority.
|
|
135
|
+
|
|
136
|
+
After the token/thread query, before acknowledging authorization, the guard
|
|
137
|
+
rechecks its held peer process/current SID/session, queries the **same pipe**
|
|
138
|
+
with `PeekNamedPipe`, and checks operation/framing/handshake deadlines. A peer
|
|
139
|
+
that exits or disconnects during that query cannot receive a stale successful
|
|
140
|
+
acknowledgement. This is still a point-in-time observation, not a promise that
|
|
141
|
+
the worker can never exit after the reply.
|
|
142
|
+
|
|
143
|
+
`inspectTrustedCodeRoot` checks root ancestors and all code-tree entries without
|
|
144
|
+
importing them. Allowed owners/writers are the actual actor, SYSTEM,
|
|
145
|
+
Administrators and the Windows TrustedInstaller service SID. Unknown write,
|
|
146
|
+
delete, ownership or permission authority is refused, even if a deny ACE might
|
|
147
|
+
make it harmless. Ancestor creation of an unrelated child is not deletion of an
|
|
148
|
+
existing protected child. Held no-delete path handles and native reparse checks
|
|
149
|
+
avoid following a replacement path. This is conservative admission, not a
|
|
150
|
+
general Windows effective-access evaluator or loaded-code attestation.
|
|
151
|
+
Only this read-only role omits the unused parent-thread observations and
|
|
152
|
+
reports `parentThreadImpersonation: "unverified"` with
|
|
153
|
+
`parentThreadQueryError: "NOT_OBSERVED_READ_ONLY_ROLE"`. It still queries the
|
|
154
|
+
held parent's primary token/SID and all root ACLs, with facts captured after
|
|
155
|
+
the completed walk. No caller flag selects this behavior. Caller/serve/authorize
|
|
156
|
+
roles retain their existing complete-observation requirement for admin authority.
|
|
157
|
+
|
|
158
|
+
## Application authentication stays outside this helper
|
|
159
|
+
|
|
160
|
+
The Node controller keeps its ephemeral private key **only in memory**. It signs
|
|
161
|
+
a domain-separated canonical operation digest, fresh worker challenge, this
|
|
162
|
+
guard's observed PID/birth/SID/session, and sequence. The worker verifies the
|
|
163
|
+
signature using the protected manifest key and compares its own native identity.
|
|
164
|
+
The same held pipe carries the exchange and later messages.
|
|
165
|
+
|
|
166
|
+
The guard does not implement these signatures or task verbs. The application
|
|
167
|
+
must not treat `awaitWorker()` alone as mutual authentication. Task `RunEx`
|
|
168
|
+
success/instance GUID/EnginePID, argv text, a nonce, or a copied JSON identity
|
|
169
|
+
is not loaded-code attestation. The elevated controller must remain safe even
|
|
170
|
+
against a lying authenticated worker: only preapproved task verbs and fixed
|
|
171
|
+
operation/sequence/state digests, with controller-chosen paths and definitions.
|
|
172
|
+
|
|
173
|
+
### Read state and cancellation
|
|
174
|
+
|
|
175
|
+
Peer reads decode UTF-8 and validate the outer JSON **object** in their own
|
|
176
|
+
bounded read task. The final authorization check observes any completed read
|
|
177
|
+
failure before emitting an acknowledgement. A valid object remains queued for
|
|
178
|
+
normal delivery exactly once; encoded/opaque values inside that object are not
|
|
179
|
+
interpreted as commands. Invalid UTF-8, invalid JSON and non-object frames cannot
|
|
180
|
+
be acknowledged merely because the peer process and pipe are still alive.
|
|
181
|
+
|
|
182
|
+
A separate continuous stdin reader keeps close/EOF/protocol failure observable
|
|
183
|
+
while a command is executing, including while authorization is blocked in token
|
|
184
|
+
or session queries. It queues at most 32 parsed commands and executes them in
|
|
185
|
+
order. Command IDs start at 1 and increase by one; duplicate/out-of-order IDs,
|
|
186
|
+
unknown commands and overflow refuse the channel. Close and EOF are latched
|
|
187
|
+
independently of queued authorization requests. The existing cancellation
|
|
188
|
+
watchdog/deadlines remain unchanged; there is no unbounded read queue or reconnect.
|
|
189
|
+
|
|
190
|
+
Owned tests compile a barrier into a private copy of `Authorize`, confirm the
|
|
191
|
+
peer read is faulted before release, then assert no authorization event. Separate
|
|
192
|
+
pre/post-peer close and EOF tests keep the parent alive and the watchdog enabled.
|
|
193
|
+
Queued-authorization cancellation, sequence/overflow refusal and valid opaque
|
|
194
|
+
FIFO delivery have their own controls. No barrier or bypass option is shipped.
|
|
195
|
+
|
|
196
|
+
## Bound interactive-session eligibility
|
|
197
|
+
|
|
198
|
+
Before creating bootstrap resources, before reporting a verified worker, and
|
|
199
|
+
on **every** authorization, the helper queries only the operation's fixed local
|
|
200
|
+
session. It uses `WTSQuerySessionInformationW` and `LookupAccountNameW`;
|
|
201
|
+
it does not enumerate sessions/users or call `WTSQueryUserToken`.
|
|
202
|
+
|
|
203
|
+
Full `WTSUserName`/`WTSDomainName` strings are resolved to a canonical **user SID**,
|
|
204
|
+
which must equal the selected SID. They are sampled twice, bracketed by
|
|
205
|
+
`WTSSessionInfoEx` snapshots with the same session ID, state and nonzero logon
|
|
206
|
+
FILETIME. The fixed-width extended-info name fields are consistency checks only;
|
|
207
|
+
a truncated 20-character name is **never** used for SID resolution. Permission
|
|
208
|
+
denial, missing/empty/unmapped identity, unsupported generation information,
|
|
209
|
+
changed samples, non-user SIDs and wrong ownership all cause refusal.
|
|
210
|
+
|
|
211
|
+
`active` and `disconnected` (still logged on without a connected client) are
|
|
212
|
+
eligible. Connected-but-not-logged-on, idle, listen, reset, down, initialization
|
|
213
|
+
and shadow states are not. The captured logon generation is fixed for this
|
|
214
|
+
operation; reauthorization cannot silently switch to a reused session.
|
|
215
|
+
|
|
216
|
+
The trusted `targetSession` shape is:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
{ sessionId, ownerSid, state, logonTime, observedAt,
|
|
220
|
+
source: "wts-account-sid-snapshot", atomicRunExBinding: false }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`ready.targetSession` and `document.targetSession` are initial observations.
|
|
224
|
+
After `awaitWorker()`, `channel.workerSession` is the separately trusted fresh
|
|
225
|
+
post-launch observation, not a peer frame field. `authorize().targetSession`
|
|
226
|
+
is fresh again. The reader also returns a fresh top-level `targetSession`.
|
|
227
|
+
The application must authenticate the handshake and freshly authorize before
|
|
228
|
+
importing target code or performing target work/stop operations.
|
|
229
|
+
|
|
230
|
+
Each session observation has a five-second bound. Other-session queries require
|
|
231
|
+
the existing WTS Query Information rights; refusal does not trigger permission
|
|
232
|
+
repair, logon, impersonation or privilege enabling.
|
|
233
|
+
|
|
234
|
+
**WTS snapshots cannot hold session ownership or bind it atomically to Task
|
|
235
|
+
Scheduler `RunEx`.** A logoff/reassignment can still happen after the last query
|
|
236
|
+
and before the task call. The application must report this snapshot/call limit,
|
|
237
|
+
recheck immediately before bootstrap, and require actual same-connection worker
|
|
238
|
+
SID/session/generation proof plus fresh session eligibility after launch and
|
|
239
|
+
before target work. No task result or stored session ID replaces these checks.
|
|
240
|
+
|
|
241
|
+
The runtime hash check catches packaged drift, not atomic replacement by a
|
|
242
|
+
same-user package writer. Updates must use immutable candidate directories;
|
|
243
|
+
there is no new trusted-launcher chain or on-the-fly compilation.
|
|
244
|
+
|
|
245
|
+
## Ordinary CLI effective-context observation
|
|
246
|
+
|
|
247
|
+
`queryCliCallerContext()` is a separate fixed role for ordinary **current-user**
|
|
248
|
+
dispatch. It does not authorize elevated/cross-user task work and does not
|
|
249
|
+
replace `queryTaskChannelCaller`, `serve` or `authorize`.
|
|
250
|
+
|
|
251
|
+
The native role derives the caller from its fresh private stdin pipe using
|
|
252
|
+
`GetNamedPipeServerProcessId`. It cross-checks the existing NT basic-process
|
|
253
|
+
parent-slot pattern, exact returned size/self PID, parent birth and liveness.
|
|
254
|
+
The JS export also requires the returned caller PID to equal `process.pid`.
|
|
255
|
+
The installed SDK calls that NT slot `Reserved3`: this is an explicit Windows
|
|
256
|
+
compatibility limit, not a portable documented PPID contract. Unsupported layout
|
|
257
|
+
or binding refuses the observation. The pipe query runs before its next blocking
|
|
258
|
+
read. No supplied PID or mode boolean provides authority.
|
|
259
|
+
|
|
260
|
+
Like the full caller/serve roles, this role opens its actual parent with
|
|
261
|
+
`PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE`
|
|
262
|
+
(`0x101400`). Two bounded, process-scoped `PSS_CAPTURE_THREADS` (`0x80`) captures
|
|
263
|
+
with context flags zero retain raw thread identity and termination metadata.
|
|
264
|
+
This uses the same complete held-generation observation as full facts, including
|
|
265
|
+
the strict already-ended classification described above. Every live thread must
|
|
266
|
+
positively return `ERROR_NO_TOKEN` from `OpenThreadToken(TOKEN_QUERY)` twice;
|
|
267
|
+
the complete ID set, held generations and classifications must stay unchanged.
|
|
268
|
+
Primary SID/session and token facts must remain stable. Primary elevation,
|
|
269
|
+
enabled Administrators and guard-thread impersonation must be false.
|
|
270
|
+
|
|
271
|
+
No VM reads, thread-context or handle capture, global enumeration,
|
|
272
|
+
impersonation, privilege enabling or rights-expansion fallback are used.
|
|
273
|
+
Denial, token presence, churn or unknown information means explicit
|
|
274
|
+
noneligibility/refusal, never a primary-token-only shortcut. The failed limited
|
|
275
|
+
mask and successful query-information probes establish behavior on the owned
|
|
276
|
+
host, not a documented universal minimum-rights claim.
|
|
277
|
+
|
|
278
|
+
The result contains `proofScope: "cli-effective-context-snapshot"`,
|
|
279
|
+
`ordinaryEligible`, `reason`, caller `identity`, `actorFacts`, `guardIdentity`,
|
|
280
|
+
`helperExit`, and:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
observation: {
|
|
284
|
+
method: "pss-threads-held-token-query", processAccess: "0x101400",
|
|
285
|
+
captureFlags: "0x80", threadContextFlags: 0, threadCount,
|
|
286
|
+
completeStableThreadSet, primaryStable,
|
|
287
|
+
atomicFutureProtection: false, observedAt
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Only a complete positive result with verified helper exit supports the
|
|
292
|
+
non-elevated current-user path. Selected-instance JSON cannot supply that
|
|
293
|
+
decision. Verified noneligibility keeps strict checks; thrown errors, especially
|
|
294
|
+
`cleanupUnverified`, must not become fallback success. This is a **moment-in-time
|
|
295
|
+
complete observation**, not immunity to future thread creation/impersonation.
|
|
296
|
+
Consume it before importing target code; do not cache it as lasting authority.
|
|
297
|
+
Native-probe and role-roundtrip timings are not end-to-end CLI measurements.
|
|
298
|
+
|
|
299
|
+
## Bounds and qualification
|
|
300
|
+
|
|
301
|
+
- Peer frames: 16 KiB UTF-8 including newline, JSON objects only; strict UTF-8,
|
|
302
|
+
no CR framing. A partial frame has five seconds after its first byte.
|
|
303
|
+
- Manifest/init/event bounds: 64 KiB; public document at most 48 KiB.
|
|
304
|
+
- Handshake: 2–30 seconds (default 30); total lifetime: 2–900 seconds
|
|
305
|
+
(default 900). Native startup/read-only queries have a 13-second bound, below
|
|
306
|
+
the unchanged 15-second JS readiness limit. A dedicated background thread
|
|
307
|
+
watches parent exit, stdin cancellation and deadlines even if WMI or file ACL
|
|
308
|
+
calls block the main thread. It exits **only this helper**. Root walks also
|
|
309
|
+
check cancellation/liveness between entries. A close/EOF gets a short 500 ms
|
|
310
|
+
grace for normal shutdown, not a longer operation deadline.
|
|
311
|
+
- One outstanding native peer read; JS queues at most 32 messages/requests.
|
|
312
|
+
Native pipe write deadline is one second.
|
|
313
|
+
- Thread set: 1–128, two bounded observations. Code-root scan: at most 2,048
|
|
314
|
+
directories/16,384 files. Bounds/inaccessible objects cause refusal.
|
|
315
|
+
|
|
316
|
+
Build with `tools/windows-task-channel/build.ps1`; `-Verify` checks recorded
|
|
317
|
+
compiler/reference/source inputs and two byte-identical clean compilations.
|
|
318
|
+
No downloads, installs, elevation or runtime compiler are used.
|
|
319
|
+
|
|
320
|
+
`test/windows-task-channel.test.mjs` uses only owned same-user processes/pipes,
|
|
321
|
+
45-second expiry and captured PID+FILETIME cleanup receipts. Pure reflection
|
|
322
|
+
policy tests are labeled models and never bypass shipped native authorization.
|
|
323
|
+
Actual elevated **two-user** pipe/security/task/session behavior is not qualified
|
|
324
|
+
by those tests. Run it only in a separately authorized existing two-user test
|
|
325
|
+
environment; this work creates no accounts, credentials, logons or tasks.
|
|
326
|
+
Blocked-query tests compile an isolated, explicitly marked test variant with
|
|
327
|
+
a 30-second stand-in for a blocked root read. One failure-injection variant
|
|
328
|
+
also disables its watchdog to verify the JS exit-unverified error path. Neither
|
|
329
|
+
behavior is exposed by a shipped argument, environment variable or auth bypass.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param([switch]$Verify, [string]$CompilerPath, [string]$ReferenceAssemblyPath)
|
|
3
|
+
Set-StrictMode -Version Latest
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
|
|
6
|
+
function Get-Digest([string]$Path, [switch]$Text) {
|
|
7
|
+
$bytes = [IO.File]::ReadAllBytes($Path)
|
|
8
|
+
if ($Text) { $bytes = [Text.Encoding]::UTF8.GetBytes([IO.File]::ReadAllText($Path).Replace("`r`n", "`n")) }
|
|
9
|
+
$hash = [Security.Cryptography.SHA256]::Create()
|
|
10
|
+
try { return [BitConverter]::ToString($hash.ComputeHash($bytes)).Replace('-', '').ToLowerInvariant() }
|
|
11
|
+
finally { $hash.Dispose() }
|
|
12
|
+
}
|
|
13
|
+
if ($env:OS -ne 'Windows_NT') { throw 'Use installed Windows Roslyn and .NET Framework 4.6.2 references.' }
|
|
14
|
+
$root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
|
|
15
|
+
$directory = Join-Path $root 'bin\windows-task-channel'
|
|
16
|
+
$binary = 'TaskChannelGuard.exe'
|
|
17
|
+
$metadataPath = Join-Path $directory 'TaskChannelGuard.build.json'
|
|
18
|
+
$sources = @('AssemblyInfo.cs', 'ChannelCliContext.cs', 'ChannelFiles.cs', 'ChannelNative.cs', 'ChannelSession.cs', 'TaskChannelGuard.cs')
|
|
19
|
+
$references = @('mscorlib.dll', 'System.dll', 'System.Core.dll', 'System.Management.dll', 'System.Web.Extensions.dll')
|
|
20
|
+
if (-not $CompilerPath) {
|
|
21
|
+
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
|
22
|
+
if (-not (Test-Path -LiteralPath $vswhere)) { throw 'Supply an installed -CompilerPath; no tools are installed.' }
|
|
23
|
+
$installation = & $vswhere -latest -products '*' -requires Microsoft.Component.MSBuild -property installationPath
|
|
24
|
+
if ($LASTEXITCODE -ne 0 -or
|
|
25
|
+
-not $installation) { throw 'Installed compiler missing.' }
|
|
26
|
+
$CompilerPath = Join-Path $installation 'MSBuild\Current\Bin\Roslyn\csc.exe'
|
|
27
|
+
}
|
|
28
|
+
if (-not $ReferenceAssemblyPath) {
|
|
29
|
+
$ReferenceAssemblyPath = Join-Path ${env:ProgramFiles(x86)} 'Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2'
|
|
30
|
+
}
|
|
31
|
+
$CompilerPath = (Resolve-Path -LiteralPath $CompilerPath).Path
|
|
32
|
+
$ReferenceAssemblyPath = (Resolve-Path -LiteralPath $ReferenceAssemblyPath).Path
|
|
33
|
+
$framework = [xml][IO.File]::ReadAllText((Join-Path $ReferenceAssemblyPath 'RedistList\FrameworkList.xml'))
|
|
34
|
+
if ($framework.FileList.Name -cne '.NET Framework 4.6.2') { throw 'Wrong targeting pack.' }
|
|
35
|
+
$version = (& $CompilerPath /version | Out-String).Trim()
|
|
36
|
+
if ($LASTEXITCODE -ne 0) { throw 'Compiler version query failed.' }
|
|
37
|
+
$options = @('/nologo', '/noconfig', '/nostdlib+', '/target:exe', '/platform:anycpu',
|
|
38
|
+
'/langversion:7.3', '/optimize+', '/debug-', '/deterministic+', '/checked-',
|
|
39
|
+
'/unsafe-', '/warn:4', '/warnaserror+', '/utf8output')
|
|
40
|
+
$sourceHashes = [ordered]@{}
|
|
41
|
+
foreach ($name in $sources) { $sourceHashes[$name] = Get-Digest (Join-Path $directory "src\$name") -Text }
|
|
42
|
+
$referenceHashes = [ordered]@{}
|
|
43
|
+
foreach ($name in $references) { $referenceHashes[$name] = Get-Digest (Join-Path $ReferenceAssemblyPath $name) }
|
|
44
|
+
$inputs = [ordered]@{
|
|
45
|
+
targetFramework = '.NETFramework,Version=v4.6.2'
|
|
46
|
+
platform = 'AnyCPU'
|
|
47
|
+
compilerVersion = $version
|
|
48
|
+
compilerSha256 = Get-Digest $CompilerPath
|
|
49
|
+
compilerArguments = $options
|
|
50
|
+
pathMap = '/_/mcp-pacemaker/windows-task-channel'
|
|
51
|
+
sourceEncoding = 'UTF-8 without BOM, LF line endings'
|
|
52
|
+
sourceSha256 = $sourceHashes
|
|
53
|
+
buildScriptSha256 = Get-Digest $PSCommandPath -Text
|
|
54
|
+
referenceSha256 = $referenceHashes
|
|
55
|
+
}
|
|
56
|
+
$expected = $null
|
|
57
|
+
if ($Verify) {
|
|
58
|
+
$expected = [IO.File]::ReadAllText($metadataPath) | ConvertFrom-Json
|
|
59
|
+
if ($expected.schemaVersion -ne 1 -or
|
|
60
|
+
$expected.binary -cne $binary -or
|
|
61
|
+
(ConvertTo-Json $inputs -Depth 6 -Compress) -cne (ConvertTo-Json $expected.inputs -Depth 6 -Compress) -or
|
|
62
|
+
(Get-Digest (Join-Path $directory $binary)) -cne $expected.binarySha256) {
|
|
63
|
+
throw 'Task guard inputs/toolchain/bytes differ; Verify changes nothing.'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
$temporary = Join-Path ([IO.Path]::GetTempPath()) ('mcp-task-channel-build-' + [Guid]::NewGuid().ToString('N'))
|
|
67
|
+
$files = [Collections.Generic.List[string]]::new()
|
|
68
|
+
$directories = [Collections.Generic.List[string]]::new()
|
|
69
|
+
$outputs = [Collections.Generic.List[string]]::new()
|
|
70
|
+
try {
|
|
71
|
+
$null = [IO.Directory]::CreateDirectory($temporary)
|
|
72
|
+
$directories.Add($temporary)
|
|
73
|
+
foreach ($pass in @('first', 'second')) {
|
|
74
|
+
$scratch = Join-Path $temporary $pass
|
|
75
|
+
$null = [IO.Directory]::CreateDirectory($scratch)
|
|
76
|
+
$directories.Add($scratch)
|
|
77
|
+
$paths = foreach ($name in $sources) {
|
|
78
|
+
$path = Join-Path $scratch $name
|
|
79
|
+
$files.Add($path)
|
|
80
|
+
$text = [IO.File]::ReadAllText((Join-Path $directory "src\$name")).Replace("`r`n", "`n")
|
|
81
|
+
[IO.File]::WriteAllText($path, $text, [Text.UTF8Encoding]::new($false))
|
|
82
|
+
$path
|
|
83
|
+
}
|
|
84
|
+
$output = Join-Path $scratch $binary
|
|
85
|
+
$files.Add($output)
|
|
86
|
+
$arguments = $options + @("/out:$output", "/pathmap:$scratch=$($inputs.pathMap)")
|
|
87
|
+
foreach ($name in $references) { $arguments += '/reference:' + (Join-Path $ReferenceAssemblyPath $name) }
|
|
88
|
+
& $CompilerPath @arguments @paths
|
|
89
|
+
if ($LASTEXITCODE -ne 0) { throw "Task guard compile failed: $pass" }
|
|
90
|
+
$outputs.Add($output)
|
|
91
|
+
}
|
|
92
|
+
$digest = Get-Digest $outputs[0]
|
|
93
|
+
if ($digest -cne (Get-Digest $outputs[1])) { throw 'Deterministic builds differ; no assets changed.' }
|
|
94
|
+
if ($Verify) {
|
|
95
|
+
if ($digest -cne $expected.binarySha256) { throw 'Rebuilt bytes differ; no assets changed.' }
|
|
96
|
+
Write-Output "Verified task guard source/toolchain/bytes: SHA256 $digest"
|
|
97
|
+
} else {
|
|
98
|
+
$metadata = [ordered]@{ schemaVersion = 1; binary = $binary; binarySha256 = $digest; inputs = $inputs }
|
|
99
|
+
[IO.File]::Copy($outputs[0], (Join-Path $directory $binary), $true)
|
|
100
|
+
[IO.File]::WriteAllText($metadataPath, (ConvertTo-Json $metadata -Depth 8).Replace("`r`n", "`n") + "`n",
|
|
101
|
+
[Text.UTF8Encoding]::new($false))
|
|
102
|
+
Write-Output "Built two identical task guards: SHA256 $digest"
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
foreach ($path in $files) { Remove-Item -LiteralPath $path -ErrorAction SilentlyContinue }
|
|
106
|
+
for ($index = $directories.Count - 1; $index -ge 0; $index--) { [IO.Directory]::Delete($directories[$index]) }
|
|
107
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const TASK_CHANNEL_FILES = Object.freeze([
|
|
7
|
+
'bin/windows-task-channel.mjs',
|
|
8
|
+
'bin/windows-task-channel/TaskChannelGuard.exe',
|
|
9
|
+
'bin/windows-task-channel/TaskChannelGuard.build.json',
|
|
10
|
+
'bin/windows-task-channel/src/AssemblyInfo.cs',
|
|
11
|
+
'bin/windows-task-channel/src/ChannelCliContext.cs',
|
|
12
|
+
'bin/windows-task-channel/src/ChannelFiles.cs',
|
|
13
|
+
'bin/windows-task-channel/src/ChannelNative.cs',
|
|
14
|
+
'bin/windows-task-channel/src/ChannelSession.cs',
|
|
15
|
+
'bin/windows-task-channel/src/TaskChannelGuard.cs',
|
|
16
|
+
'tools/windows-task-channel/build.ps1',
|
|
17
|
+
'tools/windows-task-channel/inventory.mjs',
|
|
18
|
+
'tools/windows-task-channel/README.md',
|
|
19
|
+
]);
|
|
20
|
+
export const TASK_CHANNEL_TEST = 'test/windows-task-channel.test.mjs';
|
|
21
|
+
export const TASK_CHANNEL_REQUIRED_TESTS = Object.freeze([
|
|
22
|
+
...['invalid UTF8', 'invalid JSON', 'nonobject'].map(name =>
|
|
23
|
+
`task channel rejects completed ${name} before authorization acknowledgement`),
|
|
24
|
+
...['prepeer', 'postpeer'].flatMap(phase => ['close', 'EOF'].map(action =>
|
|
25
|
+
`task channel ${phase} ${action} cancels blocked authorization`)),
|
|
26
|
+
'task channel preserves valid opaque frames and FIFO commands across authorization',
|
|
27
|
+
...['close', 'EOF'].map(action => `task channel ${action} stays observable behind a queued authorization`),
|
|
28
|
+
...['duplicate sequence', 'queue overflow'].map(fault =>
|
|
29
|
+
`task channel refuses ${fault} while authorization is blocked`),
|
|
30
|
+
'task channel observes its actual caller token and held parent identity',
|
|
31
|
+
'task channel binds direct Node peer and preserves identity across bidirectional frames',
|
|
32
|
+
'task channel refuses another SID without actual elevated token despite supplied admin flags',
|
|
33
|
+
'task channel refuses wrong peer generation without stopping the worker',
|
|
34
|
+
'task channel refuses an unavailable bound session before creating bootstrap resources',
|
|
35
|
+
'task channel refuses extra worker argv without stopping the worker',
|
|
36
|
+
'task channel rejects changed bootstrap bytes before creating operation resources',
|
|
37
|
+
'task channel accepts only its first connection and cannot reconnect after close',
|
|
38
|
+
...['disconnect', 'exit'].map(action => `task channel invalidates held connection on worker ${action}`),
|
|
39
|
+
...['exit', 'disconnect'].map(action =>
|
|
40
|
+
`task channel refuses authorization when peer ${action} occurs during token observation`),
|
|
41
|
+
...['nonobject', 'invalid json', 'invalid UTF8', 'oversize', 'CR framing', 'incomplete frame timeout']
|
|
42
|
+
.map(name => `task channel refuses ${name} without terminating its peer`),
|
|
43
|
+
'task channel processes fragmented UTF8 and multiple complete frames on the same peer',
|
|
44
|
+
'task channel handshake deadline releases its resources without adopting a peer',
|
|
45
|
+
'task channel rejects unsupported role and never uses claimed parent or admin identity',
|
|
46
|
+
'task channel admin policy requires all actual token and parent-thread facts',
|
|
47
|
+
'task channel session policy refuses unavailable owner state and logon-generation ambiguity',
|
|
48
|
+
'task channel trusted-root inspection refuses a reparse path and does not import files',
|
|
49
|
+
'task channel parent exit releases native proof without stopping the direct worker',
|
|
50
|
+
'task channel manifest reader rejects wrong operation and a caller that is not its bootstrap',
|
|
51
|
+
'task channel refuses a newly created untrusted-writer code file without repairing its ACL',
|
|
52
|
+
'task channel inventories exact packaged native bytes and rejects source metadata or binary drift',
|
|
53
|
+
'task channel checks helper hashes before every native role and refuses missing helpers',
|
|
54
|
+
'task channel stdin cancellation ends a blocked query with its early owned identity',
|
|
55
|
+
'task channel parent loss ends a blocked query without waiting for its root walk',
|
|
56
|
+
'task channel blocked query deadline reports verified native exit without a surviving helper',
|
|
57
|
+
'task channel preserves exit-unverified evidence when a test-only native watchdog is blocked',
|
|
58
|
+
'task channel ordinary context proves the actual owned caller through complete held PSS threads',
|
|
59
|
+
'task channel ordinary eligibility refuses incomplete ambiguous or privileged policy facts',
|
|
60
|
+
...['denied', 'token', 'churn', 'layout', 'binding'].map(variant =>
|
|
61
|
+
`task channel ordinary context refuses owned ${variant} fault without relaxing authority`),
|
|
62
|
+
...['query-denied', 'denied', 'token', 'churn', 'reuse', 'layout', 'no-info', 'walk',
|
|
63
|
+
'marker-free', 'snapshot-free', 'duplicate', 'empty', 'count',
|
|
64
|
+
'owner', 'birth', 'exit-zero', 'exit-future', 'missing-flag', 'unknown-flags',
|
|
65
|
+
'changed-exit', 'classification', 'thread-denied', 'no-live'].map(variant =>
|
|
66
|
+
`task channel full facts refuses owned ${variant} fault without fallback`),
|
|
67
|
+
'task channel full facts repeats PSS per authorization and keeps peer and reader limited',
|
|
68
|
+
...['caller', 'cli-context'].flatMap(role => [
|
|
69
|
+
`task channel ${role} accepts proven already-ended held thread`,
|
|
70
|
+
`task channel ${role} refuses live thread ending after first snapshot`,
|
|
71
|
+
]),
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
export function verifyTaskChannelAssets(root) {
|
|
75
|
+
for (const path of ['bin', 'bin/windows-task-channel', 'bin/windows-task-channel/src',
|
|
76
|
+
'tools', 'tools/windows-task-channel']) {
|
|
77
|
+
const stat = lstatSync(join(root, path));
|
|
78
|
+
assert.ok(stat.isDirectory() &&
|
|
79
|
+
!stat.isSymbolicLink(), `Invalid task channel directory: ${path}`);
|
|
80
|
+
}
|
|
81
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
82
|
+
const files = TASK_CHANNEL_FILES.map(path => {
|
|
83
|
+
const full = join(root, path);
|
|
84
|
+
const stat = lstatSync(full);
|
|
85
|
+
assert.ok(stat.isFile() &&
|
|
86
|
+
!stat.isSymbolicLink() &&
|
|
87
|
+
stat.size > 0, `Invalid task channel asset: ${path}`);
|
|
88
|
+
return { path, sha256: hash(readFileSync(full)) };
|
|
89
|
+
});
|
|
90
|
+
assert.deepEqual(readdirSync(join(root, 'bin/windows-task-channel')).sort(),
|
|
91
|
+
['TaskChannelGuard.build.json', 'TaskChannelGuard.exe', 'src']);
|
|
92
|
+
const sources = ['AssemblyInfo.cs', 'ChannelCliContext.cs', 'ChannelFiles.cs', 'ChannelNative.cs', 'ChannelSession.cs', 'TaskChannelGuard.cs'];
|
|
93
|
+
assert.deepEqual(readdirSync(join(root, 'bin/windows-task-channel/src')).sort(), sources);
|
|
94
|
+
const metadata = JSON.parse(readFileSync(join(root, 'bin/windows-task-channel/TaskChannelGuard.build.json')));
|
|
95
|
+
assert.equal(metadata.schemaVersion, 1);
|
|
96
|
+
assert.equal(metadata.binary, 'TaskChannelGuard.exe');
|
|
97
|
+
assert.equal(metadata.inputs.targetFramework, '.NETFramework,Version=v4.6.2');
|
|
98
|
+
assert.equal(metadata.inputs.platform, 'AnyCPU');
|
|
99
|
+
assert.equal(metadata.inputs.pathMap, '/_/mcp-pacemaker/windows-task-channel');
|
|
100
|
+
assert.deepEqual(Object.keys(metadata.inputs.sourceSha256).sort(), sources);
|
|
101
|
+
assert.deepEqual(Object.keys(metadata.inputs.referenceSha256).sort(),
|
|
102
|
+
['System.Core.dll', 'System.Management.dll', 'System.Web.Extensions.dll', 'System.dll', 'mscorlib.dll']);
|
|
103
|
+
assert.match(metadata.inputs.compilerSha256, /^[a-f0-9]{64}$/);
|
|
104
|
+
assert.deepEqual(metadata.inputs.compilerArguments, [
|
|
105
|
+
'/nologo', '/noconfig', '/nostdlib+', '/target:exe', '/platform:anycpu', '/langversion:7.3',
|
|
106
|
+
'/optimize+', '/debug-', '/deterministic+', '/checked-', '/unsafe-', '/warn:4', '/warnaserror+', '/utf8output',
|
|
107
|
+
]);
|
|
108
|
+
assert.equal(files.find(file => file.path.endsWith('.exe')).sha256, metadata.binarySha256);
|
|
109
|
+
const inputs = sources.map(name => [`bin/windows-task-channel/src/${name}`, metadata.inputs.sourceSha256[name]]);
|
|
110
|
+
inputs.push(['tools/windows-task-channel/build.ps1', metadata.inputs.buildScriptSha256]);
|
|
111
|
+
for (const [path, expected] of inputs) {
|
|
112
|
+
assert.equal(hash(readFileSync(join(root, path), 'utf8').replaceAll('\r\n', '\n')), expected,
|
|
113
|
+
`Changed task channel build input: ${path}`);
|
|
114
|
+
}
|
|
115
|
+
return { status: 'source-and-binary-hashes-verified', files, binarySha256: metadata.binarySha256,
|
|
116
|
+
compilerSha256: metadata.inputs.compilerSha256, reproducibilityBuild: 'requires-recorded-toolchain-verify' };
|
|
117
|
+
}
|
|
@@ -192,7 +192,7 @@ SOFTWARE.
|
|
|
192
192
|
*/
|
|
193
193
|
|
|
194
194
|
==============================================================================
|
|
195
|
-
tailwindcss@3.
|
|
195
|
+
tailwindcss@4.3.3
|
|
196
196
|
Declared license: MIT
|
|
197
197
|
|
|
198
198
|
--- LICENSE ---
|
|
@@ -218,33 +218,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
218
218
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
219
219
|
SOFTWARE.
|
|
220
220
|
|
|
221
|
-
--- lib/css/LICENSE ---
|
|
222
|
-
MIT License
|
|
223
|
-
|
|
224
|
-
Copyright (c) Nicolas Gallagher
|
|
225
|
-
Copyright (c) Jonathan Neal
|
|
226
|
-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
|
227
|
-
Copyright (c) Adam Wathan
|
|
228
|
-
Copyright (c) Jonathan Reinink
|
|
229
|
-
|
|
230
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
231
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
232
|
-
in the Software without restriction, including without limitation the rights
|
|
233
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
234
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
235
|
-
furnished to do so, subject to the following conditions:
|
|
236
|
-
|
|
237
|
-
The above copyright notice and this permission notice shall be included in all
|
|
238
|
-
copies or substantial portions of the Software.
|
|
239
|
-
|
|
240
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
241
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
242
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
243
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
244
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
245
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
246
|
-
SOFTWARE.
|
|
247
|
-
|
|
248
221
|
==============================================================================
|
|
249
222
|
vite@7.3.6
|
|
250
223
|
Declared license: MIT
|