# How croc Handles Symlinks and File Permissions in Secure File Transfers

> Learn how croc secures file transfers by preserving symlinks and file permissions. It hashes symlink targets and restores original permissions using os.Chmod.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: internals
- Published: 2026-07-23

---

**croc treats symlinks and file-system permissions as first-class metadata during transfers, computing hashes from symlink targets rather than the links themselves and restoring original permission bits with `os.Chmod` after writing files.**

croc is a secure, cross-platform file transfer tool written in Go that prioritizes data integrity and security. When transferring directories containing symbolic links or executable scripts, preserving metadata becomes critical. According to the schollz/croc source code, the implementation handles these edge cases through specialized hashing logic, path traversal validation, and permission restoration mechanisms.

## Detecting Symlinks During Pre-Transfer Hashing

When croc indexes files for transfer, the `utils.HashFile` function in [`src/utils/utils.go`](https://github.com/schollz/croc/blob/main/src/utils/utils.go) (lines 102-121) uses `os.Lstat` to examine file metadata. If the file mode indicates `os.ModeSymlink`, the function computes the hash from the target path string rather than the link itself.

This approach ensures that identical symlink configurations produce identical hashes across different machines, even when absolute paths differ. The target path is read using standard library calls, and that string content becomes the input for the hashing algorithm.

```go
// Hashing logic in src/utils/utils.go
hash, err := utils.HashFile(path, "md5") 
// Returns SHA256 of the target path if path is a symlink

```

## Validating Symlink Targets on the Receiver

The receiver implements strict validation for any symlink announced by the sender. Located in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go), two critical functions enforce security boundaries to prevent path traversal and symlink hijacking attacks.

### Preventing Path Traversal with validateReceiveSymlinkTarget

The `validateReceiveSymlinkTarget` function (lines 60-70) inspects the target string announced by the sender. It mandates that the target must be a relative path that resolves inside the receive directory. Absolute paths are rejected, and any target attempting to escape the destination folder (using `..` sequences) triggers an error, blocking the transfer.

```go
// Validation in src/croc/croc.go
if err := validateReceiveSymlinkTarget(parentDir, fi.Symlink); err != nil {
    return err // rejects unsafe or absolute targets
}

```

### Blocking Symlink Overwrites with rejectSymlinkDestination

The `rejectSymlinkDestination` function (lines 12-22) prevents the receiver from overwriting existing symlinks on the local filesystem. This protection stops a malicious sender from redirecting an existing symlink to sensitive files, such as SSH keys or password databases, effectively preventing symlink hijacking.

```go
// Protection against overwriting existing links
if err := rejectSymlinkDestination(destPath); err != nil {
    return err // prevents accidental symlink hijacking
}

```

## Preserving File Permissions Across Systems

For each regular file, croc records the original `os.FileMode` during the initial scan. The `FileInfo` struct in [`src/models/models.go`](https://github.com/schollz/croc/blob/main/src/models/models.go) carries this metadata across the transfer. After writing the file content on the receiving side, the restore logic calls `os.Chmod` with the stored mode.

This step preserves executable bits, read-only flags, and custom permission octals (such as 0755 or 0644), ensuring that scripts remain executable and access controls remain intact after transfer.

```go
// Restoring original permissions after writing
if err := os.Chmod(destPath, originalMode); err != nil {
    log.Errorf("could not set permissions: %v", err)
}

```

## Summary

- **Symlink Detection**: croc uses `os.Lstat` in [`src/utils/utils.go`](https://github.com/schollz/croc/blob/main/src/utils/utils.go) to identify symlinks and hashes the target path rather than the link node.
- **Security Validation**: The receiver validates symlink targets using `validateReceiveSymlinkTarget` and `rejectSymlinkDestination` in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) to prevent path traversal and overwrites.
- **Metadata Preservation**: Original `os.FileMode` permissions are transmitted and restored via `os.Chmod`, maintaining executable and read-only bits across different operating systems.

## Frequently Asked Questions

### Does croc transfer the content a symlink points to or just the link itself?

croc transfers only the symlink metadata—the target path string—not the content of the target file. The `FileInfo` struct carries the symlink target path, and the receiver creates a new symlink pointing to that validated path.

### How does croc prevent symlink-based path traversal attacks?

The `validateReceiveSymlinkTarget` function ensures all symlink targets are relative paths that resolve within the receive directory. It rejects absolute paths and blocks attempts to traverse outside the destination folder using `..` components, effectively preventing directory traversal attacks.

### Are file permissions preserved when transferring between Windows and Linux?

croc captures and transmits the `os.FileMode` bits during transfer. While permission models differ between Windows and Linux, croc attempts to apply the equivalent permissions on the receiving side using `os.Chmod`. Executable bits and read-only flags are preserved when the underlying filesystem supports them.

### What happens if a symlink already exists at the destination path?

The `rejectSymlinkDestination` function in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) checks for existing symlinks at the destination and returns an error if one is found. This prevents a malicious sender from hijacking an existing symlink to point to sensitive files on the receiver's system.