MCPcopy Create free account
hub / github.com/github/spec-kit / _safe_workflow_id_dir

Function _safe_workflow_id_dir

src/specify_cli/workflows/_commands.py:111–158  ·  view source on GitHub ↗

Validate the per-id install directory before any write and return it. Installs write to ``workflows_dir / / workflow.yml``. The `` `` segment comes from a workflow YAML or catalog key, so it must be checked before ``mkdir``/copy/download follows a symlink outside the project roo

(workflows_dir: Path, workflow_id: str)

Source from the content-addressed store, hash-verified

109
110
111def _safe_workflow_id_dir(workflows_dir: Path, workflow_id: str) -> Path:
112 """Validate the per-id install directory before any write and return it.
113
114 Installs write to ``workflows_dir / <id> / workflow.yml``. The ``<id>``
115 segment comes from a workflow YAML or catalog key, so it must be checked
116 before ``mkdir``/copy/download follows a symlink outside the project root.
117 Rejects, with a clean ``typer.Exit``:
118
119 - an ``<id>`` that is a symlink or an existing non-directory
120 (the latter would otherwise make ``mkdir`` raise);
121 - an ``<id>`` that is not a single workflow-id path segment or collides
122 with internal workflow storage directories;
123 - an ``<id>`` that escapes ``workflows_dir`` (path traversal);
124 - an ``<id>/workflow.yml`` leaf that is a symlink or an existing
125 non-file (either would otherwise make the later write/copy raise).
126
127 The symlink/non-directory check runs *before* ``resolve()`` so a symlinked
128 ``<id>`` reports as a symlink rather than misleadingly as path traversal.
129 ``workflow_id`` is markup-escaped in output to avoid Rich markup injection.
130 """
131 safe_id = _escape_markup(workflow_id)
132 _validate_workflow_id_or_exit(workflow_id)
133
134 dest_dir = workflows_dir / workflow_id
135 _reject_unsafe_dir(dest_dir, f".specify/workflows/{safe_id}")
136 try:
137 dest_dir.resolve().relative_to(workflows_dir.resolve())
138 except ValueError:
139 # Escape the repr (not the raw id) so backslashes added by repr cannot
140 # re-expose markup brackets to Rich.
141 console.print(
142 f"[red]Error:[/red] Invalid workflow ID: {_escape_markup(repr(workflow_id))}"
143 )
144 raise typer.Exit(1)
145 workflow_yml = dest_dir / "workflow.yml"
146 if workflow_yml.is_symlink():
147 console.print(
148 "[red]Error:[/red] Refusing to write through symlinked "
149 f".specify/workflows/{safe_id}/workflow.yml"
150 )
151 raise typer.Exit(1)
152 if workflow_yml.exists() and not workflow_yml.is_file():
153 console.print(
154 "[red]Error:[/red] "
155 f".specify/workflows/{safe_id}/workflow.yml exists but is not a file"
156 )
157 raise typer.Exit(1)
158 return dest_dir
159
160
161# Root helper re-fetched at call time so test monkeypatching of

Calls 4

_reject_unsafe_dirFunction · 0.85
printMethod · 0.80
resolveMethod · 0.45