mirror of
https://github.com/github/spec-kit.git
synced 2026-07-07 14:39:00 +08:00
fix(manifest): lexical pre-check for record_existing + add error-case tests
Address Copilot review (2026-05-11, review id 4266902103):
1. `record_existing` was calling `(self.project_root / rel).is_symlink()`
BEFORE validating containment. For absolute paths or paths containing
`..`, this performed a filesystem stat outside the project root before
`_validate_rel_path()` raised. Add a cheap lexical pre-check that
delegates to `_validate_rel_path()` for the canonical error messages,
so the symlink stat only ever runs on paths that are already lexically
inside the project root.
2. Add focused unit tests in `tests/integrations/test_manifest.py` for
the symlink and non-regular-file error paths, including:
- symlink target rejection
- dangling symlink rejection (caught by the symlink guard before
the is_file check)
- directory path rejection (is_file == False)
- missing-path rejection (is_file == False)
- absolute-path lexical pre-check
The Copilot reviewer noted these guards had no focused coverage in
`test_manifest.py`, only via the `test_integration_claude.py`
regression test.
3. The third Copilot finding (repeated `dict(self._files)` copies via
`manifest.files` in the skip branches) is already resolved on this
branch by using `prior_hashes` — the function-scope snapshot taken at
the top of `install_shared_infra` — for the membership check, instead
of `manifest.files`.
AI disclosure: drafted with assistance from Claude (Opus 4.7).
This commit is contained in:
@@ -157,6 +157,15 @@ class IntegrationManifest:
|
||||
treat the entry as permanently broken.
|
||||
"""
|
||||
rel = Path(rel_path)
|
||||
# Cheap lexical pre-check first so absolute / parent-traversal paths
|
||||
# don't trigger a filesystem stat outside the project root before
|
||||
# ``_validate_rel_path`` raises. ``_validate_rel_path`` produces the
|
||||
# canonical error messages used elsewhere.
|
||||
if rel.is_absolute() or ".." in rel.parts:
|
||||
_validate_rel_path(rel, self.project_root)
|
||||
# Defensive: _validate_rel_path always raises on these inputs,
|
||||
# but make the contract explicit if it is ever loosened.
|
||||
raise ValueError(f"Manifest path escapes project root: {rel}")
|
||||
# Check ``is_symlink()`` on the un-resolved path because
|
||||
# ``_validate_rel_path`` resolves the path (which would follow
|
||||
# the symlink and silently record the target instead).
|
||||
|
||||
Reference in New Issue
Block a user