@gajae-code/coding-agent 0.11.6 → 0.11.7
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 +37 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +56 -6
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +5 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +19 -5
- package/dist/types/main.d.ts +24 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-status-header.d.ts +1 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -3
- package/dist/types/sdk/bus/telegram-daemon.d.ts +61 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +28 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +21 -7
- package/dist/types/session/internal/managed-session-storage.d.ts +10 -8
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +16 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +34 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +42 -9
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profiles.ts +18 -0
- package/src/config/settings-schema.ts +57 -6
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/ultragoal-guard.ts +88 -10
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +275 -12
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/internal-urls/local-protocol.ts +32 -6
- package/src/main.ts +86 -27
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +31 -3
- package/src/modes/components/tool-status-header.ts +6 -1
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/input-controller.ts +31 -0
- package/src/modes/controllers/selector-controller.ts +106 -12
- package/src/modes/interactive-mode.ts +16 -7
- package/src/modes/types.ts +1 -0
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/transports/http.ts +50 -16
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/index.ts +4 -5
- package/src/sdk/bus/lifecycle-commands.ts +16 -7
- package/src/sdk/bus/notification-service.ts +41 -18
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -3
- package/src/sdk/bus/telegram-daemon-control.ts +2 -2
- package/src/sdk/bus/telegram-daemon.ts +1663 -375
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +29 -5
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +240 -149
- package/src/session/blob-store.ts +19 -6
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +313 -57
- package/src/session/internal/managed-session-storage.ts +146 -35
- package/src/session/session-manager.ts +319 -85
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/tools/ask.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +301 -20
- package/src/tools/read.ts +2 -0
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +138 -27
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
openRecoveryFsRoot,
|
|
13
13
|
type RecoveryFsRoot,
|
|
14
14
|
renameNoReplacePath,
|
|
15
|
+
repairOwnerOnlyPathSecurityExpected,
|
|
15
16
|
snapshotDirectoryTree,
|
|
16
17
|
verifyOwnerOnlyFdSecurity,
|
|
17
18
|
verifyOwnerOnlyPathSecurity,
|
|
19
|
+
verifyOwnerOnlyPathSecurityExpected,
|
|
18
20
|
} from "@gajae-code/natives";
|
|
19
21
|
|
|
20
22
|
export const MANAGED_ARTIFACT_MAX_DEPTH = 32;
|
|
@@ -27,6 +29,8 @@ const LOCK_WAIT_MS = 5_000;
|
|
|
27
29
|
|
|
28
30
|
const LOCK_STALE_RECHECK_MS = 100;
|
|
29
31
|
|
|
32
|
+
export type ManagedSessionSecurityPolicy = "default" | "windows-existing-verify-first";
|
|
33
|
+
|
|
30
34
|
export type ManagedStorageFailure =
|
|
31
35
|
| "migration_busy"
|
|
32
36
|
| "binding_conflict"
|
|
@@ -216,14 +220,16 @@ export function retainManagedDirectoryAuthority(
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
function ensureDirectoryComponent(pathname: string):
|
|
223
|
+
function ensureDirectoryComponent(pathname: string): boolean {
|
|
224
|
+
let created = false;
|
|
220
225
|
try {
|
|
221
226
|
fs.mkdirSync(pathname, { mode: 0o700 });
|
|
227
|
+
created = true;
|
|
222
228
|
} catch (error) {
|
|
223
229
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
224
230
|
}
|
|
225
231
|
assertSafeDirectory(pathname);
|
|
226
|
-
|
|
232
|
+
return created;
|
|
227
233
|
}
|
|
228
234
|
|
|
229
235
|
export function assertManagedDirectoryRoot(root: ManagedDirectoryRoot): void {
|
|
@@ -263,10 +269,66 @@ function secure(pathname: string, kind: "directory" | "file"): void {
|
|
|
263
269
|
if (!verified.ok) throw securityError(pathname, verified);
|
|
264
270
|
}
|
|
265
271
|
|
|
272
|
+
function windowsExistingVerifyFirst(policy: ManagedSessionSecurityPolicy): boolean {
|
|
273
|
+
return process.platform === "win32" && policy === "windows-existing-verify-first";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function assertManagedPathIdentity(pathname: string, kind: "directory" | "file", expected: fs.BigIntStats): void {
|
|
277
|
+
const current = fs.lstatSync(pathname, { bigint: true });
|
|
278
|
+
if (current.isSymbolicLink()) throw new Error("reparse_point");
|
|
279
|
+
const expectedKind = kind === "directory" ? current.isDirectory() : current.isFile();
|
|
280
|
+
if (!expectedKind) throw new Error(kind === "directory" ? "not_directory" : "not_file");
|
|
281
|
+
if (current.dev !== expected.dev || current.ino !== expected.ino) throw new Error("identity_mismatch");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function verifyExistingManagedPathSecurity(
|
|
285
|
+
pathname: string,
|
|
286
|
+
kind: "directory" | "file",
|
|
287
|
+
expected: fs.BigIntStats,
|
|
288
|
+
): void {
|
|
289
|
+
const verified = validateNativeSecurityResult(
|
|
290
|
+
verifyOwnerOnlyPathSecurityExpected(pathname, kind, expected.dev, expected.ino),
|
|
291
|
+
"verify",
|
|
292
|
+
kind,
|
|
293
|
+
);
|
|
294
|
+
assertManagedPathIdentity(pathname, kind, expected);
|
|
295
|
+
if (!verified.ok) throw securityError(pathname, verified);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function secureExistingManagedDirectory(pathname: string, kind: "directory" | "file"): void {
|
|
299
|
+
const named = fs.lstatSync(pathname, { bigint: true });
|
|
300
|
+
const safeKind = kind === "directory" ? named.isDirectory() : named.isFile();
|
|
301
|
+
if (!safeKind || named.isSymbolicLink()) throw new Error(`Unsafe managed ${kind}: ${pathname}`);
|
|
302
|
+
const verified = validateNativeSecurityResult(
|
|
303
|
+
verifyOwnerOnlyPathSecurityExpected(pathname, kind, named.dev, named.ino),
|
|
304
|
+
"verify",
|
|
305
|
+
kind,
|
|
306
|
+
);
|
|
307
|
+
assertManagedPathIdentity(pathname, kind, named);
|
|
308
|
+
if (verified.ok) return;
|
|
309
|
+
if (verified.code !== "acl_verify_failed") throw securityError(pathname, verified);
|
|
310
|
+
const repaired = validateNativeSecurityResult(
|
|
311
|
+
repairOwnerOnlyPathSecurityExpected(pathname, kind, named.dev, named.ino),
|
|
312
|
+
"verify",
|
|
313
|
+
kind,
|
|
314
|
+
);
|
|
315
|
+
if (!repaired.ok) throw securityError(pathname, repaired);
|
|
316
|
+
assertManagedPathIdentity(pathname, kind, named);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function secureManagedDirectory(pathname: string, created: boolean, policy: ManagedSessionSecurityPolicy): void {
|
|
320
|
+
if (!created && windowsExistingVerifyFirst(policy)) {
|
|
321
|
+
secureExistingManagedDirectory(pathname, "directory");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
secure(pathname, "directory");
|
|
325
|
+
}
|
|
326
|
+
|
|
266
327
|
/** Internal retained-root capability for one managed descendant subtree. */
|
|
267
328
|
export class ManagedSessionDescendantStore {
|
|
268
329
|
readonly #root: ManagedDirectoryRoot;
|
|
269
330
|
readonly #baseDir: string;
|
|
331
|
+
readonly #policy: ManagedSessionSecurityPolicy;
|
|
270
332
|
readonly #authority: RecoveryFsRoot | undefined;
|
|
271
333
|
readonly #authorityBaseDir: string;
|
|
272
334
|
readonly #subtreeRoot: ManagedDirectoryRoot;
|
|
@@ -275,11 +337,13 @@ export class ManagedSessionDescendantStore {
|
|
|
275
337
|
root: ManagedDirectoryRoot,
|
|
276
338
|
baseDir: string,
|
|
277
339
|
retained?: { authority: RecoveryFsRoot; authorityBaseDir: string },
|
|
340
|
+
policy?: ManagedSessionSecurityPolicy,
|
|
278
341
|
) {
|
|
279
342
|
assertManagedDirectoryRoot(root);
|
|
280
343
|
managedRelativePath(root, baseDir);
|
|
281
344
|
this.#root = root;
|
|
282
345
|
this.#baseDir = path.resolve(baseDir);
|
|
346
|
+
this.#policy = policy ?? "default";
|
|
283
347
|
this.#authorityBaseDir = retained?.authorityBaseDir ?? this.#baseDir;
|
|
284
348
|
if (retained) {
|
|
285
349
|
const relative = path.relative(retained.authorityBaseDir, this.#baseDir).split(path.sep).join("/");
|
|
@@ -300,7 +364,7 @@ export class ManagedSessionDescendantStore {
|
|
|
300
364
|
this.#authority = retained.authority;
|
|
301
365
|
return;
|
|
302
366
|
}
|
|
303
|
-
ensureManagedDirectory(this.#baseDir, root);
|
|
367
|
+
ensureManagedDirectory(this.#baseDir, root, this.#policy);
|
|
304
368
|
const subtreeStat = fs.lstatSync(this.#baseDir, { bigint: true });
|
|
305
369
|
this.#subtreeRoot = Object.freeze({ canonicalPath: this.#baseDir, dev: subtreeStat.dev, ino: subtreeStat.ino });
|
|
306
370
|
if (process.platform === "linux") {
|
|
@@ -332,19 +396,28 @@ export class ManagedSessionDescendantStore {
|
|
|
332
396
|
return this.#subtreeRoot;
|
|
333
397
|
}
|
|
334
398
|
|
|
399
|
+
get securityPolicy(): ManagedSessionSecurityPolicy {
|
|
400
|
+
return this.#policy;
|
|
401
|
+
}
|
|
402
|
+
|
|
335
403
|
deriveSubtree(relativePath: string): ManagedSessionDescendantStore {
|
|
336
404
|
const child = this.ensureDirectory(relativePath);
|
|
337
405
|
const resolved = this.#resolve(relativePath);
|
|
338
|
-
if (!this.#authority) return new ManagedSessionDescendantStore(this.#root, resolved);
|
|
406
|
+
if (!this.#authority) return new ManagedSessionDescendantStore(this.#root, resolved, undefined, this.#policy);
|
|
339
407
|
const retainedChild = this.#authority.retainManagedDirectory(
|
|
340
408
|
this.#relative(resolved),
|
|
341
409
|
child.dev.toString(),
|
|
342
410
|
child.ino.toString(),
|
|
343
411
|
);
|
|
344
|
-
return new ManagedSessionDescendantStore(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
412
|
+
return new ManagedSessionDescendantStore(
|
|
413
|
+
this.#root,
|
|
414
|
+
resolved,
|
|
415
|
+
{
|
|
416
|
+
authority: retainedChild,
|
|
417
|
+
authorityBaseDir: resolved,
|
|
418
|
+
},
|
|
419
|
+
this.#policy,
|
|
420
|
+
);
|
|
348
421
|
}
|
|
349
422
|
|
|
350
423
|
retainAuthority(): RecoveryFsRoot | undefined {
|
|
@@ -371,12 +444,19 @@ export class ManagedSessionDescendantStore {
|
|
|
371
444
|
return;
|
|
372
445
|
}
|
|
373
446
|
} else {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
447
|
+
const named = fs.lstatSync(this.#baseDir, { bigint: true });
|
|
448
|
+
if (!named.isDirectory() || named.isSymbolicLink())
|
|
449
|
+
throw new Error(`Unsafe managed directory: ${this.#baseDir}`);
|
|
450
|
+
if (windowsExistingVerifyFirst(this.#policy))
|
|
451
|
+
verifyExistingManagedPathSecurity(this.#baseDir, "directory", named);
|
|
452
|
+
else {
|
|
453
|
+
const verified = validateNativeSecurityResult(
|
|
454
|
+
verifyOwnerOnlyPathSecurity(this.#baseDir, "directory"),
|
|
455
|
+
"verify",
|
|
456
|
+
"directory",
|
|
457
|
+
);
|
|
458
|
+
if (!verified.ok) throw securityError(this.#baseDir, verified);
|
|
459
|
+
}
|
|
380
460
|
}
|
|
381
461
|
this.#assertBound();
|
|
382
462
|
}
|
|
@@ -421,7 +501,7 @@ export class ManagedSessionDescendantStore {
|
|
|
421
501
|
ino: BigInt(ensured.identity.ino),
|
|
422
502
|
});
|
|
423
503
|
}
|
|
424
|
-
ensureManagedDirectory(this.#resolve(relativePath), this.#root);
|
|
504
|
+
ensureManagedDirectory(this.#resolve(relativePath), this.#root, this.#policy);
|
|
425
505
|
const named = fs.lstatSync(this.#resolve(relativePath), { bigint: true });
|
|
426
506
|
return Object.freeze({ canonicalPath: this.#resolve(relativePath), dev: named.dev, ino: named.ino });
|
|
427
507
|
}
|
|
@@ -434,13 +514,13 @@ export class ManagedSessionDescendantStore {
|
|
|
434
514
|
this.#assertBound();
|
|
435
515
|
return;
|
|
436
516
|
}
|
|
437
|
-
await publishManagedFileNoReplace(resolved, bytes, undefined, this.#root);
|
|
517
|
+
await publishManagedFileNoReplace(resolved, bytes, undefined, this.#root, this.#policy);
|
|
438
518
|
}
|
|
439
519
|
|
|
440
520
|
publishNoReplaceSync(relativePath: string, bytes: Uint8Array): void {
|
|
441
521
|
const resolved = this.#resolve(relativePath);
|
|
442
522
|
if (!this.#authority) {
|
|
443
|
-
publishManagedFileNoReplaceSync(resolved, bytes, this.#root);
|
|
523
|
+
publishManagedFileNoReplaceSync(resolved, bytes, this.#root, this.#policy);
|
|
444
524
|
this.#assertBound();
|
|
445
525
|
return;
|
|
446
526
|
}
|
|
@@ -453,7 +533,7 @@ export class ManagedSessionDescendantStore {
|
|
|
453
533
|
this.#assertBound();
|
|
454
534
|
const resolved = this.#resolve(relativePath);
|
|
455
535
|
if (!this.#authority) {
|
|
456
|
-
await replaceManagedFile(resolved, bytes, this.#subtreeRoot);
|
|
536
|
+
await replaceManagedFile(resolved, bytes, this.#subtreeRoot, this.#policy);
|
|
457
537
|
this.#assertBound();
|
|
458
538
|
return;
|
|
459
539
|
}
|
|
@@ -466,7 +546,7 @@ export class ManagedSessionDescendantStore {
|
|
|
466
546
|
this.#assertBound();
|
|
467
547
|
const resolved = this.#resolve(relativePath);
|
|
468
548
|
if (!this.#authority) {
|
|
469
|
-
replaceManagedFileSync(resolved, bytes, this.#subtreeRoot);
|
|
549
|
+
replaceManagedFileSync(resolved, bytes, this.#subtreeRoot, this.#policy);
|
|
470
550
|
this.#assertBound();
|
|
471
551
|
return;
|
|
472
552
|
}
|
|
@@ -1060,25 +1140,43 @@ function sameFileIdentity(left: fs.BigIntStats, right: fs.BigIntStats): boolean
|
|
|
1060
1140
|
}
|
|
1061
1141
|
|
|
1062
1142
|
/** Create a managed directory and fail closed unless its owner-only mode/ACL verifies. */
|
|
1063
|
-
export function ensureManagedDirectory(
|
|
1143
|
+
export function ensureManagedDirectory(
|
|
1144
|
+
pathname: string,
|
|
1145
|
+
root?: ManagedDirectoryRoot,
|
|
1146
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1147
|
+
): void {
|
|
1064
1148
|
if (!root) {
|
|
1149
|
+
let existed = false;
|
|
1150
|
+
try {
|
|
1151
|
+
const named = fs.lstatSync(pathname);
|
|
1152
|
+
existed = true;
|
|
1153
|
+
if (!named.isDirectory() || named.isSymbolicLink()) throw new Error(`Unsafe managed directory: ${pathname}`);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
1156
|
+
}
|
|
1065
1157
|
fs.mkdirSync(pathname, { recursive: true, mode: 0o700 });
|
|
1066
1158
|
assertSafeDirectory(pathname);
|
|
1067
|
-
|
|
1159
|
+
if (existed && windowsExistingVerifyFirst(policy)) secureExistingManagedDirectory(pathname, "directory");
|
|
1160
|
+
else secure(pathname, "directory");
|
|
1068
1161
|
return;
|
|
1069
1162
|
}
|
|
1070
1163
|
ensureManagedRoot(root);
|
|
1164
|
+
const components = managedRelativePath(root, pathname);
|
|
1165
|
+
if (components.length === 0) {
|
|
1166
|
+
if (windowsExistingVerifyFirst(policy)) secureExistingManagedDirectory(root.canonicalPath, "directory");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1071
1169
|
let current = root.canonicalPath;
|
|
1072
|
-
|
|
1170
|
+
if (windowsExistingVerifyFirst(policy)) secureExistingManagedDirectory(current, "directory");
|
|
1171
|
+
else secure(current, "directory");
|
|
1172
|
+
for (const component of components) {
|
|
1073
1173
|
// Re-inspect the captured root or already-secured descendant before every
|
|
1074
1174
|
// descent; a replaced component cannot grant authority to the next one.
|
|
1075
1175
|
if (current === root.canonicalPath) assertManagedDirectoryRoot(root);
|
|
1076
|
-
else
|
|
1077
|
-
assertSafeDirectory(current);
|
|
1078
|
-
secure(current, "directory");
|
|
1079
|
-
}
|
|
1176
|
+
else assertSafeDirectory(current);
|
|
1080
1177
|
current = path.join(current, component);
|
|
1081
|
-
ensureDirectoryComponent(current);
|
|
1178
|
+
const created = ensureDirectoryComponent(current);
|
|
1179
|
+
secureManagedDirectory(current, created, policy);
|
|
1082
1180
|
}
|
|
1083
1181
|
}
|
|
1084
1182
|
|
|
@@ -1123,9 +1221,10 @@ export async function publishManagedFileNoReplace(
|
|
|
1123
1221
|
bytes: Uint8Array,
|
|
1124
1222
|
assertOwned?: () => void,
|
|
1125
1223
|
root?: ManagedDirectoryRoot,
|
|
1224
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1126
1225
|
): Promise<void> {
|
|
1127
1226
|
const parent = path.dirname(destination);
|
|
1128
|
-
ensureManagedDirectory(parent, root);
|
|
1227
|
+
ensureManagedDirectory(parent, root, policy);
|
|
1129
1228
|
const staging = path.join(parent, `.${path.basename(destination)}.${randomUUID()}.staging`);
|
|
1130
1229
|
let fd: number | undefined;
|
|
1131
1230
|
let stagingIdentity: { dev: bigint; ino: bigint } | undefined;
|
|
@@ -1208,9 +1307,10 @@ export function publishManagedFileNoReplaceSync(
|
|
|
1208
1307
|
destination: string,
|
|
1209
1308
|
bytes: Uint8Array,
|
|
1210
1309
|
root?: ManagedDirectoryRoot,
|
|
1310
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1211
1311
|
): void {
|
|
1212
1312
|
const parent = path.dirname(destination);
|
|
1213
|
-
ensureManagedDirectory(parent, root);
|
|
1313
|
+
ensureManagedDirectory(parent, root, policy);
|
|
1214
1314
|
const staging = path.join(parent, `.${path.basename(destination)}.${randomUUID()}.staging`);
|
|
1215
1315
|
let fd: number | undefined;
|
|
1216
1316
|
let stagingIdentity: { dev: bigint; ino: bigint } | undefined;
|
|
@@ -1291,9 +1391,14 @@ export function publishManagedFileNoReplaceSync(
|
|
|
1291
1391
|
}
|
|
1292
1392
|
|
|
1293
1393
|
/** Replace one managed regular file while retaining the secured staging fd through publication. */
|
|
1294
|
-
export function replaceManagedFileSync(
|
|
1394
|
+
export function replaceManagedFileSync(
|
|
1395
|
+
destination: string,
|
|
1396
|
+
bytes: Uint8Array,
|
|
1397
|
+
root: ManagedDirectoryRoot,
|
|
1398
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1399
|
+
): void {
|
|
1295
1400
|
const parent = path.dirname(destination);
|
|
1296
|
-
ensureManagedDirectory(parent, root);
|
|
1401
|
+
ensureManagedDirectory(parent, root, policy);
|
|
1297
1402
|
const staging = path.join(parent, `.${path.basename(destination)}.${randomUUID()}.replacement`);
|
|
1298
1403
|
let fd: number | undefined;
|
|
1299
1404
|
let stagedIdentity: { dev: bigint; ino: bigint } | undefined;
|
|
@@ -1338,8 +1443,9 @@ export async function replaceManagedFile(
|
|
|
1338
1443
|
destination: string,
|
|
1339
1444
|
bytes: Uint8Array,
|
|
1340
1445
|
root: ManagedDirectoryRoot,
|
|
1446
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1341
1447
|
): Promise<void> {
|
|
1342
|
-
replaceManagedFileSync(destination, bytes, root);
|
|
1448
|
+
replaceManagedFileSync(destination, bytes, root, policy);
|
|
1343
1449
|
}
|
|
1344
1450
|
|
|
1345
1451
|
/** Copy the exact bytes captured from one no-follow source descriptor. */
|
|
@@ -1348,11 +1454,12 @@ export async function copyManagedFileNoReplace(
|
|
|
1348
1454
|
destination: string,
|
|
1349
1455
|
snapshot = captureManagedFileNoFollow(source),
|
|
1350
1456
|
root?: ManagedDirectoryRoot,
|
|
1457
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1351
1458
|
): Promise<void> {
|
|
1352
1459
|
const named = captureManagedFileNoFollow(source);
|
|
1353
1460
|
if (!sameIdentity(snapshot.identity, named.identity) || !snapshot.bytes.equals(named.bytes))
|
|
1354
1461
|
throw new Error("source_changed");
|
|
1355
|
-
await publishManagedFileNoReplace(destination, snapshot.bytes, undefined, root);
|
|
1462
|
+
await publishManagedFileNoReplace(destination, snapshot.bytes, undefined, root, policy);
|
|
1356
1463
|
const destinationSnapshot = captureManagedFileNoFollow(destination);
|
|
1357
1464
|
if (!destinationSnapshot.bytes.equals(snapshot.bytes)) throw new Error("durability_failed");
|
|
1358
1465
|
}
|
|
@@ -1362,8 +1469,9 @@ export async function acquireManagedLock(
|
|
|
1362
1469
|
locksDirectory: string,
|
|
1363
1470
|
name: string,
|
|
1364
1471
|
root?: ManagedDirectoryRoot,
|
|
1472
|
+
policy: ManagedSessionSecurityPolicy = "default",
|
|
1365
1473
|
): Promise<ManagedStorageLock> {
|
|
1366
|
-
ensureManagedDirectory(locksDirectory, root);
|
|
1474
|
+
ensureManagedDirectory(locksDirectory, root, policy);
|
|
1367
1475
|
const lockPath = path.join(locksDirectory, `${name}.lock`);
|
|
1368
1476
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
1369
1477
|
let staleObservedAt: number | undefined;
|
|
@@ -1513,7 +1621,10 @@ export function fsyncManagedArtifactTree(root: string): NativeDirectoryTreeSnaps
|
|
|
1513
1621
|
const stat = fs.lstatSync(pathname);
|
|
1514
1622
|
if (stat.isSymbolicLink()) throw new Error("unsafe_artifacts");
|
|
1515
1623
|
if (stat.isFile()) {
|
|
1516
|
-
const fd = fs.openSync(
|
|
1624
|
+
const fd = fs.openSync(
|
|
1625
|
+
pathname,
|
|
1626
|
+
(process.platform === "win32" ? fs.constants.O_WRONLY : fs.constants.O_RDONLY) | fs.constants.O_NOFOLLOW,
|
|
1627
|
+
);
|
|
1517
1628
|
try {
|
|
1518
1629
|
fs.fsyncSync(fd);
|
|
1519
1630
|
} finally {
|