borgmcp 4.4.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,26 @@
1
- import { appendFileSync, chmodSync, existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
- import { createHash } from 'crypto';
1
+ import {
2
+ closeSync,
3
+ constants,
4
+ existsSync,
5
+ fchmodSync,
6
+ fstatSync,
7
+ openSync,
8
+ readFileSync,
9
+ readSync,
10
+ renameSync,
11
+ unlinkSync,
12
+ writeFileSync,
13
+ writeSync,
14
+ } from 'fs';
15
+ import { createHash, randomUUID } from 'crypto';
3
16
  import { createServer } from 'node:net';
4
17
  import { join } from 'path';
5
18
  import { tmpdir } from 'os';
19
+ import {
20
+ borgConfigRoot,
21
+ ensurePrivateBorgConfigRoot,
22
+ ensurePrivateBorgConfigRootSync,
23
+ } from './private-root.js';
6
24
  import {
7
25
  OPENCODE_INJECTED_ENTRY_METADATA_KEY,
8
26
  OPENCODE_WAKE_IDENTITY_METADATA_KEY,
@@ -30,37 +48,115 @@ function stateIdentityDigest(current: OpenCodeDroneState): string {
30
48
  return createHash('sha256').update(key).digest('hex').slice(0, 24);
31
49
  }
32
50
 
33
- function diagnosticLogPath(owner: OpenCodeDroneState): string {
34
- const path = join(tmpdir(), `borg-opencode-drone-${stateIdentityDigest(owner)}.log`);
51
+ export function openCodeStartupDiagnosticLogPath(): string {
52
+ return join(borgConfigRoot(), 'opencode-drone-startup.log');
53
+ }
54
+
55
+ function diagnosticLogPath(owner: OpenCodeDroneState | null): string {
56
+ const root = borgConfigRoot();
57
+ const path = owner
58
+ ? join(root, `opencode-drone-${stateIdentityDigest(owner)}.log`)
59
+ : openCodeStartupDiagnosticLogPath();
35
60
  diagnosticLogPathsForTests.add(path);
36
61
  return path;
37
62
  }
38
63
 
39
- function log(msg: string, owner: OpenCodeDroneState | null = state) {
64
+ function log(msg: string, owner: OpenCodeDroneState | null = state, throwOnFailure = false) {
40
65
  const line = `[${new Date().toISOString()}] ${msg}\n`;
41
- if (!owner) {
42
- process.stderr.write(line);
43
- return;
44
- }
66
+ let descriptor: number | null = null;
67
+ let temporaryDescriptor: number | null = null;
68
+ let temporary: string | null = null;
45
69
  try {
70
+ ensurePrivateBorgConfigRootSync(borgConfigRoot());
46
71
  const path = diagnosticLogPath(owner);
47
- if (existsSync(path)) chmodSync(path, 0o600);
48
- appendFileSync(path, line, { encoding: 'utf8', mode: 0o600 });
49
- chmodSync(path, 0o600);
50
- if (statSync(path).size <= OPEN_CODE_DIAGNOSTIC_LOG_MAX_BYTES) return;
51
- const contents = readFileSync(path);
52
- const tail = contents.subarray(contents.length - OPEN_CODE_DIAGNOSTIC_LOG_MAX_BYTES);
53
- const firstNewline = tail.indexOf(0x0a);
54
- const bounded = firstNewline >= 0 ? tail.subarray(firstNewline + 1) : tail;
55
- const temporary = `${path}.${process.pid}.tmp`;
56
- writeFileSync(temporary, bounded, { mode: 0o600 });
72
+ // The private root is the primary boundary. Where available, no-follow also
73
+ // closes the final replacement gap between root verification and this open.
74
+ const noFollow = constants.O_NOFOLLOW ?? 0;
75
+ descriptor = openSync(
76
+ path,
77
+ constants.O_RDWR |
78
+ constants.O_APPEND |
79
+ constants.O_CREAT |
80
+ noFollow,
81
+ 0o600,
82
+ );
83
+ if (!fstatSync(descriptor).isFile()) {
84
+ throw Object.assign(new Error('OpenCode diagnostic log is not a regular file'), { code: 'EINVAL' });
85
+ }
86
+ fchmodSync(descriptor, 0o600);
87
+ writeSync(descriptor, line, null, 'utf8');
88
+
89
+ const size = fstatSync(descriptor).size;
90
+ if (size <= OPEN_CODE_DIAGNOSTIC_LOG_MAX_BYTES) return;
91
+ const tail = Buffer.allocUnsafe(OPEN_CODE_DIAGNOSTIC_LOG_MAX_BYTES);
92
+ let bytesRead = 0;
93
+ while (bytesRead < tail.length) {
94
+ const count = readSync(
95
+ descriptor,
96
+ tail,
97
+ bytesRead,
98
+ tail.length - bytesRead,
99
+ size - tail.length + bytesRead,
100
+ );
101
+ if (count === 0) break;
102
+ bytesRead += count;
103
+ }
104
+ const completeTail = tail.subarray(0, bytesRead);
105
+ const firstNewline = completeTail.indexOf(0x0a);
106
+ const bounded = firstNewline >= 0 ? completeTail.subarray(firstNewline + 1) : completeTail;
107
+
108
+ temporary = `${path}.${randomUUID()}.tmp`;
109
+ temporaryDescriptor = openSync(
110
+ temporary,
111
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow,
112
+ 0o600,
113
+ );
114
+ if (!fstatSync(temporaryDescriptor).isFile()) {
115
+ throw Object.assign(new Error('OpenCode diagnostic temporary is not a regular file'), { code: 'EINVAL' });
116
+ }
117
+ fchmodSync(temporaryDescriptor, 0o600);
118
+ let bytesWritten = 0;
119
+ while (bytesWritten < bounded.length) {
120
+ const count = writeSync(
121
+ temporaryDescriptor,
122
+ bounded,
123
+ bytesWritten,
124
+ bounded.length - bytesWritten,
125
+ );
126
+ if (count === 0) throw Object.assign(new Error('OpenCode diagnostic temporary write stalled'), { code: 'EIO' });
127
+ bytesWritten += count;
128
+ }
129
+ closeSync(temporaryDescriptor);
130
+ temporaryDescriptor = null;
131
+ closeSync(descriptor);
132
+ descriptor = null;
57
133
  renameSync(temporary, path);
134
+ temporary = null;
58
135
  } catch (error) {
59
136
  const code = (error as NodeJS.ErrnoException | null)?.code ?? 'unknown';
60
137
  process.stderr.write(`OpenCode diagnostic log write failed (${code})\n`);
138
+ if (throwOnFailure) throw error;
139
+ } finally {
140
+ if (temporaryDescriptor !== null) {
141
+ try { closeSync(temporaryDescriptor); } catch { /* The primary write error is already reported. */ }
142
+ }
143
+ if (descriptor !== null) {
144
+ try { closeSync(descriptor); } catch { /* The primary write error is already reported. */ }
145
+ }
146
+ if (temporary !== null) {
147
+ try {
148
+ unlinkSync(temporary);
149
+ } catch {
150
+ // Already absent or inaccessible; the randomized name cannot be reused.
151
+ }
152
+ }
61
153
  }
62
154
  }
63
155
 
156
+ export function writeOpenCodeStartupDiagnostic(message: string): void {
157
+ log(message, null, true);
158
+ }
159
+
64
160
  interface OpenCodeDroneState {
65
161
  serverUrl: string;
66
162
  apiPassword: string;
@@ -229,6 +325,7 @@ export async function connectOpenCodeDrone(deps: ConnectDeps): Promise<void> {
229
325
  if (!isOpenCode256BitIdentity(deps.apiPassword)) {
230
326
  throw new OpenCodeAuthenticationError('OpenCode API password is missing or unverifiable');
231
327
  }
328
+ await ensurePrivateBorgConfigRoot(borgConfigRoot());
232
329
  abandonOpenCodeDeliveries(state);
233
330
  state = {
234
331
  serverUrl: deps.serverUrl,
@@ -1,4 +1,4 @@
1
- import { lstatSync, realpathSync } from 'node:fs';
1
+ import fs, { lstatSync, realpathSync } from 'node:fs';
2
2
  import { chmod, lstat, mkdir } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, isAbsolute, join, resolve } from 'node:path';
@@ -100,3 +100,40 @@ export async function ensurePrivateBorgConfigRoot(root = borgConfigRoot()): Prom
100
100
  throw new Error('Borg private-state directory is not private');
101
101
  }
102
102
  }
103
+
104
+ /** Synchronous equivalent for startup-failure paths that must never await. */
105
+ export function ensurePrivateBorgConfigRootSync(root = borgConfigRoot()): void {
106
+ if (!isAbsolute(root) || resolve(root) !== root) {
107
+ throw new Error('Borg private-state directory path is not canonical');
108
+ }
109
+
110
+ let metadata: fs.Stats;
111
+ try {
112
+ metadata = fs.lstatSync(root);
113
+ } catch (error) {
114
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
115
+ fs.mkdirSync(root, { recursive: true, mode: 0o700 });
116
+ metadata = fs.lstatSync(root);
117
+ }
118
+
119
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
120
+ throw new Error('Borg private-state directory must be a real directory');
121
+ }
122
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
123
+ if (uid !== null && metadata.uid !== uid) {
124
+ throw new Error('Borg private-state directory is not owned by the current user');
125
+ }
126
+
127
+ const mode = metadata.mode & 0o777;
128
+ if ((mode & 0o022) !== 0) {
129
+ throw new Error('Borg private-state directory is writable by other users');
130
+ }
131
+ if (mode !== 0o700) {
132
+ fs.chmodSync(root, 0o700);
133
+ }
134
+
135
+ const final = fs.lstatSync(root);
136
+ if (!final.isDirectory() || (final.mode & 0o777) !== 0o700) {
137
+ throw new Error('Borg private-state directory is not private');
138
+ }
139
+ }
@@ -32,6 +32,8 @@
32
32
  * populated," so the assimilated path is the common case.
33
33
  */
34
34
 
35
+ import { escapeSyncDisplay } from './sync-roles-render.js';
36
+
35
37
  /**
36
38
  * Pure: compose the title string for a session. Exported so tests can
37
39
  * exercise every branch without TTY / process / fs dependencies.
@@ -46,9 +48,9 @@ export function composeTerminalTitle(
46
48
  repoBasename: string
47
49
  ): string {
48
50
  if (activeDrone) {
49
- return `borg · ${activeDrone.label} · ${activeDrone.cubeName}`;
51
+ return `borg · ${escapeSyncDisplay(activeDrone.label)} · ${escapeSyncDisplay(activeDrone.cubeName)}`;
50
52
  }
51
- return `borg · ${repoBasename}`;
53
+ return `borg · ${escapeSyncDisplay(repoBasename)}`;
52
54
  }
53
55
 
54
56
  /**