spawn_test() (bcc_core.py ~L1584) builds merged_env = {**os.environ, "PATH": augmented_path()} then merged_env.update(data.get("env") or {}) and passes it to subprocess.Popen. If a server's env contains a non-string value — trivially reachable by pasting JSON like "env": {"PORT": 8080} and saving without ever opening the editor (the KeyValueTable str()-casts on load, but paste/import doesn't round-trip through it) — Popen raises TypeError, which is not in the except (FileNotFoundError, OSError) clause.
The exception escapes SpawnTester.run() / the Test-all worker: done is never emitted, so:
Test launch button stays disabled saying "Launching…" forever;
Test all stalls mid-queue with the button stuck on "Testing N/M…".
Args are already str-cast ([str(a) for a in …]); env is the gap. args containing non-str is handled; command non-str is .strip()ed — a non-str command (e.g. number) would also raise AttributeError before that ((data.get("command") or "").strip() breaks on int → actually or "" passes the int through; .strip() → AttributeError). Same escape path.
Proposed fix
In spawn_test: coerce env keys/values with str() when merging; coerce command via str(data.get("command") or "").strip().
Defense in depth: wrap the body of spawn_test (or SpawnTester.run / the test-all worker) so any unexpected exception returns/emits an {"outcome": "crashed", "detail": repr(e)}-style result instead of dying silently — the UI must always get a result.
Acceptance
Unit test: spawn_test({"command": sys.executable, "args": ["-c", "pass"], "env": {"PORT": 8080}}) returns a result dict (no exception), and the env value is passed as "8080".
Unit test: a data dict engineered to raise inside spawn_test still yields an outcome dict.
Manual: paste a server with numeric env value → Test launch completes and re-enables the button.
**Found in audit of `main` @ 06326e5.**
`spawn_test()` (`bcc_core.py` ~L1584) builds `merged_env = {**os.environ, "PATH": augmented_path()}` then `merged_env.update(data.get("env") or {})` and passes it to `subprocess.Popen`. If a server's `env` contains a non-string value — trivially reachable by pasting JSON like `"env": {"PORT": 8080}` and saving without ever opening the editor (the KeyValueTable str()-casts on load, but paste/import doesn't round-trip through it) — `Popen` raises `TypeError`, which is **not** in the `except (FileNotFoundError, OSError)` clause.
The exception escapes `SpawnTester.run()` / the Test-all worker: `done` is never emitted, so:
- **Test launch** button stays disabled saying "Launching…" forever;
- **Test all** stalls mid-queue with the button stuck on "Testing N/M…".
Args are already str-cast (`[str(a) for a in …]`); env is the gap. `args` containing non-str is handled; `command` non-str is `.strip()`ed — a non-str `command` (e.g. number) would also raise `AttributeError` before that (`(data.get("command") or "").strip()` breaks on int → actually `or ""` passes the int through; `.strip()` → AttributeError). Same escape path.
**Proposed fix**
- In `spawn_test`: coerce env keys/values with `str()` when merging; coerce `command` via `str(data.get("command") or "").strip()`.
- Defense in depth: wrap the body of `spawn_test` (or `SpawnTester.run` / the test-all worker) so any unexpected exception returns/emits an `{"outcome": "crashed", "detail": repr(e)}`-style result instead of dying silently — the UI must always get a result.
**Acceptance**
- Unit test: `spawn_test({"command": sys.executable, "args": ["-c", "pass"], "env": {"PORT": 8080}})` returns a result dict (no exception), and the env value is passed as `"8080"`.
- Unit test: a data dict engineered to raise inside spawn_test still yields an outcome dict.
- Manual: paste a server with numeric env value → Test launch completes and re-enables the button.
the_og
added the P1 label 2026-07-12 12:44:29 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found in audit of
main@06326e5.spawn_test()(bcc_core.py~L1584) buildsmerged_env = {**os.environ, "PATH": augmented_path()}thenmerged_env.update(data.get("env") or {})and passes it tosubprocess.Popen. If a server'senvcontains a non-string value — trivially reachable by pasting JSON like"env": {"PORT": 8080}and saving without ever opening the editor (the KeyValueTable str()-casts on load, but paste/import doesn't round-trip through it) —PopenraisesTypeError, which is not in theexcept (FileNotFoundError, OSError)clause.The exception escapes
SpawnTester.run()/ the Test-all worker:doneis never emitted, so:Args are already str-cast (
[str(a) for a in …]); env is the gap.argscontaining non-str is handled;commandnon-str is.strip()ed — a non-strcommand(e.g. number) would also raiseAttributeErrorbefore that ((data.get("command") or "").strip()breaks on int → actuallyor ""passes the int through;.strip()→ AttributeError). Same escape path.Proposed fix
spawn_test: coerce env keys/values withstr()when merging; coercecommandviastr(data.get("command") or "").strip().spawn_test(orSpawnTester.run/ the test-all worker) so any unexpected exception returns/emits an{"outcome": "crashed", "detail": repr(e)}-style result instead of dying silently — the UI must always get a result.Acceptance
spawn_test({"command": sys.executable, "args": ["-c", "pass"], "env": {"PORT": 8080}})returns a result dict (no exception), and the env value is passed as"8080".