Borg

Security and Safety Considerations

Writing from Multiple hosts

Writing to a repository from multiple clients is insecure. A sequentially incremented nonce is used to derive an IV. In order to avoid reuse/collisions, the last used/reserved nonce is stored on the server. This puts the server into a position where it could send an already used nonce to another client. When only one client is used, this is not possible because it stores the last used nonce locally and refuses to lower it.

Upstream puts it this way:

However, this design is not infallible, and requires synchronization between clients, which is handled through the repository. Therefore in a multiple-client scenario a repository can trick a client into reusing counter values by ignoring counter reservations and replaying the manifest (which will fail if the client has seen a more recent manifest or has a more recent nonce reservation).

See:

Restore from Snapshots

For the same reason as described in Writing from Multiple hosts above, when restoring a snapshot, the nonce stored on the server may be lowered as it too is restored from the snapshot.

If possible, sync the nonce from the client after restoring a snapshot. The client will refuse to lower the nonce locally.

If current nonce value isn’t available from the client, consider using the repository read-only and recreating it.

Repokey vs keyfile

In keyfile mode, key material is stored locally. In repokey mode, key material is stored on the server protected by a KDF.

Properties of keyfile/repokey:

  • Repokey secrets (a password) is generally easier to back up than a keyfile.

  • Repokey is easier to restore, entering a password is enough.

  • Repokey needs a strong password. Key material is stored together with the data.

See Offline key security in Borg’s documentation

Data integrity verification

Server-side checksums:

A simple data integrity check can be ran on the server (without transfering any data):

borg check <repo_location>

This command can be triggered by the client or run directly on the server. For security reasons, when running the command on the server directly, you should ensure command is executed with limited privileges.

Borg uses rather weak CRC32 checksums for this purpose. It’s also worth noting that checksums are calculated after encryption and transfer. Thus, errors resulting from these steps may not be cought.

Client-side checksums:

An integrity check using strong cryptographic checksums can be ran on the client:

borg check --verify <repo_location>

Note that this will transfer all data to the client for decryption and verification.

Corruption

The manifest is vital in order to be able to access the repository. It contains the encryption key, scrambled by a KDF, which required to decrypt any of the content. Consider backing up this file.

As result of deduplication, there is no redundancy within the archive. If a chunk of a file is currupted, there is no secondary copy. Also, for files that have not changed between the oldest and newest backup available, there won’t even be a older version of the file available.

The client cache contains the ctime, inode number and other information that allows Borg to detect when a file was changed and needs to be re-read. However, it’s possible that an incorrect data is returned from disk or that corruption happens before calculating the a checksum for a data chunk. If this happens, the corruption will remain undetected until the file is modified. To avoid this, you can remove the local cache to force re-reading all files:

borg delete --cache-only <repo_location>

Environment Variables

Env. Var.

Description

BORG_REPO

Repository path used when none is specified on command line.

Example 1:

host:/path/to/repo

Relative paths are relative to user’s home directory.

Example 2:

ssh://user@host:port/path/to/repo

BORG_PASSCOMMAND

Command used to retrieve passphrase.

Example:

cat $HOME/.borg-passphrase

Note

  • Set proper permissions on passphrase file:

    chmod 600 $HOME/.borg-passphrase
    
  • Command is not executed in shell. When using $HOME ensure it’s expanded properly before the env. var is set.

BORG_PASSPHRASE

Passphrase

Example:

<a secret passphrase>

Note

This is an alternative to setting the passphrase via BORG_PASSCOMMAND mentioned above. The inheratory nature of environment variables means they can easily leak into the wrong places. Make sure env. var is not set globally on the host but rather only for users that need it. It also worth noting that it likely leak into sandboxed processes running as the same user.

BORG_HOST_ID

ID used to identify host when locking on filesystem is broken or unavailable.

Example:

unused

Setting this to a static string is virtually always the right thing.

Important

⚠️ Always set this! ⚠️

Set this on the client and server. The server uses this during repository locking and the client during cache locking.

You are strongly encouraged to set BORG_HOST_ID statically. This ID is only needed if the repository resides on a filesystem that, in turn, resides on the network, is accessed by multiple hosts and doesn’t support proper locking.

Borg tries to set this based on the hosts MAC. Thus, the ID mechanism is broken when the MAC isn’t stable. Which is often the case for VMs, Docker, sandboxes and many other environments. Unfortunately, more often than not, leaving this unset breaks Borg’s locking mechanism. That is, the ID changes on reboot or crash and then the resulting stale lock can only be removed manually via borg break-lock.

See also Environment Variables in Borg’s documentation.

I/O Errors

When reading a segment results in an I/O error, ddrescue(1) can be used to try to recover as much of a segment as possible:

ddrescue <borg_repo_dir>/data/<no>/<segment_no> <borg_repo_dir>/data/<no>/<segment_no>.restored
mv <borg_repo_dir>/data/<no>/<segment_no> <borg_repo_dir>/data/<no>/<segment_no>.orig
mv <borg_repo_dir>/data/<no>/<segment_no>.restored <borg_repo_dir>/data/<no>/<segment_no>

ddrescue will try to recover any part of the file that can be read, leaving zero-bytes where content can’t be read. Repair mode can then be used to find and recover valid chunks within the affected segment(s):

borg check --repair <borg_location>