{"record":{"id":"0b90db18ee2ac175","repo":"nektos/act","slug":"symlink-tries-to-access-file-s-outside-of-s","errorCode":null,"errorMessage":"symlink tries to access file '%s' outside of '%s'","messagePattern":"symlink tries to access file '(.+?)' outside of '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/runner/step.go","lineNumber":374,"sourceCode":"\t\t}\n\t\tfoldKeys[strings.ToLower(foldKey)] = s\n\t\treturn s\n\t}\n\tfor _, m := range maps {\n\t\tfor k, v := range m {\n\t\t\ttarget[toKey(k)] = v\n\t\t}\n\t}\n}\n\nfunc symlinkJoin(filename, sym, parent string) (string, error) {\n\tdir := path.Dir(filename)\n\tdest := path.Join(dir, sym)\n\tprefix := path.Clean(parent) + \"/\"\n\tif strings.HasPrefix(dest, prefix) || prefix == \"./\" {\n\t\treturn dest, nil\n\t}\n\treturn \"\", fmt.Errorf(\"symlink tries to access file '%s' outside of '%s'\", strings.ReplaceAll(dest, \"'\", \"''\"), strings.ReplaceAll(parent, \"'\", \"''\"))\n}\n","sourceCodeStart":356,"sourceCodeEnd":376,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/step.go#L356-L376","documentation":"Security guard for tar-based action reading: symlinkJoin resolves a symlink found inside an action archive and rejects it when the resolved destination escapes the parent directory (the action's root). The error names the offending destination and the allowed parent, with quotes doubled to keep the message injection-safe.","triggerScenarios":"An action's tar archive (local or cached remote action) contains a symlink whose target, joined with the current path, lands outside the action root — e.g. Linkname like '../../../../etc/passwd' or an absolute-ish traversal via path.Join semantics.","commonSituations":"Malicious or corrupted third-party action tarball attempting path traversal; an action legitimately symlinking to repo-root files in a way that resolves above the extracted directory (works on GitHub's full checkout but violates act's sandbox); cache poisoning supplying a tampered archive.","solutions":["If you own the action, replace the escaping symlink with a copy of the file or a relative symlink that stays inside the action directory.","If it's a third-party action, audit its repository for unexpected symlinks before trusting it — this error may indicate a supply-chain attempt.","Clear act's action cache (`~/.cache/act` or the configured cache dir) in case a corrupted archive was cached.","Pin the action to a known-good full commit SHA of a version without the offending symlink."],"exampleFix":"# inside the action repo, before (symlink escapes root):\n#   .github/actions/tool/run -> ../../../shared/run.sh\n# after (keep it inside):\n#   .github/actions/tool/run -> ./run.sh  (copy shared/run.sh into the action dir)","handlingStrategy":"type-guard","validationCode":"import (\"path\"; \"strings\")\nfunc safeSymlink(filename, linkname, parent string) bool {\n  dest := path.Join(path.Dir(filename), linkname)\n  return strings.HasPrefix(dest, path.Clean(parent)+\"/\")\n}","typeGuard":"func isSymlinkInBounds(filename, linkname, parent string) bool {\n  dest := path.Join(path.Dir(filename), linkname)\n  return strings.HasPrefix(dest, path.Clean(parent)+\"/\")\n}","tryCatchPattern":null,"preventionTips":["Audit third-party actions for symlinks escaping their directory before use.","Pin actions to full SHAs from trusted maintainers.","Keep action self-contained: no symlinks above the action root."],"tags":["security","symlink","path-traversal","supply-chain"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}