We value responsible disclosure and the safety of our users.
- Email the maintainers using the contact in this repository’s metadata or open a private, security‑labeled issue if available.
- Provide a detailed description, steps to reproduce, and affected versions.
- Do not create public issues or pull requests that include exploit details.
We will acknowledge reports within 72 hours and provide a timeline for remediation when possible.
- Please do not disclose the issue publicly until a fix is available and users have had a reasonable time to update.
- Coordinated disclosure is appreciated.
This is an active repository; we support the latest main branch and the most recent tagged release. Security fixes may be backported at the maintainers’ discretion.
The Python package targets the interpreter range in requires-python (see the root pyproject.toml). Prefer installing with pinned dependencies (uv.lock / uv sync --frozen) or the exported requirements.txt when deploying.
CI runs Bandit fail-closed: any HIGH finding fails the job, and any finding not present in the committed .bandit-baseline.json also fails (new LOW/MEDIUM drift).
The CLI uses subprocess in fixed-argv, no-shell helpers:
| Location | Why |
|---|---|
cli/ucli/commands/tour_ops.py |
tour_run fixture execution |
cli/ucli/commands/doctor_ops.py |
Doctor environment probes (node -v) |
cli/ucli/commands/demo_ops.py |
Local demo HTTP server lifecycle |
cli/ucli/trace/pytrace.py |
Isolated trace worker (never exec in-process) |
Each of those imports is marked # nosec B404 with an inline rationale. Call sites that spawn processes use # nosec B603 where Bandit flags the invoke. Do not silence B404/B603 for shell=True, user-controlled argv, or new call sites without review.
The committed .bandit-baseline.json is refreshed to zero residual findings when intentional subprocess imports are # nosec B404'd and call sites use # nosec B603 where needed. Re-run bandit -r cli/ -f json -o .bandit-baseline.json after accepting any new residual finding — never to hide HIGH severity or unreviewed drift. Fail-closed gates (-lll HIGH + -b baseline) stay mandatory.
Tracing must execute target Python to collect call events. Mitigations:
| Control | Behavior |
|---|---|
| Process isolation | Worker runs in a subprocess; the CLI process never exec_modules the target |
| Path allowlist | Target must resolve under cwd or UF_TRACE_ALLOW_ROOTS; non-.py refused |
| Argv denylist | func must be a simple identifier; dunders and eval/exec/__import__/compile/open/… are refused |
| POSIX limits | Soft RLIMIT_CPU / RLIMIT_AS in the worker when resource exists |
| Windows Job Object | Best-effort process memory cap via Job Object (ctypes); failure is non-fatal |
| Timeout | 60s hard kill |
- Target code still runs with the privileges of the user invoking
u trace. A malicious.pyunder an allowlisted root can perform any action the OS user can (network, filesystem outside the job memory limit, etc.). - Windows Job Object assignment is best-effort; if ctypes/API calls fail, only the timeout remains as a hard stop (no POSIX
RLIMIT_*). - Denylist blocks names passed to
getattr(module, name)— it does not inspect what the callable body does. - Prefer
u trace errors(AST-only) when execution is not required.
Do not treat trace as a general-purpose sandbox for untrusted code.