{"record":{"id":"3a8fce0bd812a0c2","repo":"1Panel-dev/1Panel","slug":"outside-module-root","errorCode":null,"errorMessage":"outside-module-root","messagePattern":"outside-module-root","errorType":"console","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/openresty-modules/diagnose-install.sh","lineNumber":223,"sourceCode":"print(\"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,\n            ]))\nsys.exit(1 if failed else 0)\nPY","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/1Panel-dev/1Panel/blob/5ac7c808815b0691009cd390414f03471312262f/scripts/openresty-modules/diagnose-install.sh#L205-L241","documentation":"Raised after resolve(strict=True) when the fully resolved path is not under the modules root (modules_root not in full_path.parents). This catches escapes that survived the lexical checks: symlinks in intermediate directories (parent dir is a link even if the final file is not) or a modules_root that is itself a symlink making the resolved parents diverge.","triggerScenarios":"An intermediate directory under modules_root is a symlink pointing elsewhere (modules/resty -> /srv/resty), so the artifact resolves outside; or modules_root was derived from a different symlinked path than the one used for comparison, so parents never match.","commonSituations":"The modules directory is on a bind mount or symlinked data volume; a Docker volume mount maps the modules dir to another host path; admin symlinked a whole subdirectory for space reasons.","solutions":["find <modules_root> -type l and replace any directory symlinks with real directories (or bind mounts)","Ensure modules_root passed to the script is the canonical resolved path (realpath) so the parents check compares against the same tree the artifacts resolve into","Re-run diagnose-install.sh; the row must show result=OK"],"exampleFix":"# before\nmodules/resty -> /srv/resty (symlinked dir)\n# after\nmkdir modules/resty && cp -L /srv/resty/* modules/resty/","handlingStrategy":"validation","validationCode":"python3 -c \"import os; r=os.path.realpath('/opt/1panel/openresty/modules'); print(r)\"  # pass this resolved path as modules_root","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use realpath for the modules root on both producer and verifier sides","Avoid symlinked data volumes for the modules directory; prefer bind mounts"],"tags":["path-traversal","symlink","docker","openresty"],"backgroundTag":null,"analyzedSha":"5ac7c808815b0691009cd390414f03471312262f","analyzedAt":"2026-08-15T14:02:06.953Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}