{"record":{"id":"5cef3087fb23e1c9","repo":"1Panel-dev/1Panel","slug":"symlink-not-allowed","errorCode":null,"errorMessage":"symlink-not-allowed","messagePattern":"symlink-not-allowed","errorType":"console","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/openresty-modules/diagnose-install.sh","lineNumber":220,"sourceCode":"modules_root = pathlib.Path(sys.argv[2]).resolve()\nmodules = json.loads(state_path.read_text(encoding=\"utf-8\"))\nfailed = False\nprint(\"module\\tbuild_status\\ttarget_key\\tartifact\\texpected\\tactual\\tresult\")\nfor module in modules:\n    for build in module.get(\"builds\") or []:\n        target_key = (build.get(\"target\") or {}).get(\"key\", \"\")\n        for artifact in build.get(\"artifacts\") or []:\n            relative = artifact.get(\"path\", \"\")\n            expected = artifact.get(\"checksum\", \"\")\n            result = \"OK\"\n            actual = \"\"\n            try:\n                pure = pathlib.PurePosixPath(relative)\n                if not relative or pure.is_absolute() or \"..\" in pure.parts or \"\\\\\" in relative:\n                    raise ValueError(\"unsafe-path\")\n                candidate = modules_root / pathlib.Path(*pure.parts)\n                if candidate.is_symlink():\n                    raise ValueError(\"symlink-not-allowed\")\n                full_path = candidate.resolve(strict=True)\n                if modules_root not in full_path.parents:\n                    raise ValueError(\"outside-module-root\")\n                if not full_path.is_file():\n                    raise ValueError(\"not-regular-file\")\n                digest = hashlib.sha256()\n                with full_path.open(\"rb\") as handle:\n                    for chunk in iter(lambda: handle.read(1024 * 1024), b\"\"):\n                        digest.update(chunk)\n                actual = digest.hexdigest()\n                if actual.lower() != expected.lower():\n                    raise ValueError(\"checksum-mismatch\")\n            except Exception as error:\n                result = str(error)\n                failed = True\n            print(\"\\t\".join([\n                module.get(\"name\", \"\"), build.get(\"status\", \"\"), target_key,\n                relative, expected, actual, result,","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/1Panel-dev/1Panel/blob/5ac7c808815b0691009cd390414f03471312262f/scripts/openresty-modules/diagnose-install.sh#L202-L238","documentation":"Raised when the joined candidate file (modules_root/<artifact path>) is itself a symlink. The verifier refuses symlinks explicitly before resolve() so a link pointing outside the modules root can never be checksummed, even if its target resolves back inside.","triggerScenarios":"diagnose-install.sh verifies a build whose artifact path under the modules root is a symlink (ln -s /somewhere/else/mod.so modules/resty/mod.so), created manually or by an install step that linked instead of copied.","commonSituations":"Operator 'deduplicated' module files with symlinks; a package install used update-alternatives-style links; artifacts were restored from a tarball that preserved symlinks.","solutions":["Replace the symlink with the real file: rm modules/resty/mod.so && cp /somewhere/else/mod.so modules/resty/mod.so","Re-verify the checksum of the copied file matches the manifest (sha256sum) or re-run diagnose-install.sh","Find other links with: find <modules_root> -type l"],"exampleFix":"# before\nln -s /opt/other/mod.so /opt/1panel/openresty/modules/resty/mod.so\n# after\ncp /opt/other/mod.so /opt/1panel/openresty/modules/resty/mod.so","handlingStrategy":"validation","validationCode":"find /opt/1panel/openresty/modules -type l -print  # must be empty before diagnosing","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Install artifacts by copy, never symlink","After unpacking tarballs, strip links: tar --dereference when creating, find -type l audit after extracting"],"tags":["symlink","security","openresty","python"],"backgroundTag":null,"analyzedSha":"5ac7c808815b0691009cd390414f03471312262f","analyzedAt":"2026-08-15T14:02:06.953Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}