affaan-m/ECC · error · ContractError
artifact path escapes output directory
Error message
artifact path escapes output directory: {relative} What it means
Each receipt artifact path is resolved against the output directory and must remain inside it (path.relative_to(out_dir) must succeed). This ContractError is raised when a receipt path resolves outside out_dir — e.g. via '../' segments or absolute-path injection — indicating a path-traversal attempt or a corrupt receipt.
Solutions
- Fix the receipt entry to use a relative path contained within the output directory.
- Regenerate the receipt with tasteforge.
- Audit the receipt for suspicious paths (look for '..' or leading '/') before validating; treat any occurrence as tampering.
Example fix
// before
{"path": "../../etc/passwd", ...}
// after
{"path": "images/hero.png", ...} Defensive patterns
Strategy: validation
Validate before calling
resolved = (out_dir / entry['path']).resolve()
if not resolved.is_relative_to(out_dir):
raise SystemExit(f'path escapes output dir: {entry["path"]}') Type guard
def path_is_contained(out_dir: Path, relative: str) -> bool:
try:
return (out_dir / relative).resolve().is_relative_to(out_dir)
except (ValueError, OSError):
return False Try / catch
try:
validate_artifact_receipt(out_dir, entries)
except ContractError as e:
if 'escapes output directory' in str(e):
reject_receipt(receipt_path) # treat as tampering, do not attempt repair
else:
raise Prevention
- Only accept receipts from trusted generation runs; verify signatures/hashes of receipt.json where possible.
- Reject any receipt path containing '..' or starting with '/' during intake.
- Review receipts from external sources before validating.
When it happens
Trigger: Calling validate_artifact_receipt()/validate_bundle() with a receipt entry whose path contains '../' segments, is an absolute path, or is a symlink-resolving target that lands outside the output directory.
Common situations: A maliciously crafted or tampered receipt trying to escape the sandbox; a generation bug writing absolute paths into the receipt; copying entries between bundles with different roots.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- output path contains an invalid component
- Refusing unsafe repair source metadata: sources must stay…
- spec.file must keep generated files directly inside the…
- artifact path must stay beneath output root
- artifact permits provider execution
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/c37b264baa8955c4.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:393
raise ContractError("artifact path must be a non-empty relative path")
bound_paths.append(relative)
if len(bound_paths) != len(set(bound_paths)):
raise ContractError("receipt contains duplicate artifact paths")
missing = emitted - set(bound_paths)
extra = set(bound_paths) - emitted
if missing:
raise ContractError(f"unbound emitted artifact: {sorted(missing)}")
if extra:
raise ContractError(f"receipt binds missing artifact: {sorted(extra)}")
for entry in entries:
relative = entry.get("path")
assert isinstance(relative, str)
path = (out_dir / relative).resolve()
try:
path.relative_to(out_dir)
except ValueError as error:
raise ContractError(f"artifact path escapes output directory: {relative}") from error
if entry.get("provider_execution") is not False:
raise ContractError(f"artifact {relative} permits provider execution")
if not isinstance(entry.get("genre_numbers"), list):
raise ContractError(f"artifact {relative} lacks genre binding")
modalities = entry.get("modalities")
if (not isinstance(modalities, list)
or any(modality not in _REQUIRED_MODALITIES for modality in modalities)):
raise ContractError(f"artifact {relative} has invalid modality binding")
if entry.get("bytes") != path.stat().st_size:
raise ContractError(f"artifact {relative} byte size does not match receipt")
if entry.get("sha256") != _sha256(path):
raise ContractError(f"artifact {relative} SHA-256 does not match receipt")
provenance = entry.get("provenance")
if not isinstance(provenance, list) or not provenance:
raise ContractError(f"artifact {relative} lacks exact reference/time provenance")
for source in provenance:
if not isinstance(source.get("reference_path"), str) or not source["reference_path"]:
raise ContractError(f"artifact {relative} has invalid reference path")View on GitHub (pinned to 8321021c54)