hyperledger/fabric · error

refusing to copy symlink %s -> %s pointing outside of source

Error message

refusing to copy symlink %s -> %s pointing outside of source root

What it means

During a copy of package contents, a symlink whose resolved target escapes the source root is rejected. Fabric refuses to follow symlinks that would read or write files outside the extracted package directory, preventing path traversal from malicious chaincode packages.

Source

Thrown at core/container/externalbuilder/copy.go:78

	// If the symlink is absolute, then we do not want to copy it.
	symlinkDest, err := os.Readlink(srcpath)
	if err != nil {
		return err
	}
	if filepath.IsAbs(symlinkDest) {
		return errors.Errorf("refusing to copy absolute symlink %s -> %s", srcpath, symlinkDest)
	}

	// Determine where the symlink points to. If it points outside
	// of the source root, then we do not want to copy it.
	symlinkDir := filepath.Dir(srcpath)
	symlinkTarget := filepath.Clean(filepath.Join(symlinkDir, symlinkDest))
	relativeTarget, err := filepath.Rel(srcroot, symlinkTarget)
	if err != nil {
		return err
	}
	if relativeTargetElements := strings.Split(relativeTarget, string(os.PathSeparator)); len(relativeTargetElements) >= 1 && relativeTargetElements[0] == ".." {
		return errors.Errorf("refusing to copy symlink %s -> %s pointing outside of source root", srcpath, symlinkDest)
	}

	return os.Symlink(symlinkDest, destpath)
}

func copyFile(srcpath, destpath string) error {
	srcFile, err := os.Open(srcpath)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	info, err := srcFile.Stat()
	if err != nil {
		return err
	}

	destFile, err := os.Create(destpath)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Remove or rewrite the symlink so its target resolves inside the source package directory
  2. Copy the actual file content into the package instead of symlinking external files
  3. Recreate the package tarball ensuring symlinks use relative paths within the package

Example fix

// before
ln -s ../../../shared/libfoo.so lib/libfoo.so
// after
cp ../../../shared/libfoo.so lib/libfoo.so
Defensive patterns

Strategy: validation

Validate before calling

target, _ := filepath.EvalSymlinks(linkPath)
root, _ := filepath.EvalSymlinks(srcRoot)
rel, err := filepath.Rel(root, target)
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
    return fmt.Errorf("symlink escapes package: %s", linkPath)
}

Type guard

func symlinkInsideRoot(link, root string) bool {
    target, err := filepath.EvalSymlinks(link)
    if err != nil { return false }
    absRoot, _ := filepath.Abs(root)
    return strings.HasPrefix(target, absRoot+string(os.PathSeparator))
}

Prevention

When it happens

Trigger: An external builder package contains a symlink whose destination, when joined with its directory and resolved relative to the source root, begins with '..' — i.e. it points outside the extracted package (e.g. symlink -> '../../etc/passwd' or an absolute-target symlink).

Common situations: Developers packaging chaincode that symlinks shared libraries or config from elsewhere on disk; build scripts generating relative symlinks with too many '..' segments; tarballs created with absolute symlink targets.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/147f97737b03a834. Report an issue: GitHub.