{"record":{"id":"971ee6ac28975ce6","repo":"larksuite/cli","slug":"s-path-q-is-a-symlink-not-allowed","errorCode":null,"errorMessage":"%s: path %q is a symlink (not allowed)","messagePattern":"(.+?): path %q is a symlink \\(not allowed\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/binding/audit.go","lineNumber":102,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s: cannot stat %q: %w\", label, target, err)\n\t}\n\tif info.IsDir() {\n\t\treturn nil, fmt.Errorf(\"%s: path %q is a directory, not a file\", label, target)\n\t}\n\treturn info, nil\n}\n\n// resolveSymlinkIfAllowed resolves a symlink to its target when\n// params.AllowSymlinkPath is true, or rejects it otherwise. When the input\n// is not a symlink, target is returned unchanged. A symlink that points to\n// another symlink is rejected so callers only deal with a single hop.\nfunc resolveSymlinkIfAllowed(target string, linfo fs.FileInfo, params AuditParams) (string, error) {\n\tif linfo.Mode()&os.ModeSymlink == 0 {\n\t\treturn target, nil\n\t}\n\tif !params.AllowSymlinkPath {\n\t\treturn \"\", fmt.Errorf(\"%s: path %q is a symlink (not allowed)\", params.Label, target)\n\t}\n\tresolved, err := vfs.EvalSymlinks(target)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%s: cannot resolve symlink %q: %w\", params.Label, target, err)\n\t}\n\trinfo, err := vfs.Lstat(resolved)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%s: cannot stat resolved path %q: %w\", params.Label, resolved, err)\n\t}\n\tif rinfo.Mode()&os.ModeSymlink != 0 {\n\t\treturn \"\", fmt.Errorf(\"%s: resolved path %q is still a symlink\", params.Label, resolved)\n\t}\n\treturn resolved, nil\n}\n\n// requireInTrustedDirs enforces that effectivePath lives under one of the\n// caller-declared trusted directories, if any were declared. An empty\n// trustedDirs list disables the check.","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/binding/audit.go#L84-L120","documentation":"resolveSymlinkIfAllowed rejects the audited path because it is a symlink and AuditParams.AllowSymlinkPath is false. The secure-path audit refuses symlink indirection by default to prevent path-traversal/link-swap attacks; even when allowed, only a single hop is resolved and a symlink chain is rejected.","triggerScenarios":"AssertSecurePath -> resolveSymlinkIfAllowed where linfo.Mode() has os.ModeSymlink and params.AllowSymlinkPath is false — the file being audited is a symlink (e.g. dotfile managers, synced folders, or package-managed symlinks).","commonSituations":"Dotfile managers (stow, chezmoi, ln -s) symlink config files into a repo; cloud-sync folders replacing files with symlinks; CI checkout creating symlinks; user manually linking a shared config across projects.","solutions":["Set AllowSymlinkPath: true in AuditParams if symlinked configs are acceptable in your environment.","Replace the symlink with a real file copy (cp --remove-destination or rm the link and cp the target).","Point the binding directly at the symlink's real target path so no link is audited.","If AllowSymlinkPath is true but it still fails, check the resolved target: a second-hop symlink (link to a link) is rejected — flatten the chain to one hop."],"exampleFix":"// before\nparams := binding.AuditParams{Label: \"config\"}\n// after\nparams := binding.AuditParams{Label: \"config\", AllowSymlinkPath: true}","handlingStrategy":"validation","validationCode":"// preflight: detect symlink and flatten to real path if policy disallows links\nif info, err := os.Lstat(p); err == nil && info.Mode()&os.ModeSymlink != 0 && !params.AllowSymlinkPath {\n    real, err := filepath.EvalSymlinks(p)\n    if err != nil { return err }\n    p = real // or copy the file over the link\n}","typeGuard":"func isSymlinkErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"is a symlink (not allowed)\")\n}","tryCatchPattern":"if err := binding.AssertSecurePath(p, params); err != nil {\n    if isSymlinkErr(err) {\n        if real, e := filepath.EvalSymlinks(p); e == nil {\n            return binding.AssertSecurePath(real, params)\n        }\n    }\n    return err\n}","preventionTips":["Decide a single policy for symlinks and set AllowSymlinkPath consistently.","Replace dotfile-manager symlinks with copies when audits must pass.","Point bindings at the real target path rather than the link.","Keep symlink chains to one hop — even allowed mode rejects link-to-link."],"tags":["symlink","security","path-validation","filesystem"],"backgroundTag":"symlink-not-allowed","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}