@cocorograph/hub-agent 0.8.28 → 0.8.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.8.28",
3
+ "version": "0.8.29",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの Claude / Codex チャットと tmux セッションを中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -59,7 +59,7 @@
59
59
  */
60
60
 
61
61
  import { watch } from "node:fs"
62
- import { mkdir, readFile, writeFile, rename, unlink, readdir } from "node:fs/promises"
62
+ import { chmod, lstat, mkdir, readFile, writeFile, rename, unlink, readdir } from "node:fs/promises"
63
63
  import path from "node:path"
64
64
 
65
65
  import { verifySiblingTokenAny, siblingTokenCwd } from "./sibling-token.mjs"
@@ -268,9 +268,46 @@ export class SiblingInjectBridge {
268
268
  return { claudeBridge: this.claudeBridge, codexBridge: this.codexBridge }
269
269
  }
270
270
 
271
+ async _lstatDir() {
272
+ return lstat(this.dir)
273
+ }
274
+
275
+ /** IPC ディレクトリを所有者限定にし、安全性を確認できなければ例外で閉じる。 */
276
+ async _prepareDir() {
277
+ try {
278
+ await this._lstatDir()
279
+ } catch (err) {
280
+ if (err?.code !== "ENOENT") throw err
281
+ await mkdir(this.dir, { recursive: true, mode: 0o700 })
282
+ }
283
+
284
+ const info = await this._lstatDir()
285
+ if (info.isSymbolicLink()) {
286
+ throw new Error("IPC directory must not be a symbolic link")
287
+ }
288
+ if (!info.isDirectory()) {
289
+ throw new Error("IPC path is not a directory")
290
+ }
291
+ const uid = process.getuid?.()
292
+ if (uid == null || info.uid !== uid) {
293
+ throw new Error("IPC directory is not owned by the current user")
294
+ }
295
+ if ((info.mode & 0o777) !== 0o700) {
296
+ await chmod(this.dir, 0o700)
297
+ }
298
+ }
299
+
271
300
  /** 監視ディレクトリを作成し、既存要求を sweep してから fs.watch を張る。 */
272
301
  async start() {
273
- await mkdir(this.dir, { recursive: true })
302
+ try {
303
+ await this._prepareDir()
304
+ } catch (err) {
305
+ this.logger?.warn(
306
+ { err: err?.message, dir: this.dir },
307
+ "sibling-inject IPC directory is unsafe, IPC disabled",
308
+ )
309
+ return
310
+ }
274
311
  await this._sweep()
275
312
  try {
276
313
  this._watcher = watch(this.dir, (_event, filename) => {
@@ -640,7 +677,7 @@ export class SiblingInjectBridge {
640
677
  const fp = path.join(this.dir, `${request_id}.result.json`)
641
678
  const tmp = `${fp}.tmp`
642
679
  try {
643
- await writeFile(tmp, JSON.stringify(result))
680
+ await writeFile(tmp, JSON.stringify(result), { mode: 0o600 })
644
681
  await rename(tmp, fp)
645
682
  } catch (err) {
646
683
  this.logger?.warn({ err: err?.message, request_id }, "sibling-inject result write failed")