{"record":{"id":"16e64a27bf216dc4","repo":"affaan-m/ECC","slug":"output-bundle-contains-a-symlink-entry-path","errorCode":null,"errorMessage":"output bundle contains a symlink: {entry.path}","messagePattern":"output bundle contains a symlink: (.+?)","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":110,"sourceCode":"\n\ndef _validate_output_tree(root: Path) -> None:\n    \"\"\"Reject symlinks and special files before parsing bundle content.\"\"\"\n    try:\n        metadata = root.lstat()\n    except FileNotFoundError:\n        raise ContractError(\"output bundle is missing\") from None\n    if stat.S_ISLNK(metadata.st_mode):\n        raise ContractError(\"output bundle root must not be a symlink\")\n    if not stat.S_ISDIR(metadata.st_mode):\n        raise ContractError(\"output bundle root must be a directory\")\n    pending = [root]\n    while pending:\n        directory = pending.pop()\n        with os.scandir(directory) as entries:\n            for entry in entries:\n                if entry.is_symlink():\n                    raise ContractError(f\"output bundle contains a symlink: {entry.path}\")\n                if entry.is_dir(follow_symlinks=False):\n                    pending.append(Path(entry.path))\n                elif not entry.is_file(follow_symlinks=False):\n                    raise ContractError(f\"output bundle contains a special file: {entry.path}\")\n\n\ndef validate_genre_specs(specs: list[dict[str, Any]]) -> None:\n    \"\"\"Require complete, semantically distinct numbered genre specs.\"\"\"\n    if not specs:\n        raise ContractError(\"at least one genre spec is required\")\n    numbers = [spec.get(\"number\") for spec in specs]\n    if len(numbers) != len(set(numbers)):\n        raise ContractError(\"genre numbers must be distinct\")\n\n    fingerprints = [spec.get(\"style_fingerprint\") for spec in specs]\n    signatures = [_semantic_signature(spec) for spec in specs]\n    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):\n        raise ContractError(\"genre references collapsed into a generic style; distinct specs required\")","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L92-L128","documentation":"While walking the output tree, _validate_output_tree rejects any symlink found at any depth inside the bundle. Symlinks in a shipped bundle can escape the bundle boundary or break on copy/packaging, so the contract forbids them entirely.","triggerScenarios":"The bundle content includes a symlink — e.g. node_modules or assets symlinked in during generation, venv paths linked in, or a build tool creating links inside the output dir.","commonSituations":"Build scripts that symlink shared assets to save space, Python venv symlinks copied into the bundle, packaging tools creating convenience links, or copying the bundle with cp -r vs -a differences across environments.","solutions":["Find the offending link (the message names it exactly) and replace it with a real copy of the target","Re-run generation with symlinks disabled in the build tooling","Copy bundle contents with cp -rL / rsync -L to dereference links before validation","Exclude link-producing steps (venv, node_modules linking) from the bundle"],"exampleFix":"# before\nln -s ../shared/logo.png dist/bundle/assets/logo.png\n# after\ncp ../shared/logo.png dist/bundle/assets/logo.png","handlingStrategy":"validation","validationCode":"import os\nfor dirpath, dirnames, filenames in os.walk(bundle_root):\n    for name in dirnames + filenames:\n        if os.path.islink(os.path.join(dirpath, name)):\n            raise SystemExit(f'symlink in bundle: {os.path.join(dirpath, name)}')","typeGuard":null,"tryCatchPattern":"try:\n    validate_bundle(root)\nexcept ContractError as e:\n    if 'contains a symlink' in str(e):\n        print(f'Dereference and copy: {e}')\n    else:\n        raise","preventionTips":["Copy bundles with cp -rL / rsync -L to dereference symlinks","Disable link-creating steps (venv, node_modules links) in bundle builds","Sweep with find -type l before packaging"],"tags":["filesystem","symlink","security"],"backgroundTag":"incompatible-source-type","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}