@hraness/ghostget 0.18.14 → 0.18.16
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 +9 -0
- package/README.md +6 -6
- package/dist/apple-photos-client.js +1 -1
- package/dist/beeper-client.js +1 -1
- package/dist/{imessage-direct-install-5nftwc6p.js → imessage-direct-install-akfrgtpc.js} +2 -2
- package/dist/{index-kaav16fd.js → index-k2m73ke6.js} +3 -3
- package/dist/{index-g6na3p6k.js → index-km75a0bm.js} +1 -1
- package/dist/{index-yq6maz71.js → index-wwtk6nnt.js} +8 -26
- package/dist/messaging-automation-api.js +2 -2
- package/dist/{messaging-automation-58s1366z.js → messaging-automation-w7n3j4sf.js} +1 -1
- package/dist/{messaging-native-install-c90nv501.js → messaging-native-install-wk10f605.js} +2 -2
- package/dist/{whatsapp-automation-runtime-ke1dx91p.js → whatsapp-automation-runtime-zp2fx46r.js} +1 -1
- package/package.json +14 -7
- package/skills/ghostget/references/install.md +5 -5
- package/src/accounts-auth.ts +188 -0
- package/src/args.ts +14 -0
- package/src/beeper-client-types.ts +1 -1
- package/src/derivation-network-guard.ts +37 -165
- package/src/derivation-network-proxy-helper.ts +27 -56
- package/src/derive.ts +36 -37
- package/src/ghostget.ts +43 -0
- package/src/providers/x-web-runtime.ts +11 -11
- package/src/providers/x-web.ts +36 -35
- package/src/storage.ts +8 -25
- package/src/usage.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
closeSync,
|
|
5
|
-
constants,
|
|
6
|
-
fchmodSync,
|
|
7
|
-
fstatSync,
|
|
8
|
-
fsyncSync,
|
|
9
|
-
lstatSync,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
openSync,
|
|
12
|
-
readSync,
|
|
13
|
-
unlinkSync,
|
|
14
|
-
writeSync,
|
|
15
|
-
} from "node:fs";
|
|
2
|
+
import { chmodSync, mkdirSync } from "node:fs";
|
|
16
3
|
import { tmpdir } from "node:os";
|
|
17
|
-
import { join } from "node:path";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
assertOwnedPathSync,
|
|
8
|
+
readOwnedFileStableSync,
|
|
9
|
+
} from "@hraness/local-custody/private-paths";
|
|
10
|
+
import { createPrivateFileOnceSync } from "@hraness/local-custody/atomic-publish";
|
|
18
11
|
|
|
19
12
|
import { canonicalJson, sha256 } from "./canonical-json";
|
|
20
13
|
import type { ProcessOwnerIdentity } from "./process-identity";
|
|
@@ -141,11 +134,6 @@ type GuardRuntimeCase = {
|
|
|
141
134
|
readonly ruleId: number;
|
|
142
135
|
};
|
|
143
136
|
|
|
144
|
-
function currentUserOwns(uid: number | bigint): boolean {
|
|
145
|
-
const current = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
146
|
-
return current === undefined || uid === (typeof uid === "bigint" ? BigInt(current) : current);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
137
|
function escapeRegex(value: string): string {
|
|
150
138
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
151
139
|
}
|
|
@@ -307,14 +295,11 @@ function extensionIdFromKey(): string {
|
|
|
307
295
|
|
|
308
296
|
function inspectDirectory(path: string): GuardDirectoryIdentity {
|
|
309
297
|
try {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|| (stats.mode & 0o777n) !== 0o700n
|
|
316
|
-
) throw new Error("unsafe");
|
|
317
|
-
return { device: stats.dev.toString(), inode: stats.ino.toString() };
|
|
298
|
+
const identity = assertOwnedPathSync(path, {
|
|
299
|
+
kind: "directory",
|
|
300
|
+
exactMode: 0o700,
|
|
301
|
+
});
|
|
302
|
+
return { device: identity.dev.toString(), inode: identity.ino.toString() };
|
|
318
303
|
} catch {
|
|
319
304
|
throw new Error("derivation network guard directory is unavailable or unsafe");
|
|
320
305
|
}
|
|
@@ -325,71 +310,18 @@ function sameIdentity(left: GuardDirectoryIdentity, right: GuardDirectoryIdentit
|
|
|
325
310
|
}
|
|
326
311
|
|
|
327
312
|
export function writeGuardPrivateFile(path: string, content: string): GuardPrivateFileEvidence {
|
|
328
|
-
let descriptor: number | null = null;
|
|
329
|
-
let openedIdentity: GuardDirectoryIdentity | null = null;
|
|
330
|
-
let completed = false;
|
|
331
313
|
try {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
);
|
|
340
|
-
const opened = fstatSync(descriptor, { bigint: true });
|
|
341
|
-
openedIdentity = { device: opened.dev.toString(), inode: opened.ino.toString() };
|
|
342
|
-
fchmodSync(descriptor, 0o600);
|
|
343
|
-
const bytes = Buffer.from(content, "utf8");
|
|
344
|
-
let offset = 0;
|
|
345
|
-
while (offset < bytes.byteLength) {
|
|
346
|
-
offset += writeSync(descriptor, bytes, offset, bytes.byteLength - offset);
|
|
347
|
-
}
|
|
348
|
-
fsyncSync(descriptor);
|
|
349
|
-
const stats = fstatSync(descriptor, { bigint: true });
|
|
350
|
-
if (
|
|
351
|
-
!stats.isFile()
|
|
352
|
-
|| !currentUserOwns(stats.uid)
|
|
353
|
-
|| (stats.mode & 0o777n) !== 0o600n
|
|
354
|
-
|| stats.size !== BigInt(bytes.byteLength)
|
|
355
|
-
) throw new Error("derivation network guard file could not be secured");
|
|
356
|
-
const evidence = {
|
|
357
|
-
device: stats.dev.toString(),
|
|
358
|
-
inode: stats.ino.toString(),
|
|
359
|
-
byteLength: bytes.byteLength,
|
|
314
|
+
const outcome = createPrivateFileOnceSync(dirname(path), basename(path), content);
|
|
315
|
+
if (outcome !== "created") throw new Error("exists");
|
|
316
|
+
const identity = assertOwnedPathSync(path, { kind: "file", exactMode: 0o600 });
|
|
317
|
+
return {
|
|
318
|
+
device: identity.dev.toString(),
|
|
319
|
+
inode: identity.ino.toString(),
|
|
320
|
+
byteLength: Buffer.byteLength(content, "utf8"),
|
|
360
321
|
sha256: sha256(content),
|
|
361
322
|
};
|
|
362
|
-
completed = true;
|
|
363
|
-
return evidence;
|
|
364
323
|
} catch {
|
|
365
324
|
throw new Error("derivation network guard file could not be secured");
|
|
366
|
-
} finally {
|
|
367
|
-
if (descriptor !== null) {
|
|
368
|
-
if (!completed && openedIdentity === null) {
|
|
369
|
-
try {
|
|
370
|
-
const stats = fstatSync(descriptor, { bigint: true });
|
|
371
|
-
openedIdentity = { device: stats.dev.toString(), inode: stats.ino.toString() };
|
|
372
|
-
} catch {
|
|
373
|
-
// Without an exact identity, preserve the path instead of unlinking.
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
try {
|
|
377
|
-
closeSync(descriptor);
|
|
378
|
-
} catch {
|
|
379
|
-
// The categorical write failure below must not expose a local path.
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
if (!completed && openedIdentity !== null) {
|
|
383
|
-
try {
|
|
384
|
-
const current = lstatSync(path, { bigint: true });
|
|
385
|
-
if (
|
|
386
|
-
current.dev.toString() === openedIdentity.device
|
|
387
|
-
&& current.ino.toString() === openedIdentity.inode
|
|
388
|
-
) unlinkSync(path);
|
|
389
|
-
} catch {
|
|
390
|
-
// A changed path is intentionally preserved.
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
325
|
}
|
|
394
326
|
}
|
|
395
327
|
|
|
@@ -397,55 +329,23 @@ export function readGuardPrivateFile(
|
|
|
397
329
|
path: string,
|
|
398
330
|
maximumBytes = 4 * 1024 * 1024,
|
|
399
331
|
): { readonly content: string; readonly evidence: GuardPrivateFileEvidence } {
|
|
400
|
-
let descriptor: number | null = null;
|
|
401
332
|
try {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
);
|
|
406
|
-
const
|
|
407
|
-
if (
|
|
408
|
-
!before.isFile()
|
|
409
|
-
|| !currentUserOwns(before.uid)
|
|
410
|
-
|| (before.mode & 0o777n) !== 0o600n
|
|
411
|
-
|| before.size < 1n
|
|
412
|
-
|| before.size > BigInt(maximumBytes)
|
|
413
|
-
) throw new Error("derivation network guard file is unavailable or unsafe");
|
|
414
|
-
const bytes = Buffer.alloc(Number(before.size));
|
|
415
|
-
let offset = 0;
|
|
416
|
-
while (offset < bytes.byteLength) {
|
|
417
|
-
const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null);
|
|
418
|
-
if (count === 0) throw new Error("derivation network guard file changed size");
|
|
419
|
-
offset += count;
|
|
420
|
-
}
|
|
421
|
-
const after = fstatSync(descriptor, { bigint: true });
|
|
422
|
-
if (
|
|
423
|
-
before.dev !== after.dev
|
|
424
|
-
|| before.ino !== after.ino
|
|
425
|
-
|| before.size !== after.size
|
|
426
|
-
|| before.mtimeNs !== after.mtimeNs
|
|
427
|
-
|| before.ctimeNs !== after.ctimeNs
|
|
428
|
-
) throw new Error("derivation network guard file changed while reading");
|
|
429
|
-
const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
333
|
+
const read = readOwnedFileStableSync(path, maximumBytes, {
|
|
334
|
+
exactMode: 0o600,
|
|
335
|
+
minimumBytes: 1n,
|
|
336
|
+
});
|
|
337
|
+
const content = new TextDecoder("utf-8", { fatal: true }).decode(read.bytes);
|
|
430
338
|
return {
|
|
431
339
|
content,
|
|
432
340
|
evidence: {
|
|
433
|
-
device:
|
|
434
|
-
inode:
|
|
435
|
-
byteLength: bytes.byteLength,
|
|
341
|
+
device: read.dev.toString(),
|
|
342
|
+
inode: read.ino.toString(),
|
|
343
|
+
byteLength: read.bytes.byteLength,
|
|
436
344
|
sha256: sha256(content),
|
|
437
345
|
},
|
|
438
346
|
};
|
|
439
347
|
} catch {
|
|
440
348
|
throw new Error("derivation network guard file is unavailable or unsafe");
|
|
441
|
-
} finally {
|
|
442
|
-
if (descriptor !== null) {
|
|
443
|
-
try {
|
|
444
|
-
closeSync(descriptor);
|
|
445
|
-
} catch {
|
|
446
|
-
// Keep diagnostics categorical and path-free.
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
349
|
}
|
|
450
350
|
}
|
|
451
351
|
|
|
@@ -484,48 +384,20 @@ export function verifyGuardPrivateFile(
|
|
|
484
384
|
expected: GuardPrivateFileEvidence,
|
|
485
385
|
expectedContent: string,
|
|
486
386
|
): void {
|
|
487
|
-
let descriptor: number | null = null;
|
|
488
387
|
try {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
);
|
|
493
|
-
const before = fstatSync(descriptor, { bigint: true });
|
|
388
|
+
const read = readOwnedFileStableSync(path, expected.byteLength, {
|
|
389
|
+
exactMode: 0o600,
|
|
390
|
+
minimumBytes: 1n,
|
|
391
|
+
});
|
|
494
392
|
if (
|
|
495
|
-
|
|
496
|
-
||
|
|
497
|
-
||
|
|
498
|
-
||
|
|
499
|
-
|| before.ino.toString() !== expected.inode
|
|
500
|
-
|| before.size !== BigInt(expected.byteLength)
|
|
501
|
-
) throw new Error("derivation network guard file changed identity");
|
|
502
|
-
const bytes = Buffer.alloc(expected.byteLength);
|
|
503
|
-
let offset = 0;
|
|
504
|
-
while (offset < bytes.byteLength) {
|
|
505
|
-
const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null);
|
|
506
|
-
if (count === 0) throw new Error("derivation network guard file changed size");
|
|
507
|
-
offset += count;
|
|
508
|
-
}
|
|
509
|
-
const after = fstatSync(descriptor, { bigint: true });
|
|
510
|
-
if (
|
|
511
|
-
before.dev !== after.dev
|
|
512
|
-
|| before.ino !== after.ino
|
|
513
|
-
|| before.size !== after.size
|
|
514
|
-
|| before.mtimeNs !== after.mtimeNs
|
|
515
|
-
|| before.ctimeNs !== after.ctimeNs
|
|
516
|
-
|| bytes.toString("utf8") !== expectedContent
|
|
393
|
+
read.dev.toString() !== expected.device
|
|
394
|
+
|| read.ino.toString() !== expected.inode
|
|
395
|
+
|| read.bytes.byteLength !== expected.byteLength
|
|
396
|
+
|| read.bytes.toString("utf8") !== expectedContent
|
|
517
397
|
|| sha256(expectedContent) !== expected.sha256
|
|
518
|
-
) throw new Error("derivation network guard file changed
|
|
398
|
+
) throw new Error("derivation network guard file changed");
|
|
519
399
|
} catch {
|
|
520
400
|
throw new Error("derivation network guard file changed or is unavailable");
|
|
521
|
-
} finally {
|
|
522
|
-
if (descriptor !== null) {
|
|
523
|
-
try {
|
|
524
|
-
closeSync(descriptor);
|
|
525
|
-
} catch {
|
|
526
|
-
// Keep diagnostics categorical and path-free.
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
401
|
}
|
|
530
402
|
}
|
|
531
403
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import {
|
|
3
3
|
chmodSync,
|
|
4
|
-
closeSync,
|
|
5
|
-
constants,
|
|
6
|
-
fstatSync,
|
|
7
4
|
lstatSync,
|
|
8
|
-
openSync,
|
|
9
|
-
readSync,
|
|
10
5
|
rmdirSync,
|
|
11
6
|
unlinkSync,
|
|
12
7
|
} from "node:fs";
|
|
13
8
|
import { createServer, type Server, type Socket } from "node:net";
|
|
14
9
|
|
|
10
|
+
import {
|
|
11
|
+
assertOwnedPathSync,
|
|
12
|
+
readOwnedFileStableSync,
|
|
13
|
+
} from "@hraness/local-custody/private-paths";
|
|
14
|
+
|
|
15
15
|
import {
|
|
16
16
|
DERIVATION_GUARD_PROXY_CONFIG,
|
|
17
17
|
DERIVATION_GUARD_PROXY_READY,
|
|
@@ -39,11 +39,6 @@ function injectFailureForTest(stage: HelperFailureStage): void {
|
|
|
39
39
|
) throw new Error("injected derivation proxy helper failure");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function currentUserOwns(uid: number | bigint): boolean {
|
|
43
|
-
const current = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
44
|
-
return current === undefined || uid === (typeof uid === "bigint" ? BigInt(current) : current);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
42
|
function sameIdentity(left: GuardDirectoryIdentity, right: GuardDirectoryIdentity): boolean {
|
|
48
43
|
return left.device === right.device && left.inode === right.inode;
|
|
49
44
|
}
|
|
@@ -95,25 +90,23 @@ function provisionalReadyFileIsAbsent(): boolean {
|
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
function inspectPrivateDirectory(path: string): GuardDirectoryIdentity {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return { device: stats.dev.toString(), inode: stats.ino.toString() };
|
|
93
|
+
let identity: { readonly dev: number; readonly ino: number };
|
|
94
|
+
try {
|
|
95
|
+
identity = assertOwnedPathSync(path, { kind: "directory", exactMode: 0o700 });
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error("derivation proxy directory is unavailable or unsafe");
|
|
98
|
+
}
|
|
99
|
+
return { device: identity.dev.toString(), inode: identity.ino.toString() };
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
function inspectPrivateControlSocket(path: string): GuardDirectoryIdentity {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return { device: stats.dev.toString(), inode: stats.ino.toString() };
|
|
103
|
+
let identity: { readonly dev: number; readonly ino: number };
|
|
104
|
+
try {
|
|
105
|
+
identity = assertOwnedPathSync(path, { kind: "socket", exactMode: 0o600 });
|
|
106
|
+
} catch {
|
|
107
|
+
throw new Error("derivation proxy control socket is unavailable or unsafe");
|
|
108
|
+
}
|
|
109
|
+
return { device: identity.dev.toString(), inode: identity.ino.toString() };
|
|
117
110
|
}
|
|
118
111
|
|
|
119
112
|
function unlinkExactControlSocket(
|
|
@@ -147,38 +140,16 @@ function waitForSocketClose(socket: Socket): Promise<void> {
|
|
|
147
140
|
}
|
|
148
141
|
|
|
149
142
|
function readPrivateConfig(): unknown {
|
|
150
|
-
|
|
151
|
-
DERIVATION_GUARD_PROXY_CONFIG,
|
|
152
|
-
constants.O_RDONLY | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0),
|
|
153
|
-
);
|
|
143
|
+
let read: ReturnType<typeof readOwnedFileStableSync>;
|
|
154
144
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|| before.size > BigInt(MAX_CONFIG_BYTES)
|
|
162
|
-
) throw new Error("derivation proxy config is unavailable or unsafe");
|
|
163
|
-
const bytes = Buffer.alloc(Number(before.size));
|
|
164
|
-
let offset = 0;
|
|
165
|
-
while (offset < bytes.byteLength) {
|
|
166
|
-
const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null);
|
|
167
|
-
if (count === 0) throw new Error("derivation proxy config changed size");
|
|
168
|
-
offset += count;
|
|
169
|
-
}
|
|
170
|
-
const after = fstatSync(descriptor, { bigint: true });
|
|
171
|
-
if (
|
|
172
|
-
before.dev !== after.dev
|
|
173
|
-
|| before.ino !== after.ino
|
|
174
|
-
|| before.size !== after.size
|
|
175
|
-
|| before.mtimeNs !== after.mtimeNs
|
|
176
|
-
|| before.ctimeNs !== after.ctimeNs
|
|
177
|
-
) throw new Error("derivation proxy config changed while reading");
|
|
178
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) as unknown;
|
|
179
|
-
} finally {
|
|
180
|
-
closeSync(descriptor);
|
|
145
|
+
read = readOwnedFileStableSync(DERIVATION_GUARD_PROXY_CONFIG, MAX_CONFIG_BYTES, {
|
|
146
|
+
exactMode: 0o600,
|
|
147
|
+
minimumBytes: 1n,
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
throw new Error("derivation proxy config is unavailable or unsafe");
|
|
181
151
|
}
|
|
152
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(read.bytes)) as unknown;
|
|
182
153
|
}
|
|
183
154
|
|
|
184
155
|
function exactKeys(record: Record<string, unknown>, expected: readonly string[]): boolean {
|
package/src/derive.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "@hraness/kb/clip/acquire";
|
|
23
23
|
import { isPrivateAddress, isPrivateHostname } from "@hraness/kb/clip/network";
|
|
24
24
|
import type { StrictCookie } from "@hraness/kb/clip/cookies";
|
|
25
|
+
import { assertOwnedPathSync } from "@hraness/local-custody/private-paths";
|
|
25
26
|
import type { GhostgetAuth } from "./auth";
|
|
26
27
|
import {
|
|
27
28
|
agentBrowserFailure,
|
|
@@ -234,14 +235,14 @@ function ownedByCurrentUser(uid: number | bigint): boolean {
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
function inspectDirectoryIdentity(path: string): DirectoryIdentity {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return { device:
|
|
238
|
+
let identity: { readonly dev: number; readonly ino: number };
|
|
239
|
+
try {
|
|
240
|
+
identity = assertOwnedPathSync(path, { kind: "directory", exactMode: 0o700 });
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (hasErrorCode(error, "ENOENT")) throw error;
|
|
243
|
+
throw new Error("derivation session directory is unsafe", { cause: error });
|
|
244
|
+
}
|
|
245
|
+
return { device: identity.dev.toString(), inode: identity.ino.toString() };
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
function parseDirectoryIdentity(value: unknown): DirectoryIdentity {
|
|
@@ -1101,25 +1102,21 @@ function inspectPrivateFile(
|
|
|
1101
1102
|
maximumBytes: number,
|
|
1102
1103
|
label: string,
|
|
1103
1104
|
): Omit<PrivateFileEvidence, "sha256"> {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|| (stats.mode & 0o077n) !== 0n
|
|
1116
|
-
|| stats.size < 1n
|
|
1117
|
-
|| stats.size > BigInt(maximumBytes)
|
|
1118
|
-
) throw new Error(`${label} is unavailable or unsafe`);
|
|
1105
|
+
let identity: { readonly dev: number; readonly ino: number; readonly size: number };
|
|
1106
|
+
try {
|
|
1107
|
+
identity = assertOwnedPathSync(path, {
|
|
1108
|
+
kind: "file",
|
|
1109
|
+
ownerOnly: true,
|
|
1110
|
+
minimumBytes: 1n,
|
|
1111
|
+
maximumBytes: BigInt(maximumBytes),
|
|
1112
|
+
});
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
throw new Error(`${label} is unavailable or unsafe`, { cause: error });
|
|
1115
|
+
}
|
|
1119
1116
|
return {
|
|
1120
|
-
device:
|
|
1121
|
-
inode:
|
|
1122
|
-
byteLength:
|
|
1117
|
+
device: identity.dev.toString(),
|
|
1118
|
+
inode: identity.ino.toString(),
|
|
1119
|
+
byteLength: identity.size,
|
|
1123
1120
|
};
|
|
1124
1121
|
}
|
|
1125
1122
|
|
|
@@ -1444,18 +1441,20 @@ function inspectCapturedHar(path: string): {
|
|
|
1444
1441
|
readonly inode: string;
|
|
1445
1442
|
readonly byteLength: number;
|
|
1446
1443
|
} {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1444
|
+
let identity: { readonly dev: number; readonly ino: number; readonly size: number };
|
|
1445
|
+
try {
|
|
1446
|
+
identity = assertOwnedPathSync(path, {
|
|
1447
|
+
kind: "file",
|
|
1448
|
+
minimumBytes: 1n,
|
|
1449
|
+
maximumBytes: BigInt(MAX_HAR_BYTES),
|
|
1450
|
+
});
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
throw new Error("derivation review HAR is unavailable or unsafe", { cause: error });
|
|
1453
|
+
}
|
|
1455
1454
|
return {
|
|
1456
|
-
device:
|
|
1457
|
-
inode:
|
|
1458
|
-
byteLength:
|
|
1455
|
+
device: identity.dev.toString(),
|
|
1456
|
+
inode: identity.ino.toString(),
|
|
1457
|
+
byteLength: identity.size,
|
|
1459
1458
|
};
|
|
1460
1459
|
}
|
|
1461
1460
|
|
package/src/ghostget.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
saveAuth,
|
|
23
23
|
type GhostgetAuth,
|
|
24
24
|
} from "./auth";
|
|
25
|
+
import { runAccountsDeviceLogin, runAccountsSignOut } from "./accounts-auth";
|
|
25
26
|
import {
|
|
26
27
|
isPublicWebSessionInvocationAuthority,
|
|
27
28
|
type InvocationAuthority,
|
|
@@ -1983,6 +1984,48 @@ async function runCommand(
|
|
|
1983
1984
|
output.stdout(renderGhostgetUsage());
|
|
1984
1985
|
return 0;
|
|
1985
1986
|
}
|
|
1987
|
+
if (arguments_.command === "login") {
|
|
1988
|
+
const result = await runAccountsDeviceLogin(
|
|
1989
|
+
environment,
|
|
1990
|
+
{
|
|
1991
|
+
onUserCode: (userCode, verificationUri) => {
|
|
1992
|
+
output.stderr(
|
|
1993
|
+
`Open ${safe(verificationUri)} in a browser where you are already signed in to Hraness Accounts and enter code: ${safe(userCode)}\n`,
|
|
1994
|
+
);
|
|
1995
|
+
},
|
|
1996
|
+
onPending: () => {
|
|
1997
|
+
output.stderr("Waiting for browser approval...\n");
|
|
1998
|
+
},
|
|
1999
|
+
onSlowDown: () => {
|
|
2000
|
+
output.stderr("Polling more slowly...\n");
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
2003
|
+
undefined,
|
|
2004
|
+
signal,
|
|
2005
|
+
);
|
|
2006
|
+
if (arguments_.json) {
|
|
2007
|
+
output.stdout(exactTerminalJson({ ok: result.kind === "success", ...result }));
|
|
2008
|
+
} else if (result.kind === "success") {
|
|
2009
|
+
output.stdout(`${safe(result.message)}\n`);
|
|
2010
|
+
} else if (result.kind === "denied") {
|
|
2011
|
+
output.stderr("Login was denied in the browser.\n");
|
|
2012
|
+
} else if (result.kind === "expired") {
|
|
2013
|
+
output.stderr("The sign-in code expired. Run 'ghostget login' again.\n");
|
|
2014
|
+
} else {
|
|
2015
|
+
output.stderr(`Login failed: ${safe(result.message)}\n`);
|
|
2016
|
+
}
|
|
2017
|
+
return result.kind === "success" ? 0 : 3;
|
|
2018
|
+
}
|
|
2019
|
+
if (arguments_.command === "logout") {
|
|
2020
|
+
await runAccountsSignOut(environment);
|
|
2021
|
+
const message = "Signed out of Hraness Accounts.";
|
|
2022
|
+
if (arguments_.json) {
|
|
2023
|
+
output.stdout(exactTerminalJson({ ok: true, message }));
|
|
2024
|
+
} else {
|
|
2025
|
+
output.stdout(`${message}\n`);
|
|
2026
|
+
}
|
|
2027
|
+
return 0;
|
|
2028
|
+
}
|
|
1986
2029
|
if (arguments_.command === "whatsapp-automation-install") {
|
|
1987
2030
|
const { installReviewedWhatsAppAutomationBinary } = await import("./providers/whatsapp-automation-runtime");
|
|
1988
2031
|
const installed = arguments_.binary === undefined
|
|
@@ -153,8 +153,8 @@ const viewerEvidence = Object.freeze({
|
|
|
153
153
|
operationName: "Viewer",
|
|
154
154
|
operationType: "query" as const,
|
|
155
155
|
queryId: "9t128XgFic52jPUEkJMf6w",
|
|
156
|
-
sourceChunk: "main.
|
|
157
|
-
observedOn: "2026-09-
|
|
156
|
+
sourceChunk: "main.52fc4dd0aada586aa.js",
|
|
157
|
+
observedOn: "2026-09-17",
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
function isRecord(value: unknown): value is JsonRecord {
|
|
@@ -722,10 +722,10 @@ async function currentChunkText(bootstrap: XBootstrap, sourceChunk: string): Pro
|
|
|
722
722
|
}
|
|
723
723
|
|
|
724
724
|
const articleRichContractEvidence = Object.freeze({
|
|
725
|
-
uploader: "shared~bundle.LoggedInMain~ondemand.HoverCard~loader.AudioDock~loader.Dock~bundle.BookmarkFolders~bundle.Book.
|
|
726
|
-
entities: "shared~bundle.TwitterArticles~ondemand.Verified~bundle.SettingsExtendedProfile~bundle.WorkHistory.
|
|
727
|
-
converter: "shared~bundle.Grok~bundle.GrokDrawer~bundle.ReaderMode~bundle.Birdwatch~bundle.TwitterArticles~bundle.Compose.
|
|
728
|
-
observedOn: "2026-
|
|
725
|
+
uploader: "shared~bundle.LoggedInMain~ondemand.HoverCard~loader.AudioDock~loader.Dock~bundle.BookmarkFolders~bundle.Book.9549529d09fb73baa.js",
|
|
726
|
+
entities: "shared~bundle.TwitterArticles~ondemand.Verified~bundle.SettingsExtendedProfile~bundle.WorkHistory.f5f6edfb2fe9ac4ca.js",
|
|
727
|
+
converter: "shared~bundle.Grok~bundle.GrokDrawer~bundle.ReaderMode~bundle.Birdwatch~bundle.TwitterArticles~bundle.Compose.c851d723cd517177a.js",
|
|
728
|
+
observedOn: "2026-09-17",
|
|
729
729
|
});
|
|
730
730
|
|
|
731
731
|
function requireCurrentBundleTokens(text: string, tokens: readonly string[], label: string): void {
|
|
@@ -743,10 +743,10 @@ async function assertCurrentArticleRichContract(
|
|
|
743
743
|
currentChunkText(bootstrap, articleRichContractEvidence.converter),
|
|
744
744
|
]);
|
|
745
745
|
requireCurrentBundleTokens(entities, [
|
|
746
|
-
'createEntity(
|
|
746
|
+
'createEntity(E.Sg,"MUTABLE",{url:',
|
|
747
747
|
], "Article entity");
|
|
748
748
|
requireCurrentBundleTokens(converter, [
|
|
749
|
-
'mutability:s[
|
|
749
|
+
'mutability:s[i.mutability]',
|
|
750
750
|
'inline_style_ranges:',
|
|
751
751
|
], "Article content converter");
|
|
752
752
|
if (!includeImages) return;
|
|
@@ -767,12 +767,12 @@ async function assertCurrentArticleRichContract(
|
|
|
767
767
|
'TwitterArticle:"twitter_article"',
|
|
768
768
|
], "media uploader");
|
|
769
769
|
requireCurrentBundleTokens(entities, [
|
|
770
|
-
'createEntity(
|
|
771
|
-
'mediaCategory:
|
|
770
|
+
'createEntity(g.LA.MEDIA,g.Ei.IMMUTABLE',
|
|
771
|
+
'mediaCategory:K(e)',
|
|
772
772
|
'mediaId:e.uploadId',
|
|
773
773
|
], "Article entity");
|
|
774
774
|
requireCurrentBundleTokens(converter, [
|
|
775
|
-
'media_items:
|
|
775
|
+
'media_items:i.data?.mediaItems?.map',
|
|
776
776
|
'media_category:e.mediaCategory',
|
|
777
777
|
], "Article content converter");
|
|
778
778
|
}
|