Skip to content

[Bug]: closing one background profile kills profiles whose directory name shares a prefix #242

Description

@Kaushik2003

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.

terminateProfilesrc/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

  1. 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
  2. Confirm both are live:
    ps -axo pid=,command= | grep -c -- '--user-data-dir=.*/cloak/profiles/work'
    # 2
  3. 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.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions