Description
On macOS, background profile teardown selects which Chromium processes to kill
with a substring test on the ps command line, so it also matches unrelated
profiles.
terminateProfile — src/browser/runtime/local-cloak/darwin-background-launch.ts:42-50:
const needle = `--user-data-dir=${userDataDir}`;
for (const line of stdout.split('\n')) {
if (!line.includes(needle)) continue;
const pid = Number.parseInt(line.trim().split(/\s+/, 1)[0], 10);
if (Number.isInteger(pid) && pid !== process.pid) process.kill(pid, 'SIGTERM');
}
Profiles are siblings under ~/.webcmd/cloak/profiles/<id> (profiles.ts:23) and
ids are validated only by character class, /^[A-Za-z0-9._-]+$/, so hyphens and
dots are allowed. --user-data-dir=…/profiles/work is therefore a substring of
--user-data-dir=…/profiles/work-2, and tearing down work sends SIGTERM to the
browser serving work-2. Prefix-sharing names are ordinary: default /
default-2, actor / actor-profile, test / test.old.
There is also no check that the matched process belongs to webcmd, so a user's own
Chrome started with a matching --user-data-dir is killed too.
Two things make this easy to hit rather than exotic:
background is the default window mode (execution.ts:536-540), so on macOS
every browser-backed command goes through this launcher unless --window foreground is passed.
terminateProfile runs both on normal context close
(darwin-background-launch.ts:112) and on the launch-failure cleanup path
(darwin-background-launch.ts:118), so one profile failing to start is enough to
kill a healthy one.
The victim gets no useful signal — its next command fails with the generic
Target page, context or browser has been closed, which is likely why this has
gone unnoticed.
session-manager.ts:603-668 already solves exactly this problem for lock recovery:
commandUsesProfileDir requires the directory to end at an argument boundary,
profileDirAliases resolves symlinks, and isCloakBrowserCommand restricts the
match to CloakBrowser binaries. terminateProfile uses none of it.
Steps to Reproduce
- On macOS in local mode, start background browsers under two profiles whose
names share a prefix:
webcmd --profile work github whoami
webcmd --profile work-2 github whoami
- Confirm both are live:
ps -axo pid=,command= | grep -c -- '--user-data-dir=.*/cloak/profiles/work'
# 2
- Cause the
work profile's context to close or its launch to fail — for example
quit its browser and re-run a work command so the launch-failure cleanup at
darwin-background-launch.ts:118 runs.
- Run any command under
work-2. Its browser is gone and the command fails with
Target page, context or browser has been closed.
Expected Behavior
Tearing down work signals only the browser webcmd started for work. A profile
whose directory merely shares a prefix, and any browser webcmd did not start, are
left running.
webcmd Version
0.5.3
Node.js Version
22.x
Operating System
macOS
Logs / Screenshots
Description
On macOS, background profile teardown selects which Chromium processes to kill
with a substring test on the
pscommand line, so it also matches unrelatedprofiles.
terminateProfile—src/browser/runtime/local-cloak/darwin-background-launch.ts:42-50:Profiles are siblings under
~/.webcmd/cloak/profiles/<id>(profiles.ts:23) andids are validated only by character class,
/^[A-Za-z0-9._-]+$/, so hyphens anddots are allowed.
--user-data-dir=…/profiles/workis therefore a substring of--user-data-dir=…/profiles/work-2, and tearing downworksendsSIGTERMto thebrowser serving
work-2. Prefix-sharing names are ordinary:default/default-2,actor/actor-profile,test/test.old.There is also no check that the matched process belongs to webcmd, so a user's own
Chrome started with a matching
--user-data-diris killed too.Two things make this easy to hit rather than exotic:
backgroundis the default window mode (execution.ts:536-540), so on macOSevery browser-backed command goes through this launcher unless
--window foregroundis passed.terminateProfileruns both on normal context close(
darwin-background-launch.ts:112) and on the launch-failure cleanup path(
darwin-background-launch.ts:118), so one profile failing to start is enough tokill a healthy one.
The victim gets no useful signal — its next command fails with the generic
Target page, context or browser has been closed, which is likely why this hasgone unnoticed.
session-manager.ts:603-668already solves exactly this problem for lock recovery:commandUsesProfileDirrequires the directory to end at an argument boundary,profileDirAliasesresolves symlinks, andisCloakBrowserCommandrestricts thematch to CloakBrowser binaries.
terminateProfileuses none of it.Steps to Reproduce
names share a prefix:
workprofile's context to close or its launch to fail — for examplequit its browser and re-run a
workcommand so the launch-failure cleanup atdarwin-background-launch.ts:118runs.work-2. Its browser is gone and the command fails withTarget page, context or browser has been closed.Expected Behavior
Tearing down
worksignals only the browser webcmd started forwork. A profilewhose directory merely shares a prefix, and any browser webcmd did not start, are
left running.
webcmd Version
0.5.3
Node.js Version
22.x
Operating System
macOS
Logs / Screenshots