farming-code 2.2.22 → 2.2.23

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.
@@ -77,6 +77,15 @@ function sanitizeAgentEnv(env) {
77
77
  });
78
78
  }
79
79
 
80
+ function socketIdentity(socketPath) {
81
+ const stat = fs.statSync(socketPath, { bigint: true });
82
+ return { dev: stat.dev, ino: stat.ino };
83
+ }
84
+
85
+ function sameSocketIdentity(left, right) {
86
+ return Boolean(left && right && left.dev === right.dev && left.ino === right.ino);
87
+ }
88
+
80
89
  class NativePtyHost {
81
90
  constructor(options = {}) {
82
91
  this.configDir = options.configDir || process.env.FARMING_CONFIG_DIR || path.join(os.homedir(), '.farming');
@@ -84,6 +93,13 @@ class NativePtyHost {
84
93
  this.ownerPid = Number(options.ownerPid || process.env.FARMING_NATIVE_PTY_HOST_OWNER_PID || 0);
85
94
  this.runtimeIdentity = options.runtimeIdentity || HOST_RUNTIME_IDENTITY;
86
95
  this.exitOnShutdown = options.exitOnShutdown !== false;
96
+ this.boundSocketPath = process.platform === 'win32'
97
+ ? this.socketPath
98
+ : path.join(
99
+ path.dirname(this.socketPath),
100
+ `.fpty-${process.pid}-${crypto.createHash('sha256').update(this.socketPath).digest('hex').slice(0, 8)}-${crypto.randomBytes(4).toString('hex')}.sock`,
101
+ );
102
+ this.boundSocketIdentity = null;
87
103
  this.sessions = new Map();
88
104
  this.clients = new Set();
89
105
  this.ownerCheckTimer = null;
@@ -130,13 +146,22 @@ class NativePtyHost {
130
146
  this.server = net.createServer(socket => this.handleConnection(socket));
131
147
  await new Promise((resolve, reject) => {
132
148
  this.server.once('error', reject);
133
- this.server.listen(this.socketPath, () => {
149
+ this.server.listen(this.boundSocketPath, () => {
134
150
  this.server.off('error', reject);
135
151
  resolve();
136
152
  });
137
153
  });
138
154
  if (process.platform !== 'win32') {
139
- fs.chmodSync(this.socketPath, 0o600);
155
+ this.boundSocketIdentity = socketIdentity(this.boundSocketPath);
156
+ fs.chmodSync(this.boundSocketPath, 0o600);
157
+ try {
158
+ fs.linkSync(this.boundSocketPath, this.socketPath);
159
+ } catch (error) {
160
+ await new Promise(resolve => this.server.close(() => resolve()));
161
+ this.server = null;
162
+ this.boundSocketIdentity = null;
163
+ throw error;
164
+ }
140
165
  }
141
166
  console.error(`[${new Date().toISOString()}] Native PTY host listening on ${this.socketPath} ownerPid=${this.ownerPid || ''}`);
142
167
  this.startOwnerWatch();
@@ -173,6 +198,11 @@ class NativePtyHost {
173
198
  } catch (error) {
174
199
  if (!error || error.code !== 'ENOENT') throw error;
175
200
  }
201
+ try {
202
+ fs.unlinkSync(this.boundSocketPath);
203
+ } catch (error) {
204
+ if (!error || error.code !== 'ENOENT') throw error;
205
+ }
176
206
  }
177
207
 
178
208
  handleConnection(socket) {
@@ -1476,16 +1506,22 @@ class NativePtyHost {
1476
1506
  }
1477
1507
  }
1478
1508
  await this.screenWorkerPool.dispose();
1479
- if (this.server) {
1480
- await new Promise(resolve => this.server.close(() => resolve()));
1481
- this.server = null;
1482
- }
1483
- if (process.platform !== 'win32') {
1509
+ if (this.server && process.platform !== 'win32') {
1484
1510
  try {
1485
- fs.unlinkSync(this.socketPath);
1511
+ const currentSocketIdentity = socketIdentity(this.socketPath);
1512
+ if (sameSocketIdentity(currentSocketIdentity, this.boundSocketIdentity)) {
1513
+ // Unlink while this listener is still active. A competing host cannot
1514
+ // replace an active socket between the ownership check and unlink.
1515
+ fs.unlinkSync(this.socketPath);
1516
+ }
1486
1517
  } catch (error) {
1487
1518
  if (!error || error.code !== 'ENOENT') throw error;
1488
1519
  }
1520
+ this.boundSocketIdentity = null;
1521
+ }
1522
+ if (this.server) {
1523
+ await new Promise(resolve => this.server.close(() => resolve()));
1524
+ this.server = null;
1489
1525
  }
1490
1526
  }
1491
1527
  }