mirror of https://github.com/garrytan/gstack.git
fix: browse server lock fails when .gstack/ dir missing
acquireServerLock() tried to create a lock file in .gstack/browse.json.lock but ensureStateDir() was only called inside startServer() — after lock acquisition. When .gstack/ didn't exist, openSync threw ENOENT, the catch returned null, and every invocation thought another process held the lock. Fix: call ensureStateDir() before acquireServerLock() in ensureServer(). Also skip DNS rebinding resolution for localhost/private IPs to eliminate unnecessary latency in concurrent E2E test sessions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
328ec924a2
commit
9d7fb1c3c2
|
|
@ -262,6 +262,9 @@ async function ensureServer(): Promise<ServerState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure state directory exists before lock acquisition (lock file lives there)
|
||||||
|
ensureStateDir(config);
|
||||||
|
|
||||||
// Acquire lock to prevent concurrent restart races (TOCTOU)
|
// Acquire lock to prevent concurrent restart races (TOCTOU)
|
||||||
const releaseLock = acquireServerLock();
|
const releaseLock = acquireServerLock();
|
||||||
if (!releaseLock) {
|
if (!releaseLock) {
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,12 @@ export async function validateNavigationUrl(url: string): Promise<void> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS rebinding protection: resolve hostname and check if it points to metadata IPs
|
// DNS rebinding protection: resolve hostname and check if it points to metadata IPs.
|
||||||
if (await resolvesToBlockedIp(hostname)) {
|
// Skip for loopback/private IPs — they can't be DNS-rebinded and the async DNS
|
||||||
|
// resolution adds latency that breaks concurrent E2E tests under load.
|
||||||
|
const isLoopback = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
||||||
|
const isPrivateNet = /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/.test(hostname);
|
||||||
|
if (!isLoopback && !isPrivateNet && await resolvesToBlockedIp(hostname)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Blocked: ${parsed.hostname} resolves to a cloud metadata IP. Possible DNS rebinding attack.`
|
`Blocked: ${parsed.hostname} resolves to a cloud metadata IP. Possible DNS rebinding attack.`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue