git-watchtower 1.6.0 → 1.6.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.
- package/bin/git-watchtower.js +40 -7
- package/package.json +1 -1
package/bin/git-watchtower.js
CHANGED
|
@@ -676,17 +676,27 @@ function stopServerProcess() {
|
|
|
676
676
|
|
|
677
677
|
addLog('Stopping server...', 'update');
|
|
678
678
|
|
|
679
|
+
// Capture reference before nulling — needed for deferred SIGKILL
|
|
680
|
+
const proc = serverProcess;
|
|
681
|
+
|
|
679
682
|
// Try graceful shutdown first
|
|
680
683
|
if (process.platform === 'win32') {
|
|
681
|
-
spawn('taskkill', ['/pid',
|
|
684
|
+
spawn('taskkill', ['/pid', proc.pid.toString(), '/f', '/t']);
|
|
682
685
|
} else {
|
|
683
|
-
|
|
684
|
-
// Force kill after
|
|
685
|
-
setTimeout(() => {
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
proc.kill('SIGTERM');
|
|
687
|
+
// Force kill after grace period if process hasn't exited
|
|
688
|
+
const forceKillTimeout = setTimeout(() => {
|
|
689
|
+
try {
|
|
690
|
+
proc.kill('SIGKILL');
|
|
691
|
+
} catch (e) {
|
|
692
|
+
// Process may already be dead
|
|
688
693
|
}
|
|
689
694
|
}, 3000);
|
|
695
|
+
|
|
696
|
+
// Clear the force-kill timer if the process exits cleanly
|
|
697
|
+
proc.once('close', () => {
|
|
698
|
+
clearTimeout(forceKillTimeout);
|
|
699
|
+
});
|
|
690
700
|
}
|
|
691
701
|
|
|
692
702
|
serverProcess = null;
|
|
@@ -796,15 +806,28 @@ const LIVE_RELOAD_SCRIPT = `
|
|
|
796
806
|
// Utility Functions
|
|
797
807
|
// ============================================================================
|
|
798
808
|
|
|
809
|
+
// Default timeout for execAsync (30 seconds) — prevents hung git/CLI commands
|
|
810
|
+
// from permanently blocking the polling loop
|
|
811
|
+
const EXEC_ASYNC_TIMEOUT = 30000;
|
|
812
|
+
|
|
799
813
|
function execAsync(command, options = {}) {
|
|
814
|
+
const { timeout = EXEC_ASYNC_TIMEOUT, ...restOptions } = options;
|
|
800
815
|
return new Promise((resolve, reject) => {
|
|
801
|
-
exec(command, { cwd: PROJECT_ROOT, ...
|
|
816
|
+
const child = exec(command, { cwd: PROJECT_ROOT, timeout, ...restOptions }, (error, stdout, stderr) => {
|
|
802
817
|
if (error) {
|
|
803
818
|
reject({ error, stdout, stderr });
|
|
804
819
|
} else {
|
|
805
820
|
resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
|
806
821
|
}
|
|
807
822
|
});
|
|
823
|
+
// Also kill the child if the timeout fires (exec timeout sends SIGTERM
|
|
824
|
+
// but doesn't guarantee cleanup of process trees)
|
|
825
|
+
if (timeout > 0) {
|
|
826
|
+
const killTimer = setTimeout(() => {
|
|
827
|
+
try { child.kill('SIGKILL'); } catch (e) { /* already dead */ }
|
|
828
|
+
}, timeout + 5000);
|
|
829
|
+
child.on('close', () => clearTimeout(killTimer));
|
|
830
|
+
}
|
|
808
831
|
});
|
|
809
832
|
}
|
|
810
833
|
|
|
@@ -2010,6 +2033,16 @@ const server = http.createServer((req, res) => {
|
|
|
2010
2033
|
pathname = path.normalize(pathname).replace(/^(\.\.[\/\\])+/, '');
|
|
2011
2034
|
let filePath = path.join(STATIC_DIR, pathname);
|
|
2012
2035
|
|
|
2036
|
+
// Security: ensure resolved path stays within STATIC_DIR to prevent path traversal
|
|
2037
|
+
const resolvedPath = path.resolve(filePath);
|
|
2038
|
+
const resolvedStaticDir = path.resolve(STATIC_DIR);
|
|
2039
|
+
if (!resolvedPath.startsWith(resolvedStaticDir + path.sep) && resolvedPath !== resolvedStaticDir) {
|
|
2040
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
2041
|
+
res.end('<h1>403 Forbidden</h1>');
|
|
2042
|
+
addServerLog(`GET ${logPath} → 403 (path traversal blocked)`, true);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2013
2046
|
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
2014
2047
|
filePath = path.join(filePath, 'index.html');
|
|
2015
2048
|
}
|
package/package.json
CHANGED