livedesk 0.1.593 → 0.1.595

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 (31) hide show
  1. package/bin/livedesk.js +8 -8
  2. package/bootstrap/role-bootstrap-server.js +1 -1
  3. package/client/bin/livedesk-client-fast.js +1 -1
  4. package/client/bin/livedesk-client-node.js +1 -1
  5. package/client/bin/livedesk-client.js +30 -30
  6. package/client/package.json +6 -6
  7. package/client/src/runtime/client-runtime-server.js +4 -4
  8. package/diagnostics/livedesk-mac-physical-isolation.mjs +354 -354
  9. package/diagnostics/livedesk-transport-physical-core.mjs +702 -702
  10. package/electron/auth-config.mjs +10 -10
  11. package/electron/bounded-desktop-log.mjs +609 -609
  12. package/electron/electron-builder.win-dev.yml +1 -1
  13. package/electron/icon.svg +11 -11
  14. package/electron/main.mjs +5 -5
  15. package/electron/runtime-process-owner.mjs +287 -287
  16. package/hub/package.json +2 -2
  17. package/hub/src/server.js +2 -2
  18. package/package.json +6 -6
  19. package/runtime-core/package.json +1 -1
  20. package/runtime-core/src/auth-config.js +1 -1
  21. package/web/dist/app.webmanifest +1 -1
  22. package/web/dist/assets/LiveDeskApp-CvQUgQ08.js +178 -0
  23. package/web/dist/assets/{icons-DwJmccVC.js → icons-tFaWqIIe.js} +1 -1
  24. package/web/dist/assets/{index-DjIbOQxl.js → index-BDVKTkV3.js} +3 -3
  25. package/web/dist/assets/{index-DRI7OJI6.css → index-CpHNEjHy.css} +1 -1
  26. package/web/dist/assets/{react-DOZUPHF8.js → react-BQos8uGS.js} +1 -1
  27. package/web/dist/favicon.svg +11 -11
  28. package/web/dist/index.html +6 -6
  29. package/web/dist/livedesk-build-evidence.json +25 -25
  30. package/web/dist/sw.js +1 -1
  31. package/web/dist/assets/LiveDeskApp-Djx1CsIp.js +0 -178
@@ -1,702 +1,702 @@
1
- import { createHash } from 'node:crypto';
2
- import { isIP } from 'node:net';
3
-
4
- const REQUIRED_PHASES = Object.freeze([
5
- 'direct',
6
- 'p2p',
7
- 'relay',
8
- 'recover-direct'
9
- ]);
10
-
11
- const MAX_PHASE_LATENCY_MS = 15_000;
12
- const MAX_AGENT_QUEUE_DEPTH = 8;
13
- const MAX_AGENT_QUEUE_DROPPED_DELTA = 8;
14
- const MAX_HUB_INGRESS_DROPPED_DELTA = 8;
15
- const MAX_UDP_INCOMPLETE_DELTA = 2;
16
- const MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS = 60_000;
17
-
18
- const SECRET_KEYS = new Set([
19
- 'authorization',
20
- 'bearer',
21
- 'token',
22
- 'secret',
23
- 'password',
24
- 'apikey',
25
- 'pairtoken'
26
- ]);
27
- const SAFE_NETWORK_KEYS = new Set([
28
- 'clientaddresshash',
29
- 'hubaddresshash',
30
- 'clientaddresspublic',
31
- 'hubaddresspublic',
32
- 'hubaddressobservedat',
33
- 'endpointauthenticated',
34
- 'differentpublicegress',
35
- 'evidence',
36
- 'observedat',
37
- 'rendezvousport',
38
- 'udpready'
39
- ]);
40
- const IDENTIFIER_KEYS = new Set([
41
- 'leaseid',
42
- 'deviceid',
43
- 'selecteddeviceid',
44
- 'streamid',
45
- 'streamcommandid',
46
- 'commandid',
47
- 'sessionid',
48
- 'requestid',
49
- 'connectionid',
50
- 'hubconnectionid',
51
- 'inputeventid',
52
- 'clientid',
53
- 'ownerid'
54
- ]);
55
- const FILESYSTEM_KEYS = new Set([
56
- 'cwd',
57
- 'directory',
58
- 'dirname',
59
- 'executablepath',
60
- 'filepath',
61
- 'filename',
62
- 'homedir',
63
- 'packagepath',
64
- 'path',
65
- 'reportpath'
66
- ]);
67
- const SECRET_TEXT = /\b(?:authorization|bearer|token|secret|password|api[-_ ]?key|pair[-_ ]?token)(?:\s+|\s*[:=]\s*)\S+/i;
68
- const FILESYSTEM_TEXT = /(?:^|\s)(?:[a-z]:[\\/]|\\\\[^\\\s]+\\|\/(?:applications|etc|home|opt|private|run|srv|tmp|users|usr|var|volumes)\/)\S*/i;
69
- const IPV4_TEXT = /(^|[^\d])(?:\d{1,3}\.){3}\d{1,3}(?::\d{1,5})?($|[^\d])/;
70
- const IPV6_CANDIDATE_TEXT = /\[[0-9a-f:.%]+\](?::\d{1,5})?|[0-9a-f:%]*:[0-9a-f:.%:]*/gi;
71
- const URL_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/[^\s]+/i;
72
- const HOST_TEXT = /\b(?:localhost|(?:[a-z0-9-]+\.)+[a-z]{2,63})(?::\d{1,5})?\b/i;
73
- const UUID_TEXT = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
74
- const SAFE_PLATFORMS = new Set([
75
- 'aix',
76
- 'android',
77
- 'darwin',
78
- 'freebsd',
79
- 'linux',
80
- 'macos',
81
- 'openbsd',
82
- 'sunos',
83
- 'win32',
84
- 'windows'
85
- ]);
86
- const SAFE_ARCHITECTURES = new Set([
87
- 'arm',
88
- 'arm64',
89
- 'ia32',
90
- 'loong64',
91
- 'mips',
92
- 'mipsel',
93
- 'ppc',
94
- 'ppc64',
95
- 'riscv64',
96
- 's390',
97
- 's390x',
98
- 'x64'
99
- ]);
100
- const SAFE_RUNTIME_STATES = new Set([
101
- 'booting',
102
- 'connecting',
103
- 'error',
104
- 'resolving-role',
105
- 'running',
106
- 'starting',
107
- 'stopping',
108
- 'switching',
109
- 'updating',
110
- 'waiting-auth'
111
- ]);
112
- const SAFE_RUNTIME_ROLES = new Set(['client', 'hub']);
113
- const SAFE_VERSION = /^(?:v)?\d+(?:\.\d+){1,3}(?:[-+][0-9a-z][0-9a-z.-]{0,47})?$/i;
114
-
115
- function normalizedKey(key) {
116
- return String(key || '').replace(/[-_]/g, '').toLowerCase();
117
- }
118
-
119
- function isIdentifierKey(key) {
120
- return IDENTIFIER_KEYS.has(normalizedKey(key));
121
- }
122
-
123
- function isSecretKey(key) {
124
- const normalized = normalizedKey(key);
125
- return SECRET_KEYS.has(normalized)
126
- || normalized.endsWith('token')
127
- || normalized.endsWith('secret')
128
- || normalized.endsWith('password')
129
- || normalized.endsWith('apikey');
130
- }
131
-
132
- function isRawFilesystemKey(key) {
133
- return FILESYSTEM_KEYS.has(normalizedKey(key));
134
- }
135
-
136
- function isRawNetworkKey(key) {
137
- const rawKey = String(key || '');
138
- const normalized = normalizedKey(key);
139
- if (SAFE_NETWORK_KEYS.has(normalized) || normalized.endsWith('hash')) return false;
140
- return normalized === 'address'
141
- || normalized.endsWith('address')
142
- || normalized === 'endpoint'
143
- || normalized.endsWith('endpoint')
144
- || normalized === 'host'
145
- || normalized.endsWith('host')
146
- || normalized === 'port'
147
- || /(?:^|[-_])port$/i.test(rawKey)
148
- || /[A-Z]Port$/.test(rawKey)
149
- || normalized === 'url'
150
- || normalized.endsWith('url')
151
- || normalized === 'uri'
152
- || normalized.endsWith('uri')
153
- || normalized === 'origin'
154
- || normalized.endsWith('origin');
155
- }
156
-
157
- function containsIpv6Text(value) {
158
- const candidates = String(value || '').match(IPV6_CANDIDATE_TEXT) || [];
159
- return candidates.some(candidate => {
160
- const bracketed = candidate.match(/^\[([^\]]+)\](?::\d{1,5})?$/);
161
- const address = String(bracketed?.[1] || candidate)
162
- .replace(/%[a-z0-9_.-]+$/i, '')
163
- .replace(/[.,;]+$/g, '');
164
- return isIP(address) === 6;
165
- });
166
- }
167
-
168
- function containsRawNetworkText(value) {
169
- const text = String(value || '');
170
- return IPV4_TEXT.test(text)
171
- || containsIpv6Text(text)
172
- || URL_TEXT.test(text)
173
- || HOST_TEXT.test(text);
174
- }
175
-
176
- function identifierMarker(value) {
177
- if (value === null || value === undefined || String(value) === '') return '';
178
- return '[redacted-id]';
179
- }
180
-
181
- function normalizedEnum(value, allowed) {
182
- const normalized = String(value || '').trim().toLowerCase();
183
- return allowed.has(normalized) ? normalized : 'unknown';
184
- }
185
-
186
- function normalizedVersion(value) {
187
- const normalized = String(value || '').trim();
188
- if (normalized === 'dev') return 'dev';
189
- return SAFE_VERSION.test(normalized) && isIP(normalized.replace(/^v/i, '')) === 0
190
- ? normalized
191
- : 'unknown';
192
- }
193
-
194
- function normalizedPackageName(value, fallback = '') {
195
- const normalized = String(value || '').trim().toLowerCase();
196
- if (normalized === 'livedesk' || /^@livedesk\/[a-z0-9-]{1,48}$/.test(normalized)) {
197
- return normalized;
198
- }
199
- return fallback;
200
- }
201
-
202
- function normalizedBoolean(value) {
203
- return typeof value === 'boolean' ? value : null;
204
- }
205
-
206
- function isAllowedStructuredScalar(key, value) {
207
- const normalized = normalizedKey(key);
208
- const text = String(value || '');
209
- if (normalized === 'schema') {
210
- return /^livedesk\.transport-physical\.v\d+$/.test(text);
211
- }
212
- if (normalized.endsWith('hash')) {
213
- return /^sha256:[0-9a-f]{20,64}$/.test(text);
214
- }
215
- if ([
216
- 'agentversion',
217
- 'appversion',
218
- 'managerversion',
219
- 'nodeversion',
220
- 'packageversion',
221
- 'productversion'
222
- ].includes(normalized)) {
223
- return text === 'unknown'
224
- || text === 'dev'
225
- || normalizedVersion(text) === text;
226
- }
227
- return false;
228
- }
229
-
230
- function stableFingerprint(value) {
231
- const normalized = String(value || '').trim();
232
- if (!normalized) return '';
233
- return `sha256:${createHash('sha256').update(normalized).digest('hex').slice(0, 20)}`;
234
- }
235
-
236
- function runtimeFingerprint(value = {}) {
237
- const explicit = String(value.runtimeId || value.sessionId || '').trim();
238
- if (explicit) return stableFingerprint(explicit);
239
- const pid = Number(value.runtimePid ?? value.pid);
240
- const startedAt = String(value.startedAt || value.connectedAt || '').trim();
241
- if (!Number.isSafeInteger(pid) || pid <= 0 || !startedAt) return '';
242
- return stableFingerprint(`${pid}:${startedAt}`);
243
- }
244
-
245
- function normalizedRendezvousEndpoint(value = {}) {
246
- let rawHost = String(value.host || value.rendezvousHost || '').trim();
247
- let rawPort = Number(value.port ?? value.rendezvousPort);
248
- if (rawHost) {
249
- try {
250
- const parsed = new URL(rawHost.includes('://') ? rawHost : `udp://${rawHost}`);
251
- rawHost = parsed.hostname.replace(/^\[|\]$/g, '').toLowerCase().replace(/\.$/, '');
252
- if ((!Number.isSafeInteger(rawPort) || rawPort < 1 || rawPort > 65_535)
253
- && parsed.port) {
254
- rawPort = Number(parsed.port);
255
- }
256
- } catch {
257
- rawHost = '';
258
- }
259
- }
260
- const rendezvousPort = Number.isSafeInteger(rawPort) && rawPort >= 1 && rawPort <= 65_535
261
- ? rawPort
262
- : null;
263
- return {
264
- endpointKind: 'udp-rendezvous',
265
- configured: !!rawHost && rendezvousPort !== null,
266
- hostHash: stableFingerprint(rawHost),
267
- rendezvousPort
268
- };
269
- }
270
-
271
- export function buildTransportPhysicalProvenance(input = {}) {
272
- const liveDeskPackage = input.liveDeskPackage || {};
273
- const runner = input.runner || {};
274
- const hubRuntime = input.hubRuntime || {};
275
- const hubRemote = input.hubRemote || {};
276
- const client = input.client || {};
277
- const udp = input.rendezvous || hubRemote.udp || {};
278
- const hubVersion = normalizedVersion(hubRuntime.appVersion);
279
- const managerVersion = normalizedVersion(hubRemote.managerVersion);
280
- return {
281
- livedesk: {
282
- packageName: 'livedesk',
283
- packageVersion: normalizedVersion(liveDeskPackage.version)
284
- },
285
- runner: {
286
- nodeVersion: normalizedVersion(runner.nodeVersion),
287
- platform: normalizedEnum(runner.platform, SAFE_PLATFORMS),
288
- arch: normalizedEnum(runner.arch, SAFE_ARCHITECTURES)
289
- },
290
- hub: {
291
- packageName: normalizedPackageName(hubRemote.managerPackage, '@livedesk/hub'),
292
- appVersion: hubVersion !== 'unknown' ? hubVersion : managerVersion,
293
- managerVersion,
294
- role: normalizedEnum(hubRuntime.role || hubRemote.runtimeRole, SAFE_RUNTIME_ROLES),
295
- state: normalizedEnum(hubRuntime.state, SAFE_RUNTIME_STATES),
296
- runtimeStarted: normalizedBoolean(hubRuntime.runtimeStarted),
297
- runtimeIdentityHash: runtimeFingerprint(hubRuntime)
298
- },
299
- client: {
300
- productVersion: normalizedVersion(client.productVersion),
301
- agentVersion: normalizedVersion(client.agentVersion),
302
- platform: normalizedEnum(client.platform, SAFE_PLATFORMS),
303
- arch: normalizedEnum(client.arch, SAFE_ARCHITECTURES),
304
- runtimeIdentityHash: runtimeFingerprint(client)
305
- },
306
- rendezvous: {
307
- ...normalizedRendezvousEndpoint(udp),
308
- enabled: normalizedBoolean(udp.enabled),
309
- started: normalizedBoolean(udp.started)
310
- }
311
- };
312
- }
313
-
314
- export function genericTransportPhysicalError(value, fallback = 'transport-diagnostic-error') {
315
- const text = String(value instanceof Error ? value.message : value || '').trim();
316
- if (!text
317
- || containsRawNetworkText(text)
318
- || SECRET_TEXT.test(text)
319
- || UUID_TEXT.test(text)) return fallback;
320
- if (!/^[a-z0-9][a-z0-9 _.,:;()[\]/'"+=-]{0,239}$/i.test(text)) return fallback;
321
- return text;
322
- }
323
-
324
- export function redactTransportPhysicalReport(value, key = '') {
325
- if (isSecretKey(key)) return '[redacted]';
326
- if (isIdentifierKey(key)) return identifierMarker(value);
327
- if (isRawFilesystemKey(key)) return '[redacted-path]';
328
- if (isRawNetworkKey(key)) return '[redacted-network]';
329
- if (Array.isArray(value)) {
330
- return value.map(item => redactTransportPhysicalReport(item));
331
- }
332
- if (value && typeof value === 'object') {
333
- return Object.fromEntries(Object.entries(value)
334
- .filter(([childKey]) => !isSecretKey(childKey))
335
- .map(([childKey, childValue]) => [
336
- childKey,
337
- redactTransportPhysicalReport(childValue, childKey)
338
- ]));
339
- }
340
- if (typeof value === 'string'
341
- && !isAllowedStructuredScalar(key, value)
342
- && (containsRawNetworkText(value)
343
- || FILESYSTEM_TEXT.test(value)
344
- || SECRET_TEXT.test(value)
345
- || UUID_TEXT.test(value))) {
346
- return /error|reason|detail|summary/i.test(key)
347
- ? '[redacted-sensitive-error]'
348
- : '[redacted-sensitive]';
349
- }
350
- return value;
351
- }
352
-
353
- export function findTransportPhysicalReportLeaks(value, path = '$', leaks = []) {
354
- if (Array.isArray(value)) {
355
- value.forEach((item, index) => findTransportPhysicalReportLeaks(item, `${path}[${index}]`, leaks));
356
- return leaks;
357
- }
358
- if (value && typeof value === 'object') {
359
- for (const [key, child] of Object.entries(value)) {
360
- if (isSecretKey(key)
361
- || (isRawFilesystemKey(key) && child !== '[redacted-path]')
362
- || (isRawNetworkKey(key) && child !== '[redacted-network]')) {
363
- leaks.push(`${path}.${key}`);
364
- }
365
- if (isIdentifierKey(key)
366
- && child !== ''
367
- && child !== '[redacted-id]') {
368
- leaks.push(`${path}.${key}`);
369
- }
370
- findTransportPhysicalReportLeaks(child, `${path}.${key}`, leaks);
371
- }
372
- return leaks;
373
- }
374
- if (typeof value === 'string'
375
- && !isAllowedStructuredScalar(path.split('.').at(-1)?.replace(/\[\d+\]$/, ''), value)
376
- && (containsRawNetworkText(value)
377
- || FILESYSTEM_TEXT.test(value)
378
- || SECRET_TEXT.test(value)
379
- || UUID_TEXT.test(value))) {
380
- leaks.push(path);
381
- }
382
- return leaks;
383
- }
384
-
385
- function optionalFinite(value) {
386
- if (value === null || value === undefined || value === '') return null;
387
- const parsed = Number(value);
388
- return Number.isFinite(parsed) ? parsed : null;
389
- }
390
-
391
- function phaseFrame(snapshot, phase) {
392
- return Array.isArray(snapshot?.frames)
393
- ? snapshot.frames.find(frame => frame?.phase === phase && frame?.accepted === true)
394
- : null;
395
- }
396
-
397
- function expectedPathForPhase(phase) {
398
- if (phase === 'p2p') return 'p2p';
399
- if (phase === 'relay') return 'relay';
400
- return 'direct';
401
- }
402
-
403
- export function hubObservedTransport(frame) {
404
- const transport = String(frame?.transport || '').trim().toLowerCase();
405
- if (transport === 'udp-p2p') return { path: 'p2p', label: 'udp-p2p' };
406
- if (transport === 'relay-binary') return { path: 'relay', label: 'relay-binary' };
407
- if (transport === 'binary' || transport === 'tcp-binary') {
408
- return { path: 'direct', label: 'direct-tcp' };
409
- }
410
- if (transport === 'ws-binary') return { path: 'direct', label: 'direct-ws' };
411
- return { path: '', label: 'unknown' };
412
- }
413
-
414
- export function isTransportPhysicalPhaseEvidence(frame, phase) {
415
- const expectedPath = expectedPathForPhase(phase);
416
- const observed = hubObservedTransport(frame);
417
- const phaseLatencyMs = optionalFinite(frame?.phaseLatencyMs);
418
- return frame?.accepted === true
419
- && frame?.phase === phase
420
- && frame?.actualPath === expectedPath
421
- && observed.path === expectedPath
422
- && frame?.isKeyFrame === true
423
- && frame?.annexB === true
424
- && frame?.hasIdr === true
425
- && frame?.hasSps === true
426
- && frame?.hasPps === true
427
- && frame?.nalOrderValid === true
428
- && optionalFinite(frame?.byteLength) > 0
429
- && phaseLatencyMs !== null
430
- && phaseLatencyMs >= 0
431
- && phaseLatencyMs <= MAX_PHASE_LATENCY_MS;
432
- }
433
-
434
- function cumulativeDelta(values) {
435
- if (!Array.isArray(values) || values.length < 2) {
436
- return { valid: false, delta: null };
437
- }
438
- const normalized = values.map(optionalFinite);
439
- const valid = normalized.every(value => value !== null && value >= 0)
440
- && normalized.every((value, index) => index === 0 || value >= normalized[index - 1]);
441
- return {
442
- valid,
443
- delta: valid ? normalized.at(-1) - normalized[0] : null
444
- };
445
- }
446
-
447
- function scopedCounterDelta(baselines, samples) {
448
- const scopes = new Map();
449
- for (const baseline of baselines || []) {
450
- const scope = String(baseline?.scope || '');
451
- const value = optionalFinite(baseline?.value);
452
- if (!scope || value === null || value < 0 || scopes.has(scope)) {
453
- return { valid: false, delta: null };
454
- }
455
- scopes.set(scope, { initial: value, last: value });
456
- }
457
- let total = 0;
458
- for (const sample of samples || []) {
459
- const scope = String(sample?.scope || '');
460
- const value = optionalFinite(sample?.value);
461
- if (!scope || value === null || value < 0) {
462
- return { valid: false, delta: null };
463
- }
464
- const state = scopes.get(scope) || { initial: 0, last: 0 };
465
- if (value < state.last) {
466
- return { valid: false, delta: null };
467
- }
468
- state.last = value;
469
- scopes.set(scope, state);
470
- }
471
- for (const state of scopes.values()) {
472
- total += state.last - state.initial;
473
- }
474
- return { valid: true, delta: total };
475
- }
476
-
477
- function timestampWithin(value, reference, maximumSkewMs) {
478
- const observedAt = Date.parse(String(value || ''));
479
- const referenceAt = Date.parse(String(reference || ''));
480
- return Number.isFinite(observedAt)
481
- && Number.isFinite(referenceAt)
482
- && Math.abs(observedAt - referenceAt) <= maximumSkewMs;
483
- }
484
-
485
- export function classifyTransportPhysicalSnapshot(snapshot) {
486
- const checks = [];
487
- const add = (id, ok, detail) => checks.push({ id, ok: ok === true, detail });
488
- const owner = snapshot?.owner || {};
489
-
490
- add(
491
- 'same-current-owner',
492
- !!owner.streamId
493
- && !!owner.streamCommandId
494
- && Number(owner.captureGeneration || 0) > 0
495
- && Number(owner.monitorIndex) >= 0,
496
- 'One immutable stream, command, generation, and monitor must own the complete run.');
497
-
498
- const observedFrames = REQUIRED_PHASES.map(phase => phaseFrame(snapshot, phase));
499
- for (let index = 0; index < REQUIRED_PHASES.length; index += 1) {
500
- const phase = REQUIRED_PHASES[index];
501
- const frame = observedFrames[index];
502
- const expectedPath = expectedPathForPhase(phase);
503
- const observed = hubObservedTransport(frame);
504
- add(
505
- `received-h264-${phase}`,
506
- !!frame
507
- && frame.actualPath === expectedPath
508
- && frame.isKeyFrame === true
509
- && frame.annexB === true
510
- && frame.hasIdr === true
511
- && frame.hasSps === true
512
- && frame.hasPps === true
513
- && frame.nalOrderValid === true
514
- && optionalFinite(frame.byteLength) > 0,
515
- `A received Annex-B H.264 SPS -> PPS -> IDR access unit must prove ${phase}.`);
516
- add(
517
- `hub-observed-transport-${phase}`,
518
- !!frame && observed.path === expectedPath,
519
- `Hub ingress must observe ${phase} as ${observed.label}.`);
520
- const phaseLatencyMs = optionalFinite(frame?.phaseLatencyMs);
521
- add(
522
- `phase-latency-${phase}`,
523
- phaseLatencyMs !== null
524
- && phaseLatencyMs >= 0
525
- && phaseLatencyMs <= MAX_PHASE_LATENCY_MS,
526
- `${phaseLatencyMs === null ? 'unknown' : Math.round(phaseLatencyMs)}ms/15000ms`);
527
- }
528
-
529
- const receivedTimes = observedFrames.map(frame => Date.parse(frame?.receivedAt || ''));
530
- add(
531
- 'ordered-path-transition',
532
- observedFrames.every(Boolean)
533
- && receivedTimes.every(Number.isFinite)
534
- && receivedTimes.every((time, index) => index === 0 || time > receivedTimes[index - 1]),
535
- 'Frames must arrive in strict Direct, UDP P2P, encrypted relay, Direct order.');
536
-
537
- const frameSequences = observedFrames.map(frame => optionalFinite(frame?.frameSeq));
538
- add(
539
- 'strict-frame-sequence',
540
- frameSequences.every(value => Number.isSafeInteger(value) && value > 0)
541
- && frameSequences.every((value, index) => index === 0 || value > frameSequences[index - 1]),
542
- frameSequences.map(value => value ?? '?').join(' -> '));
543
-
544
- const switchCounts = observedFrames.map(frame => optionalFinite(frame?.switchCount));
545
- add(
546
- 'transport-switch-count',
547
- switchCounts.every(value => Number.isSafeInteger(value) && value >= 0)
548
- && switchCounts.every((value, index) => index === 0 || value > switchCounts[index - 1]),
549
- switchCounts.map(value => value ?? '?').join(' -> '));
550
-
551
- const network = snapshot?.network || {};
552
- add(
553
- 'distinct-public-ipv4-egress',
554
- network.evidence === 'authenticated-udp-endpoint-rendezvous-observed'
555
- && network.endpointAuthenticated === true
556
- && network.clientAddressPublic === true
557
- && network.hubAddressPublic === true
558
- && network.differentPublicEgress === true
559
- && network.hubEndpointSource === 'trusted-rendezvous-observation',
560
- 'Authenticated UDP and trusted-rendezvous-observed distinct public IPv4 egress values are required; this does not prove distinct NAT devices.');
561
- add(
562
- 'fresh-hub-public-ipv4-observation',
563
- timestampWithin(
564
- network.hubAddressObservedAt,
565
- snapshot?.startedAt,
566
- MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS),
567
- 'The Hub public IPv4 value must be freshly observed within 60 seconds of diagnostic start.');
568
- add(
569
- 'fresh-client-udp-observation',
570
- timestampWithin(
571
- network.observedAt,
572
- snapshot?.startedAt,
573
- MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS),
574
- 'The authenticated Client UDP endpoint must be observed within 60 seconds of diagnostic start.');
575
-
576
- const cleanup = Array.isArray(snapshot?.cleanup) ? snapshot.cleanup : [];
577
- add(
578
- 'lease-cleaned',
579
- snapshot?.state === 'completed'
580
- && cleanup.some(entry => entry?.state === 'agent-released'),
581
- 'The exact-owner diagnostic lease must be released by the Agent.');
582
-
583
- const baseline = snapshot?.baseline || {};
584
- const agentTelemetryAvailable = baseline.agentQueueTelemetryAvailable === true
585
- && optionalFinite(baseline.agentQueueDropped) !== null
586
- && observedFrames.every(frame =>
587
- frame?.agentQueueTelemetryAvailable === true
588
- && optionalFinite(frame.agentQueueDepth) !== null
589
- && optionalFinite(frame.agentQueueDropped) !== null);
590
- const hubTelemetryAvailable = baseline.hubIngressTelemetryAvailable === true
591
- && optionalFinite(baseline.hubIngressDropped) !== null
592
- && !!baseline.hubIngressCounterEpochHash
593
- && !!baseline.udpCounterEpochHash
594
- && observedFrames.every(frame =>
595
- frame?.hubIngressTelemetryAvailable === true
596
- && optionalFinite(frame.hubIngressDropped) !== null
597
- && !!frame.hubIngressCounterEpochHash);
598
- const p2pFrame = observedFrames[REQUIRED_PHASES.indexOf('p2p')];
599
- const udpTelemetryAvailable = baseline.udpTelemetryAvailable === true
600
- && optionalFinite(baseline.udpIncompleteFrames) !== null
601
- && p2pFrame?.udpTelemetryAvailable === true
602
- && optionalFinite(p2pFrame?.udpIncompleteFrames) !== null
603
- && p2pFrame?.hubIngressCounterEpochHash === baseline.udpCounterEpochHash;
604
- add(
605
- 'agent-queue-telemetry',
606
- agentTelemetryAvailable,
607
- agentTelemetryAvailable ? 'baseline and all four phases' : 'unknown or incomplete');
608
- add(
609
- 'hub-ingress-telemetry',
610
- hubTelemetryAvailable,
611
- hubTelemetryAvailable ? 'baseline and all four phases' : 'unknown or incomplete');
612
- add(
613
- 'udp-reassembly-telemetry',
614
- udpTelemetryAvailable,
615
- udpTelemetryAvailable ? 'baseline and P2P phase' : 'unknown or incomplete');
616
-
617
- const agentDropped = cumulativeDelta([
618
- baseline.agentQueueDropped,
619
- ...observedFrames.map(frame => frame?.agentQueueDropped)
620
- ]);
621
- const hubDropped = scopedCounterDelta([
622
- {
623
- scope: baseline.hubIngressCounterEpochHash,
624
- value: baseline.hubIngressDropped
625
- },
626
- {
627
- scope: baseline.udpCounterEpochHash,
628
- value: baseline.udpIncompleteFrames
629
- }
630
- ], observedFrames.map(frame => ({
631
- scope: frame?.hubIngressCounterEpochHash,
632
- value: frame?.hubIngressDropped
633
- })));
634
- const udpIncomplete = cumulativeDelta([
635
- baseline.udpIncompleteFrames,
636
- p2pFrame?.udpIncompleteFrames
637
- ]);
638
- const maxAgentQueueDepth = agentTelemetryAvailable
639
- ? Math.max(...observedFrames.map(frame => Number(frame.agentQueueDepth)))
640
- : null;
641
- const boundedQueues = agentTelemetryAvailable
642
- && hubTelemetryAvailable
643
- && udpTelemetryAvailable
644
- && agentDropped.valid
645
- && hubDropped.valid
646
- && udpIncomplete.valid
647
- && maxAgentQueueDepth <= MAX_AGENT_QUEUE_DEPTH
648
- && agentDropped.delta <= MAX_AGENT_QUEUE_DROPPED_DELTA
649
- && hubDropped.delta <= MAX_HUB_INGRESS_DROPPED_DELTA
650
- && udpIncomplete.delta <= MAX_UDP_INCOMPLETE_DELTA;
651
- add(
652
- 'bounded-frame-queues',
653
- boundedQueues,
654
- `queueDepth=${maxAgentQueueDepth ?? 'unknown'}, `
655
- + `agentDroppedDelta=${agentDropped.delta ?? 'unknown'}, `
656
- + `hubDroppedDelta=${hubDropped.delta ?? 'unknown'}, `
657
- + `udpIncompleteDelta=${udpIncomplete.delta ?? 'unknown'}`);
658
-
659
- const failed = checks.filter(check => !check.ok);
660
- const phaseLatencies = observedFrames.map(frame => optionalFinite(frame?.phaseLatencyMs));
661
- return {
662
- status: failed.length === 0 ? 'passed' : 'blocked',
663
- summary: failed.length === 0
664
- ? 'Direct, UDP P2P, encrypted relay, and Direct recovery were proven by independent received H.264 access units.'
665
- : `Physical transport proof is incomplete (${failed.map(check => check.id).join(', ')}).`,
666
- checks,
667
- metrics: {
668
- maxPhaseLatencyMs: phaseLatencies.every(value => value !== null)
669
- ? Math.max(...phaseLatencies)
670
- : null,
671
- maxAgentQueueDepth,
672
- agentQueueDroppedDelta: agentDropped.delta,
673
- hubIngressDroppedDelta: hubDropped.delta,
674
- udpIncompleteFramesDelta: udpIncomplete.delta,
675
- observedTransports: observedFrames.map(frame => hubObservedTransport(frame).label)
676
- }
677
- };
678
- }
679
-
680
- export function buildTransportPhysicalReport(snapshot, context = {}) {
681
- const classified = classifyTransportPhysicalSnapshot(snapshot);
682
- const report = redactTransportPhysicalReport({
683
- schema: 'livedesk.transport-physical.v2',
684
- generatedAt: new Date().toISOString(),
685
- hubUrl: context.hubUrl,
686
- selectedDeviceId: context.deviceId,
687
- provenance: buildTransportPhysicalProvenance(context.provenance),
688
- result: classified,
689
- transport: snapshot
690
- });
691
- const leaks = findTransportPhysicalReportLeaks(report);
692
- if (leaks.length > 0) {
693
- throw new Error(`transport-physical-report-redaction-failed:${leaks.join(',')}`);
694
- }
695
- return report;
696
- }
697
-
698
- export {
699
- MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS,
700
- MAX_PHASE_LATENCY_MS,
701
- REQUIRED_PHASES
702
- };
1
+ import { createHash } from 'node:crypto';
2
+ import { isIP } from 'node:net';
3
+
4
+ const REQUIRED_PHASES = Object.freeze([
5
+ 'direct',
6
+ 'p2p',
7
+ 'relay',
8
+ 'recover-direct'
9
+ ]);
10
+
11
+ const MAX_PHASE_LATENCY_MS = 15_000;
12
+ const MAX_AGENT_QUEUE_DEPTH = 8;
13
+ const MAX_AGENT_QUEUE_DROPPED_DELTA = 8;
14
+ const MAX_HUB_INGRESS_DROPPED_DELTA = 8;
15
+ const MAX_UDP_INCOMPLETE_DELTA = 2;
16
+ const MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS = 60_000;
17
+
18
+ const SECRET_KEYS = new Set([
19
+ 'authorization',
20
+ 'bearer',
21
+ 'token',
22
+ 'secret',
23
+ 'password',
24
+ 'apikey',
25
+ 'pairtoken'
26
+ ]);
27
+ const SAFE_NETWORK_KEYS = new Set([
28
+ 'clientaddresshash',
29
+ 'hubaddresshash',
30
+ 'clientaddresspublic',
31
+ 'hubaddresspublic',
32
+ 'hubaddressobservedat',
33
+ 'endpointauthenticated',
34
+ 'differentpublicegress',
35
+ 'evidence',
36
+ 'observedat',
37
+ 'rendezvousport',
38
+ 'udpready'
39
+ ]);
40
+ const IDENTIFIER_KEYS = new Set([
41
+ 'leaseid',
42
+ 'deviceid',
43
+ 'selecteddeviceid',
44
+ 'streamid',
45
+ 'streamcommandid',
46
+ 'commandid',
47
+ 'sessionid',
48
+ 'requestid',
49
+ 'connectionid',
50
+ 'hubconnectionid',
51
+ 'inputeventid',
52
+ 'clientid',
53
+ 'ownerid'
54
+ ]);
55
+ const FILESYSTEM_KEYS = new Set([
56
+ 'cwd',
57
+ 'directory',
58
+ 'dirname',
59
+ 'executablepath',
60
+ 'filepath',
61
+ 'filename',
62
+ 'homedir',
63
+ 'packagepath',
64
+ 'path',
65
+ 'reportpath'
66
+ ]);
67
+ const SECRET_TEXT = /\b(?:authorization|bearer|token|secret|password|api[-_ ]?key|pair[-_ ]?token)(?:\s+|\s*[:=]\s*)\S+/i;
68
+ const FILESYSTEM_TEXT = /(?:^|\s)(?:[a-z]:[\\/]|\\\\[^\\\s]+\\|\/(?:applications|etc|home|opt|private|run|srv|tmp|users|usr|var|volumes)\/)\S*/i;
69
+ const IPV4_TEXT = /(^|[^\d])(?:\d{1,3}\.){3}\d{1,3}(?::\d{1,5})?($|[^\d])/;
70
+ const IPV6_CANDIDATE_TEXT = /\[[0-9a-f:.%]+\](?::\d{1,5})?|[0-9a-f:%]*:[0-9a-f:.%:]*/gi;
71
+ const URL_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/[^\s]+/i;
72
+ const HOST_TEXT = /\b(?:localhost|(?:[a-z0-9-]+\.)+[a-z]{2,63})(?::\d{1,5})?\b/i;
73
+ const UUID_TEXT = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
74
+ const SAFE_PLATFORMS = new Set([
75
+ 'aix',
76
+ 'android',
77
+ 'darwin',
78
+ 'freebsd',
79
+ 'linux',
80
+ 'macos',
81
+ 'openbsd',
82
+ 'sunos',
83
+ 'win32',
84
+ 'windows'
85
+ ]);
86
+ const SAFE_ARCHITECTURES = new Set([
87
+ 'arm',
88
+ 'arm64',
89
+ 'ia32',
90
+ 'loong64',
91
+ 'mips',
92
+ 'mipsel',
93
+ 'ppc',
94
+ 'ppc64',
95
+ 'riscv64',
96
+ 's390',
97
+ 's390x',
98
+ 'x64'
99
+ ]);
100
+ const SAFE_RUNTIME_STATES = new Set([
101
+ 'booting',
102
+ 'connecting',
103
+ 'error',
104
+ 'resolving-role',
105
+ 'running',
106
+ 'starting',
107
+ 'stopping',
108
+ 'switching',
109
+ 'updating',
110
+ 'waiting-auth'
111
+ ]);
112
+ const SAFE_RUNTIME_ROLES = new Set(['client', 'hub']);
113
+ const SAFE_VERSION = /^(?:v)?\d+(?:\.\d+){1,3}(?:[-+][0-9a-z][0-9a-z.-]{0,47})?$/i;
114
+
115
+ function normalizedKey(key) {
116
+ return String(key || '').replace(/[-_]/g, '').toLowerCase();
117
+ }
118
+
119
+ function isIdentifierKey(key) {
120
+ return IDENTIFIER_KEYS.has(normalizedKey(key));
121
+ }
122
+
123
+ function isSecretKey(key) {
124
+ const normalized = normalizedKey(key);
125
+ return SECRET_KEYS.has(normalized)
126
+ || normalized.endsWith('token')
127
+ || normalized.endsWith('secret')
128
+ || normalized.endsWith('password')
129
+ || normalized.endsWith('apikey');
130
+ }
131
+
132
+ function isRawFilesystemKey(key) {
133
+ return FILESYSTEM_KEYS.has(normalizedKey(key));
134
+ }
135
+
136
+ function isRawNetworkKey(key) {
137
+ const rawKey = String(key || '');
138
+ const normalized = normalizedKey(key);
139
+ if (SAFE_NETWORK_KEYS.has(normalized) || normalized.endsWith('hash')) return false;
140
+ return normalized === 'address'
141
+ || normalized.endsWith('address')
142
+ || normalized === 'endpoint'
143
+ || normalized.endsWith('endpoint')
144
+ || normalized === 'host'
145
+ || normalized.endsWith('host')
146
+ || normalized === 'port'
147
+ || /(?:^|[-_])port$/i.test(rawKey)
148
+ || /[A-Z]Port$/.test(rawKey)
149
+ || normalized === 'url'
150
+ || normalized.endsWith('url')
151
+ || normalized === 'uri'
152
+ || normalized.endsWith('uri')
153
+ || normalized === 'origin'
154
+ || normalized.endsWith('origin');
155
+ }
156
+
157
+ function containsIpv6Text(value) {
158
+ const candidates = String(value || '').match(IPV6_CANDIDATE_TEXT) || [];
159
+ return candidates.some(candidate => {
160
+ const bracketed = candidate.match(/^\[([^\]]+)\](?::\d{1,5})?$/);
161
+ const address = String(bracketed?.[1] || candidate)
162
+ .replace(/%[a-z0-9_.-]+$/i, '')
163
+ .replace(/[.,;]+$/g, '');
164
+ return isIP(address) === 6;
165
+ });
166
+ }
167
+
168
+ function containsRawNetworkText(value) {
169
+ const text = String(value || '');
170
+ return IPV4_TEXT.test(text)
171
+ || containsIpv6Text(text)
172
+ || URL_TEXT.test(text)
173
+ || HOST_TEXT.test(text);
174
+ }
175
+
176
+ function identifierMarker(value) {
177
+ if (value === null || value === undefined || String(value) === '') return '';
178
+ return '[redacted-id]';
179
+ }
180
+
181
+ function normalizedEnum(value, allowed) {
182
+ const normalized = String(value || '').trim().toLowerCase();
183
+ return allowed.has(normalized) ? normalized : 'unknown';
184
+ }
185
+
186
+ function normalizedVersion(value) {
187
+ const normalized = String(value || '').trim();
188
+ if (normalized === 'dev') return 'dev';
189
+ return SAFE_VERSION.test(normalized) && isIP(normalized.replace(/^v/i, '')) === 0
190
+ ? normalized
191
+ : 'unknown';
192
+ }
193
+
194
+ function normalizedPackageName(value, fallback = '') {
195
+ const normalized = String(value || '').trim().toLowerCase();
196
+ if (normalized === 'livedesk' || /^@livedesk\/[a-z0-9-]{1,48}$/.test(normalized)) {
197
+ return normalized;
198
+ }
199
+ return fallback;
200
+ }
201
+
202
+ function normalizedBoolean(value) {
203
+ return typeof value === 'boolean' ? value : null;
204
+ }
205
+
206
+ function isAllowedStructuredScalar(key, value) {
207
+ const normalized = normalizedKey(key);
208
+ const text = String(value || '');
209
+ if (normalized === 'schema') {
210
+ return /^livedesk\.transport-physical\.v\d+$/.test(text);
211
+ }
212
+ if (normalized.endsWith('hash')) {
213
+ return /^sha256:[0-9a-f]{20,64}$/.test(text);
214
+ }
215
+ if ([
216
+ 'agentversion',
217
+ 'appversion',
218
+ 'managerversion',
219
+ 'nodeversion',
220
+ 'packageversion',
221
+ 'productversion'
222
+ ].includes(normalized)) {
223
+ return text === 'unknown'
224
+ || text === 'dev'
225
+ || normalizedVersion(text) === text;
226
+ }
227
+ return false;
228
+ }
229
+
230
+ function stableFingerprint(value) {
231
+ const normalized = String(value || '').trim();
232
+ if (!normalized) return '';
233
+ return `sha256:${createHash('sha256').update(normalized).digest('hex').slice(0, 20)}`;
234
+ }
235
+
236
+ function runtimeFingerprint(value = {}) {
237
+ const explicit = String(value.runtimeId || value.sessionId || '').trim();
238
+ if (explicit) return stableFingerprint(explicit);
239
+ const pid = Number(value.runtimePid ?? value.pid);
240
+ const startedAt = String(value.startedAt || value.connectedAt || '').trim();
241
+ if (!Number.isSafeInteger(pid) || pid <= 0 || !startedAt) return '';
242
+ return stableFingerprint(`${pid}:${startedAt}`);
243
+ }
244
+
245
+ function normalizedRendezvousEndpoint(value = {}) {
246
+ let rawHost = String(value.host || value.rendezvousHost || '').trim();
247
+ let rawPort = Number(value.port ?? value.rendezvousPort);
248
+ if (rawHost) {
249
+ try {
250
+ const parsed = new URL(rawHost.includes('://') ? rawHost : `udp://${rawHost}`);
251
+ rawHost = parsed.hostname.replace(/^\[|\]$/g, '').toLowerCase().replace(/\.$/, '');
252
+ if ((!Number.isSafeInteger(rawPort) || rawPort < 1 || rawPort > 65_535)
253
+ && parsed.port) {
254
+ rawPort = Number(parsed.port);
255
+ }
256
+ } catch {
257
+ rawHost = '';
258
+ }
259
+ }
260
+ const rendezvousPort = Number.isSafeInteger(rawPort) && rawPort >= 1 && rawPort <= 65_535
261
+ ? rawPort
262
+ : null;
263
+ return {
264
+ endpointKind: 'udp-rendezvous',
265
+ configured: !!rawHost && rendezvousPort !== null,
266
+ hostHash: stableFingerprint(rawHost),
267
+ rendezvousPort
268
+ };
269
+ }
270
+
271
+ export function buildTransportPhysicalProvenance(input = {}) {
272
+ const liveDeskPackage = input.liveDeskPackage || {};
273
+ const runner = input.runner || {};
274
+ const hubRuntime = input.hubRuntime || {};
275
+ const hubRemote = input.hubRemote || {};
276
+ const client = input.client || {};
277
+ const udp = input.rendezvous || hubRemote.udp || {};
278
+ const hubVersion = normalizedVersion(hubRuntime.appVersion);
279
+ const managerVersion = normalizedVersion(hubRemote.managerVersion);
280
+ return {
281
+ livedesk: {
282
+ packageName: 'livedesk',
283
+ packageVersion: normalizedVersion(liveDeskPackage.version)
284
+ },
285
+ runner: {
286
+ nodeVersion: normalizedVersion(runner.nodeVersion),
287
+ platform: normalizedEnum(runner.platform, SAFE_PLATFORMS),
288
+ arch: normalizedEnum(runner.arch, SAFE_ARCHITECTURES)
289
+ },
290
+ hub: {
291
+ packageName: normalizedPackageName(hubRemote.managerPackage, '@livedesk/hub'),
292
+ appVersion: hubVersion !== 'unknown' ? hubVersion : managerVersion,
293
+ managerVersion,
294
+ role: normalizedEnum(hubRuntime.role || hubRemote.runtimeRole, SAFE_RUNTIME_ROLES),
295
+ state: normalizedEnum(hubRuntime.state, SAFE_RUNTIME_STATES),
296
+ runtimeStarted: normalizedBoolean(hubRuntime.runtimeStarted),
297
+ runtimeIdentityHash: runtimeFingerprint(hubRuntime)
298
+ },
299
+ client: {
300
+ productVersion: normalizedVersion(client.productVersion),
301
+ agentVersion: normalizedVersion(client.agentVersion),
302
+ platform: normalizedEnum(client.platform, SAFE_PLATFORMS),
303
+ arch: normalizedEnum(client.arch, SAFE_ARCHITECTURES),
304
+ runtimeIdentityHash: runtimeFingerprint(client)
305
+ },
306
+ rendezvous: {
307
+ ...normalizedRendezvousEndpoint(udp),
308
+ enabled: normalizedBoolean(udp.enabled),
309
+ started: normalizedBoolean(udp.started)
310
+ }
311
+ };
312
+ }
313
+
314
+ export function genericTransportPhysicalError(value, fallback = 'transport-diagnostic-error') {
315
+ const text = String(value instanceof Error ? value.message : value || '').trim();
316
+ if (!text
317
+ || containsRawNetworkText(text)
318
+ || SECRET_TEXT.test(text)
319
+ || UUID_TEXT.test(text)) return fallback;
320
+ if (!/^[a-z0-9][a-z0-9 _.,:;()[\]/'"+=-]{0,239}$/i.test(text)) return fallback;
321
+ return text;
322
+ }
323
+
324
+ export function redactTransportPhysicalReport(value, key = '') {
325
+ if (isSecretKey(key)) return '[redacted]';
326
+ if (isIdentifierKey(key)) return identifierMarker(value);
327
+ if (isRawFilesystemKey(key)) return '[redacted-path]';
328
+ if (isRawNetworkKey(key)) return '[redacted-network]';
329
+ if (Array.isArray(value)) {
330
+ return value.map(item => redactTransportPhysicalReport(item));
331
+ }
332
+ if (value && typeof value === 'object') {
333
+ return Object.fromEntries(Object.entries(value)
334
+ .filter(([childKey]) => !isSecretKey(childKey))
335
+ .map(([childKey, childValue]) => [
336
+ childKey,
337
+ redactTransportPhysicalReport(childValue, childKey)
338
+ ]));
339
+ }
340
+ if (typeof value === 'string'
341
+ && !isAllowedStructuredScalar(key, value)
342
+ && (containsRawNetworkText(value)
343
+ || FILESYSTEM_TEXT.test(value)
344
+ || SECRET_TEXT.test(value)
345
+ || UUID_TEXT.test(value))) {
346
+ return /error|reason|detail|summary/i.test(key)
347
+ ? '[redacted-sensitive-error]'
348
+ : '[redacted-sensitive]';
349
+ }
350
+ return value;
351
+ }
352
+
353
+ export function findTransportPhysicalReportLeaks(value, path = '$', leaks = []) {
354
+ if (Array.isArray(value)) {
355
+ value.forEach((item, index) => findTransportPhysicalReportLeaks(item, `${path}[${index}]`, leaks));
356
+ return leaks;
357
+ }
358
+ if (value && typeof value === 'object') {
359
+ for (const [key, child] of Object.entries(value)) {
360
+ if (isSecretKey(key)
361
+ || (isRawFilesystemKey(key) && child !== '[redacted-path]')
362
+ || (isRawNetworkKey(key) && child !== '[redacted-network]')) {
363
+ leaks.push(`${path}.${key}`);
364
+ }
365
+ if (isIdentifierKey(key)
366
+ && child !== ''
367
+ && child !== '[redacted-id]') {
368
+ leaks.push(`${path}.${key}`);
369
+ }
370
+ findTransportPhysicalReportLeaks(child, `${path}.${key}`, leaks);
371
+ }
372
+ return leaks;
373
+ }
374
+ if (typeof value === 'string'
375
+ && !isAllowedStructuredScalar(path.split('.').at(-1)?.replace(/\[\d+\]$/, ''), value)
376
+ && (containsRawNetworkText(value)
377
+ || FILESYSTEM_TEXT.test(value)
378
+ || SECRET_TEXT.test(value)
379
+ || UUID_TEXT.test(value))) {
380
+ leaks.push(path);
381
+ }
382
+ return leaks;
383
+ }
384
+
385
+ function optionalFinite(value) {
386
+ if (value === null || value === undefined || value === '') return null;
387
+ const parsed = Number(value);
388
+ return Number.isFinite(parsed) ? parsed : null;
389
+ }
390
+
391
+ function phaseFrame(snapshot, phase) {
392
+ return Array.isArray(snapshot?.frames)
393
+ ? snapshot.frames.find(frame => frame?.phase === phase && frame?.accepted === true)
394
+ : null;
395
+ }
396
+
397
+ function expectedPathForPhase(phase) {
398
+ if (phase === 'p2p') return 'p2p';
399
+ if (phase === 'relay') return 'relay';
400
+ return 'direct';
401
+ }
402
+
403
+ export function hubObservedTransport(frame) {
404
+ const transport = String(frame?.transport || '').trim().toLowerCase();
405
+ if (transport === 'udp-p2p') return { path: 'p2p', label: 'udp-p2p' };
406
+ if (transport === 'relay-binary') return { path: 'relay', label: 'relay-binary' };
407
+ if (transport === 'binary' || transport === 'tcp-binary') {
408
+ return { path: 'direct', label: 'direct-tcp' };
409
+ }
410
+ if (transport === 'ws-binary') return { path: 'direct', label: 'direct-ws' };
411
+ return { path: '', label: 'unknown' };
412
+ }
413
+
414
+ export function isTransportPhysicalPhaseEvidence(frame, phase) {
415
+ const expectedPath = expectedPathForPhase(phase);
416
+ const observed = hubObservedTransport(frame);
417
+ const phaseLatencyMs = optionalFinite(frame?.phaseLatencyMs);
418
+ return frame?.accepted === true
419
+ && frame?.phase === phase
420
+ && frame?.actualPath === expectedPath
421
+ && observed.path === expectedPath
422
+ && frame?.isKeyFrame === true
423
+ && frame?.annexB === true
424
+ && frame?.hasIdr === true
425
+ && frame?.hasSps === true
426
+ && frame?.hasPps === true
427
+ && frame?.nalOrderValid === true
428
+ && optionalFinite(frame?.byteLength) > 0
429
+ && phaseLatencyMs !== null
430
+ && phaseLatencyMs >= 0
431
+ && phaseLatencyMs <= MAX_PHASE_LATENCY_MS;
432
+ }
433
+
434
+ function cumulativeDelta(values) {
435
+ if (!Array.isArray(values) || values.length < 2) {
436
+ return { valid: false, delta: null };
437
+ }
438
+ const normalized = values.map(optionalFinite);
439
+ const valid = normalized.every(value => value !== null && value >= 0)
440
+ && normalized.every((value, index) => index === 0 || value >= normalized[index - 1]);
441
+ return {
442
+ valid,
443
+ delta: valid ? normalized.at(-1) - normalized[0] : null
444
+ };
445
+ }
446
+
447
+ function scopedCounterDelta(baselines, samples) {
448
+ const scopes = new Map();
449
+ for (const baseline of baselines || []) {
450
+ const scope = String(baseline?.scope || '');
451
+ const value = optionalFinite(baseline?.value);
452
+ if (!scope || value === null || value < 0 || scopes.has(scope)) {
453
+ return { valid: false, delta: null };
454
+ }
455
+ scopes.set(scope, { initial: value, last: value });
456
+ }
457
+ let total = 0;
458
+ for (const sample of samples || []) {
459
+ const scope = String(sample?.scope || '');
460
+ const value = optionalFinite(sample?.value);
461
+ if (!scope || value === null || value < 0) {
462
+ return { valid: false, delta: null };
463
+ }
464
+ const state = scopes.get(scope) || { initial: 0, last: 0 };
465
+ if (value < state.last) {
466
+ return { valid: false, delta: null };
467
+ }
468
+ state.last = value;
469
+ scopes.set(scope, state);
470
+ }
471
+ for (const state of scopes.values()) {
472
+ total += state.last - state.initial;
473
+ }
474
+ return { valid: true, delta: total };
475
+ }
476
+
477
+ function timestampWithin(value, reference, maximumSkewMs) {
478
+ const observedAt = Date.parse(String(value || ''));
479
+ const referenceAt = Date.parse(String(reference || ''));
480
+ return Number.isFinite(observedAt)
481
+ && Number.isFinite(referenceAt)
482
+ && Math.abs(observedAt - referenceAt) <= maximumSkewMs;
483
+ }
484
+
485
+ export function classifyTransportPhysicalSnapshot(snapshot) {
486
+ const checks = [];
487
+ const add = (id, ok, detail) => checks.push({ id, ok: ok === true, detail });
488
+ const owner = snapshot?.owner || {};
489
+
490
+ add(
491
+ 'same-current-owner',
492
+ !!owner.streamId
493
+ && !!owner.streamCommandId
494
+ && Number(owner.captureGeneration || 0) > 0
495
+ && Number(owner.monitorIndex) >= 0,
496
+ 'One immutable stream, command, generation, and monitor must own the complete run.');
497
+
498
+ const observedFrames = REQUIRED_PHASES.map(phase => phaseFrame(snapshot, phase));
499
+ for (let index = 0; index < REQUIRED_PHASES.length; index += 1) {
500
+ const phase = REQUIRED_PHASES[index];
501
+ const frame = observedFrames[index];
502
+ const expectedPath = expectedPathForPhase(phase);
503
+ const observed = hubObservedTransport(frame);
504
+ add(
505
+ `received-h264-${phase}`,
506
+ !!frame
507
+ && frame.actualPath === expectedPath
508
+ && frame.isKeyFrame === true
509
+ && frame.annexB === true
510
+ && frame.hasIdr === true
511
+ && frame.hasSps === true
512
+ && frame.hasPps === true
513
+ && frame.nalOrderValid === true
514
+ && optionalFinite(frame.byteLength) > 0,
515
+ `A received Annex-B H.264 SPS -> PPS -> IDR access unit must prove ${phase}.`);
516
+ add(
517
+ `hub-observed-transport-${phase}`,
518
+ !!frame && observed.path === expectedPath,
519
+ `Hub ingress must observe ${phase} as ${observed.label}.`);
520
+ const phaseLatencyMs = optionalFinite(frame?.phaseLatencyMs);
521
+ add(
522
+ `phase-latency-${phase}`,
523
+ phaseLatencyMs !== null
524
+ && phaseLatencyMs >= 0
525
+ && phaseLatencyMs <= MAX_PHASE_LATENCY_MS,
526
+ `${phaseLatencyMs === null ? 'unknown' : Math.round(phaseLatencyMs)}ms/15000ms`);
527
+ }
528
+
529
+ const receivedTimes = observedFrames.map(frame => Date.parse(frame?.receivedAt || ''));
530
+ add(
531
+ 'ordered-path-transition',
532
+ observedFrames.every(Boolean)
533
+ && receivedTimes.every(Number.isFinite)
534
+ && receivedTimes.every((time, index) => index === 0 || time > receivedTimes[index - 1]),
535
+ 'Frames must arrive in strict Direct, UDP P2P, encrypted relay, Direct order.');
536
+
537
+ const frameSequences = observedFrames.map(frame => optionalFinite(frame?.frameSeq));
538
+ add(
539
+ 'strict-frame-sequence',
540
+ frameSequences.every(value => Number.isSafeInteger(value) && value > 0)
541
+ && frameSequences.every((value, index) => index === 0 || value > frameSequences[index - 1]),
542
+ frameSequences.map(value => value ?? '?').join(' -> '));
543
+
544
+ const switchCounts = observedFrames.map(frame => optionalFinite(frame?.switchCount));
545
+ add(
546
+ 'transport-switch-count',
547
+ switchCounts.every(value => Number.isSafeInteger(value) && value >= 0)
548
+ && switchCounts.every((value, index) => index === 0 || value > switchCounts[index - 1]),
549
+ switchCounts.map(value => value ?? '?').join(' -> '));
550
+
551
+ const network = snapshot?.network || {};
552
+ add(
553
+ 'distinct-public-ipv4-egress',
554
+ network.evidence === 'authenticated-udp-endpoint-rendezvous-observed'
555
+ && network.endpointAuthenticated === true
556
+ && network.clientAddressPublic === true
557
+ && network.hubAddressPublic === true
558
+ && network.differentPublicEgress === true
559
+ && network.hubEndpointSource === 'trusted-rendezvous-observation',
560
+ 'Authenticated UDP and trusted-rendezvous-observed distinct public IPv4 egress values are required; this does not prove distinct NAT devices.');
561
+ add(
562
+ 'fresh-hub-public-ipv4-observation',
563
+ timestampWithin(
564
+ network.hubAddressObservedAt,
565
+ snapshot?.startedAt,
566
+ MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS),
567
+ 'The Hub public IPv4 value must be freshly observed within 60 seconds of diagnostic start.');
568
+ add(
569
+ 'fresh-client-udp-observation',
570
+ timestampWithin(
571
+ network.observedAt,
572
+ snapshot?.startedAt,
573
+ MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS),
574
+ 'The authenticated Client UDP endpoint must be observed within 60 seconds of diagnostic start.');
575
+
576
+ const cleanup = Array.isArray(snapshot?.cleanup) ? snapshot.cleanup : [];
577
+ add(
578
+ 'lease-cleaned',
579
+ snapshot?.state === 'completed'
580
+ && cleanup.some(entry => entry?.state === 'agent-released'),
581
+ 'The exact-owner diagnostic lease must be released by the Agent.');
582
+
583
+ const baseline = snapshot?.baseline || {};
584
+ const agentTelemetryAvailable = baseline.agentQueueTelemetryAvailable === true
585
+ && optionalFinite(baseline.agentQueueDropped) !== null
586
+ && observedFrames.every(frame =>
587
+ frame?.agentQueueTelemetryAvailable === true
588
+ && optionalFinite(frame.agentQueueDepth) !== null
589
+ && optionalFinite(frame.agentQueueDropped) !== null);
590
+ const hubTelemetryAvailable = baseline.hubIngressTelemetryAvailable === true
591
+ && optionalFinite(baseline.hubIngressDropped) !== null
592
+ && !!baseline.hubIngressCounterEpochHash
593
+ && !!baseline.udpCounterEpochHash
594
+ && observedFrames.every(frame =>
595
+ frame?.hubIngressTelemetryAvailable === true
596
+ && optionalFinite(frame.hubIngressDropped) !== null
597
+ && !!frame.hubIngressCounterEpochHash);
598
+ const p2pFrame = observedFrames[REQUIRED_PHASES.indexOf('p2p')];
599
+ const udpTelemetryAvailable = baseline.udpTelemetryAvailable === true
600
+ && optionalFinite(baseline.udpIncompleteFrames) !== null
601
+ && p2pFrame?.udpTelemetryAvailable === true
602
+ && optionalFinite(p2pFrame?.udpIncompleteFrames) !== null
603
+ && p2pFrame?.hubIngressCounterEpochHash === baseline.udpCounterEpochHash;
604
+ add(
605
+ 'agent-queue-telemetry',
606
+ agentTelemetryAvailable,
607
+ agentTelemetryAvailable ? 'baseline and all four phases' : 'unknown or incomplete');
608
+ add(
609
+ 'hub-ingress-telemetry',
610
+ hubTelemetryAvailable,
611
+ hubTelemetryAvailable ? 'baseline and all four phases' : 'unknown or incomplete');
612
+ add(
613
+ 'udp-reassembly-telemetry',
614
+ udpTelemetryAvailable,
615
+ udpTelemetryAvailable ? 'baseline and P2P phase' : 'unknown or incomplete');
616
+
617
+ const agentDropped = cumulativeDelta([
618
+ baseline.agentQueueDropped,
619
+ ...observedFrames.map(frame => frame?.agentQueueDropped)
620
+ ]);
621
+ const hubDropped = scopedCounterDelta([
622
+ {
623
+ scope: baseline.hubIngressCounterEpochHash,
624
+ value: baseline.hubIngressDropped
625
+ },
626
+ {
627
+ scope: baseline.udpCounterEpochHash,
628
+ value: baseline.udpIncompleteFrames
629
+ }
630
+ ], observedFrames.map(frame => ({
631
+ scope: frame?.hubIngressCounterEpochHash,
632
+ value: frame?.hubIngressDropped
633
+ })));
634
+ const udpIncomplete = cumulativeDelta([
635
+ baseline.udpIncompleteFrames,
636
+ p2pFrame?.udpIncompleteFrames
637
+ ]);
638
+ const maxAgentQueueDepth = agentTelemetryAvailable
639
+ ? Math.max(...observedFrames.map(frame => Number(frame.agentQueueDepth)))
640
+ : null;
641
+ const boundedQueues = agentTelemetryAvailable
642
+ && hubTelemetryAvailable
643
+ && udpTelemetryAvailable
644
+ && agentDropped.valid
645
+ && hubDropped.valid
646
+ && udpIncomplete.valid
647
+ && maxAgentQueueDepth <= MAX_AGENT_QUEUE_DEPTH
648
+ && agentDropped.delta <= MAX_AGENT_QUEUE_DROPPED_DELTA
649
+ && hubDropped.delta <= MAX_HUB_INGRESS_DROPPED_DELTA
650
+ && udpIncomplete.delta <= MAX_UDP_INCOMPLETE_DELTA;
651
+ add(
652
+ 'bounded-frame-queues',
653
+ boundedQueues,
654
+ `queueDepth=${maxAgentQueueDepth ?? 'unknown'}, `
655
+ + `agentDroppedDelta=${agentDropped.delta ?? 'unknown'}, `
656
+ + `hubDroppedDelta=${hubDropped.delta ?? 'unknown'}, `
657
+ + `udpIncompleteDelta=${udpIncomplete.delta ?? 'unknown'}`);
658
+
659
+ const failed = checks.filter(check => !check.ok);
660
+ const phaseLatencies = observedFrames.map(frame => optionalFinite(frame?.phaseLatencyMs));
661
+ return {
662
+ status: failed.length === 0 ? 'passed' : 'blocked',
663
+ summary: failed.length === 0
664
+ ? 'Direct, UDP P2P, encrypted relay, and Direct recovery were proven by independent received H.264 access units.'
665
+ : `Physical transport proof is incomplete (${failed.map(check => check.id).join(', ')}).`,
666
+ checks,
667
+ metrics: {
668
+ maxPhaseLatencyMs: phaseLatencies.every(value => value !== null)
669
+ ? Math.max(...phaseLatencies)
670
+ : null,
671
+ maxAgentQueueDepth,
672
+ agentQueueDroppedDelta: agentDropped.delta,
673
+ hubIngressDroppedDelta: hubDropped.delta,
674
+ udpIncompleteFramesDelta: udpIncomplete.delta,
675
+ observedTransports: observedFrames.map(frame => hubObservedTransport(frame).label)
676
+ }
677
+ };
678
+ }
679
+
680
+ export function buildTransportPhysicalReport(snapshot, context = {}) {
681
+ const classified = classifyTransportPhysicalSnapshot(snapshot);
682
+ const report = redactTransportPhysicalReport({
683
+ schema: 'livedesk.transport-physical.v2',
684
+ generatedAt: new Date().toISOString(),
685
+ hubUrl: context.hubUrl,
686
+ selectedDeviceId: context.deviceId,
687
+ provenance: buildTransportPhysicalProvenance(context.provenance),
688
+ result: classified,
689
+ transport: snapshot
690
+ });
691
+ const leaks = findTransportPhysicalReportLeaks(report);
692
+ if (leaks.length > 0) {
693
+ throw new Error(`transport-physical-report-redaction-failed:${leaks.join(',')}`);
694
+ }
695
+ return report;
696
+ }
697
+
698
+ export {
699
+ MAX_HUB_ADDRESS_OBSERVATION_SKEW_MS,
700
+ MAX_PHASE_LATENCY_MS,
701
+ REQUIRED_PHASES
702
+ };