github/spec-kit · error · Error
errors.join('; ')
Error message
errors.join('; ') What it means
Second guard inside _shared_relative_path(): the path did relativize, but the result is absolute or contains a '..' component. This catches relative destinations like '../outside/file' that Path.relative_to can still produce when dest was built as project_path/'..'/'elsewhere', meaning the write would climb out of the project root.
Source
Thrown at src/specify_cli/events.py:1903
checks = " || ".join(
f"input.tool === {json.dumps(t.lower())}" for t in tools
)
body_lines.append(
f" try {{ if ({checks}) {{ runEvent({command_lit}, {ev_lit}, input, output, {timeout_sec}); }} }} catch (e) {{ errors.push((e as Error).message); }}"
)
else:
body_lines.append(
f" try {{ runEvent({command_lit}, {ev_lit}, input, output, {timeout_sec}); }} catch (e) {{ errors.push((e as Error).message); }}"
)
elif is_injection:
body_lines.append(
f" try {{ const ctx = runEvent({command_lit}, {ev_lit}, input, output, {timeout_sec}); if (ctx) contexts.push(ctx); }} catch (e) {{ errors.push((e as Error).message); }}"
)
else:
body_lines.append(
f" try {{ runEvent({command_lit}, {ev_lit}, input, output, {timeout_sec}); }} catch (e) {{ errors.push((e as Error).message); }}"
)
body_lines.append(" if (errors.length > 0) { throw new Error(errors.join('; ')); }")
if native.startswith("tool.execute."):
ts_hook = native
event_entries.append(
f"function _{ev}(input: any, output: any) {{\n"
+ "\n".join(body_lines) + "\n"
" }"
)
plugin_returns.append(
f" {json.dumps(ts_hook)}: async (input: any, output: any) => {{\n"
f" _{ev}(input, output);\n"
f" }},"
)
elif native == "experimental.chat.system.transform":
body_lines.append(' return contexts.join("\\n\\n");')
event_entries.append(
f"function _{ev}(input: any, output: any): string {{\n"
+ "\n".join(body_lines) + "\n"View on GitHub (pinned to bf88c9f9a8)
Solutions
- Normalize and validate the relative portion: reject any dest whose parts contain '..' before calling the API
- Build destinations from trusted constants plus sanitized user input (allow only [A-Za-z0-9._-] segments)
- Fix the configuration value that introduced the '..' segment
- Run Path(os.path.normpath(dest)) checks in your own layer and fail early with a clearer message
Example fix
# before
dest = project / cfg.shared_dir / 'file.yaml' # cfg.shared_dir = '../outside'
# after
if '..' in Path(cfg.shared_dir).parts:
raise ValueError('shared_dir must stay inside the project')
dest = project / cfg.shared_dir / 'file.yaml' Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path, PurePosixPath
def safe_relative(user_path: str) -> Path | None:
p = PurePosixPath(user_path)
if p.is_absolute() or '..' in p.parts:
return None
return Path(p)
rel = safe_relative(cfg.shared_dir)
if rel is None:
raise ValueError(f'unsafe shared dir: {cfg.shared_dir!r}') Try / catch
try:
rel = _shared_relative_path(project_path, dest)
except ValueError as e:
if 'escapes project root' in str(e):
raise SystemExit(f'configuration points outside the project: {e}') from e
raise Prevention
- Sanitize directory config values with an allowlist pattern like ^[A-Za-z0-9._/-]+$ plus explicit '..' rejection
- Never construct dest via string concatenation of user input; use Path.joinpath on validated parts
- Add a unit test asserting '..' in user-supplied subdirs is rejected before the API is reached
When it happens
Trigger: Calling shared-infra write helpers with dest such as project_path / '..' / 'sibling' / 'file', or a relative dest containing '..' segments that survives relative_to. E.g. _write_shared_bytes(project, project/'.specify'/'..'/'..'/'victim', b'x').
Common situations: User-configured subdir containing '..' (e.g. config value "../shared"); sanitizing paths by string concatenation instead of resolve(); templates or presets whose directory fields were edited to traverse upward.
Related errors
- specify event ${{command}} (${{event}}) failed: ${{(e as Err
- Error: --number requires a value
- ERROR: Unknown option '{arg}'. Use --help for usage informat
- Output path {candidate!r} escapes directory {base!r}
- Invalid command name {cmd_name!r}: {name_reason}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/f0edb29ceb1eef1f.
Report an issue: GitHub.