# Risk map — github/spec-kit

> Canonical page: https://codevetter.com/unpack/spec-kit/risk-map

# Risk map — github/spec-kit

Scanned commit 175adbcd826befdccce8c60faa07ae3d8bbc8d87 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The highest-risk surface is remote-code paths the CLI intentionally supports: project-local custom workflow steps are imported and executed, URL-based extension/bundle installs download and extract archives, and generated event dispatchers + workflow `shell`/`command` steps run subprocesses with user privileges. These are hardened with HTTPS-only rules, byte caps, zip-slip defenses, SHA-256 pins, trust prompts, and path confinement — but the security posture is defense-in-depth around inherently dangerous features. Secondary risks: three giant modules concentrate blast radius, compatibility shims create fragile monkeypatch coupling, and a few silent-fallback paths can hide breakage.
- `load_custom_steps` dynamically imports and executes `__init__.py` from every package under `.specify/workflows/steps/` — arbitrary code execution by design for community step types. Mitigations: symlink rejection on every path component, sanitized synthetic module names, sys.modules cleanup on failure — but any file dropped into that directory runs with user privileges. — [src/specify_cli/workflows/__init__.py#L85-213](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L85-L213)
- Workflow `shell` and `command` steps run subprocesses with the user's full privileges; the engine explicitly documents that `requires:` (and any hypothetical `permissions:` key — which is rejected by validation) is advisory and 'NOT a security boundary'. — [src/specify_cli/workflows/engine.py#L74-85,126-133](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/engine.py#L74-85,126-L133) [src/specify_cli/workflows/steps/shell/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/steps/shell/__init__.py)
- URL installs (`--extension <url>`, `extension add --from`, catalog downloads) fetch remote archives: hardened by `is_https_or_localhost_http`, `is_safe_download_redirect`, `read_response_limited` byte caps, `safe_extract_archive` (entry count/size/path limits, Windows reserved names), and optional `sha256` catalog pins — but a missing pin means integrity rests on HTTPS alone. — [src/specify_cli/_download_security.py#L26-49,95-113](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_download_security.py#L26-49,95-L113) [src/specify_cli/shared_infra.py#L42-99](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/shared_infra.py#L42-L99) [src/specify_cli/extensions/command_add.py#L48-91](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/command_add.py#L48-L91)
- The generated `.specify/events.py` dispatcher runs event-mapped scripts inside agent hooks; `EVENT_SCRIPT_PATH_CONFINEMENT = True` exists because an older stale dispatcher would otherwise run unconfined script tokens — i.e. this code path was previously exploitable and is now gated by a version sentinel. — [src/specify_cli/events.py#L33-46](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/events.py#L33-L46)
- Three files concentrate enormous blast radius: `presets/__init__.py` (6259 LOC), `extensions/__init__.py` (5481 LOC), `workflows/_commands.py` (3901 LOC) — each mixes domain logic, validation, and CLI plumbing, so a bad edit can break an entire command group. — [src/specify_cli/presets/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/presets/__init__.py) [src/specify_cli/extensions/__init__.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py) [src/specify_cli/workflows/_commands.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py)
- Fragile test-coupling pattern: `_commands.py` modules keep thin forwarders that re-fetch helpers (`_require_specify_project`, `load_init_options`, …) from the package root at call time purely so existing monkeypatch paths keep working; renaming or moving the root helpers silently breaks tests and commands. — [src/specify_cli/extensions/_commands.py#L37-56](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/_commands.py#L37-L56) [src/specify_cli/workflows/_commands.py#L1-7](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/_commands.py#L1-L7) [design/cli.md#L98-112](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/design/cli.md#L98-L112)
- Subprocess policy is enforced by lint rather than review: ruff `extend-select` enables S602/S604/S605 so any `shell=True`/`os.system`/`popen2` reintroduction needs an explicit noqa, but other subprocess invocations (agent CLIs, scripts) are unrestricted list-form exec. — [pyproject.toml#L85-93](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/pyproject.toml#L85-L93)
- `specify init --here --force` (and `--force` on existing dirs) merges/overwrites into non-empty directories; the confirmation/EOF handling is intricate (Abort vs EOFError distinction) and a scripting mistake there writes into a user's existing project. — [src/specify_cli/commands/init.py#L429-521](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L429-L521)
- Deprecated hidden flags (`--skip-tls`, `--github-token`, `--offline`, `--debug`) are accepted as silent no-ops — scripts relying on the old behavior get no warning that TLS verification or offline mode semantics changed. — [src/specify_cli/commands/init.py#L294-317](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/commands/init.py#L294-L317)
- Custom step loading swallows every exception (`except Exception  # noqa: BLE001` → `continue`), so a broken or maliciously-aborting step package fails invisibly; same silent-skip posture applies to malformed `step.yml`. — [src/specify_cli/workflows/__init__.py#L209-213](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/workflows/__init__.py#L209-L213)
- Credential handling: `~/.specify/auth.json` maps hosts to providers and `_github_http.py` attaches GITHUB_TOKEN/GH_TOKEN only to a fixed host allowlist (github.com, api.github.com, raw.githubusercontent.com, codeload.github.com) — correct, but the file is user-managed with no permission checks enforced in the registry layer. — [src/specify_cli/_github_http.py#L18-27](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/_github_http.py#L18-L27) [src/specify_cli/authentication/__init__.py#L1-8](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/authentication/__init__.py#L1-L8) [src/specify_cli/authentication/http.py](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/authentication/http.py)
- `_load_core_command_names` falls back to a hardcoded `_FALLBACK_CORE_COMMAND_NAMES` frozenset when neither core_pack nor the repo templates are readable; if the bundled template set drifts from the fallback, extension command-name validation silently uses stale data (a previous `.parent`-counting bug already broke this once, per the docstring). — [src/specify_cli/extensions/__init__.py#L52-123](https://github.com/github/spec-kit/blob/175adbcd826befdccce8c60faa07ae3d8bbc8d87/src/specify_cli/extensions/__init__.py#L52-L123)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
