lima-vm/lima · error
symlink target %#q in %#q escapes the repository
Error message
symlink target %#q in %#q escapes the repository
What it means
symlinkTarget resolves a file-symlink target relative to the directory of the fetched file and refuses targets whose cleaned path climbs above the repository root (paths starting with ../ after path.Join/Clean). This security check prevents a template's symlink file from redirecting the fetch to a different repository or host path via .. segments.
Source
Thrown at pkg/limatmpl/github.go:200
// A symlink must be a single line (without trailing newline), no spaces, no colons
if !(content == "" || strings.ContainsAny(content, "\n :")) {
// symlink is relative to the directory of filePath, and must stay within the repo
filePath, err = symlinkTarget(filePath, content)
if err != nil {
return "", err
}
}
return githubUserContentURL(org, repo, branch, filePath), nil
}
// symlinkTarget resolves a symlink target relative to the directory of filePath.
// It returns an error if the target escapes the repository root, so a symlink
// cannot redirect the fetch to a different repository via `../` segments.
func symlinkTarget(filePath, target string) (string, error) {
resolved := path.Join(path.Dir(filePath), target)
if escapesRepo(resolved) {
return "", fmt.Errorf("symlink target %#q in %#q escapes the repository", target, filePath)
}
return resolved, nil
}
// escapesRepo reports whether the cleaned path climbs above the repository root.
func escapesRepo(p string) bool {
p = path.Clean(p)
return p == ".." || strings.HasPrefix(p, "../")
}
// resolveGitHubRedirect checks if a file at the given path is a github: URL to another file within the same repo.
// Returns the URL, or an error if the file doesn't exist, or doesn't start with github:ORG.
func resolveGitHubRedirect(ctx context.Context, org, repo, defaultBranch, filePath, origBranch string) (string, error) {
// Refetch the filepath from the defaultBranch
resp, err := getGitHubUserContent(ctx, org, repo, defaultBranch, filePath)
if err != nil {
return "", fmt.Errorf("failed to fetch file: %w", err)
}View on GitHub (pinned to dd909d0973)
Solutions
- Fix the upstream symlink file to use an in-repo relative target (no leading ../ beyond the repo root)
- If the file was never intended to be a symlink, reformat it so it contains a newline, space, or colon (e.g. make it a comment-bearing YAML) so it is not treated as a symlink path
- Reference the actual target file directly in your github: URL instead of going through the symlink
- Fork the template repo and correct the symlink target
Example fix
// symlink file content, before ../../../etc/passwd // after configs/default.yaml
Defensive patterns
Strategy: validation
Validate before calling
func safeTarget(dir, target string) bool {
resolved := path.Clean(path.Join(dir, target))
return resolved != ".." && !strings.HasPrefix(resolved, "../")
}
// check before fetching a template whose symlink content you control
if !safeTarget("templates", contents) {
return errors.New("symlink target escapes repo")
} Type guard
func escapesRepo(p string) bool {
p = path.Clean(p)
return p == ".." || strings.HasPrefix(p, "../")
} Try / catch
url, err := transformGitHubURL(ctx, ref)
if err != nil && strings.Contains(err.Error(), "escapes the repository") {
return fmt.Errorf("refusing template: symlink in %s points outside its repo; reference the target file directly", ref)
} Prevention
- Keep symlink files pointing only at in-repo relative paths
- Never author symlink-style one-line files with ../ segments
- Reference the real target file directly instead of a traversing symlink
- Audit template repos' one-line files, since any single line without newline/space/colon is treated as a symlink
When it happens
Trigger: transformGitHubURL -> resolveGitHubSymlink finds the fetched .yaml content is a single-line path (no newline, space, or colon) and calls symlinkTarget; the target resolves to a path like ../../other/repo/file.yaml that escapesRepo() flags.
Common situations: A template repo containing a symlink file that points outside its own tree (e.g. ../shared/config.yaml); content-antisymmetry mistakes where a one-line value happens to look like a path; a compromised or misconfigured upstream template attempting path traversal.
Related errors
- invalid digest algorithm %#q
- failed to symlink %#q to %#q: %w
- unexpected symlink target for virtiofs mount source %#q: exp
- failed to create symlink from %#q to %#q: %w
- sudo field must not contain newline characters
AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01).
Data as JSON: /api/errors/7ed122a506124ba1.
Report an issue: GitHub.