Security
What buckets protect: your files and their copies in your
own clouds, against losing the last good copy, and the keys to those
clouds. Files go straight between this machine and your clouds. lnk bucket talks to the relay only for share links.
Buckets use your own cloud accounts and bills; Local Link holds no storage for you.
Cloud keys
- Keys are in
~/.config/lnk/bucket/rclone.conf: mode 600, in a 700 folder. Link's own edits are atomic and made under a lock. rclone rewrites the file itself when a sign-in token refreshes. - Secrets are typed at a prompt with echo off.
connect <type> key=valuerefuses any setting rclone marks secret or sensitive, such as passwords, keys, account ids and hosts; rclone then asks for them. - On AWS and Google Cloud, a bucket signs in as its
lnk cloudaccount and nothing else (how accounts are kept).rclone.confholds none of that sign-in. Each rclone run on the bucket gets the account's fromlnk cloud: its aws profile's name or its access key, or its Google credential file, asRCLONE_CONFIG_<SECTION>_<KEY>variables, or asGOOGLE_APPLICATION_CREDENTIALSfor a person's Google login. - For those runs the shell's own
AWS_*andGOOGLE_APPLICATION_CREDENTIALSare removed, so the account used is the one connected, never whatever the shell says at that moment. - A cloud connected before this moves onto an account on its first use,
from the same login, with nothing asked. An access key reaches
lnk cloud connecton its input, never on a command line, which other local users can see. The copy of the login leavesrclone.conf(and the Keychain) only once the account is shown to reach the bucket; otherwise nothing changes and the account made for it goes again. A Google sign-in rclone saved itself can't move, and stays in the file. - Encryption passwords are kept in the same file, obscured. That is not
encryption: the file's permissions protect it. It's the only stored
copy.
lnk uninstallkeeps it unless you pickbuckets, and says so. - On a Mac, the cloud keys and encryption passwords Link writes are in
the Keychain instead (secrets in the Keychain),
and
rclone-keychain.tomlbesiderclone.conflists which. Eachlnk bucketcommand reads them from the Keychain once, holds them in memory while it runs, and passes them to rclone in its environment (RCLONE_CONFIG_<SECTION>_<KEY>) for each run. A sign-in rclone saves itself, such as a Google Drive token, stays in the file. - Other storage (
connect b2,drive,sftp, ...) is set up by rclone's own questions; its keys or sign-in tokens go in the same config file.connectrefuses rclone types that wrap other remotes (crypt,alias,union, ...) or aren't storage (local,http,memory).
Files Link keeps
All in ~/.config/lnk/bucket/:
clouds.toml: which clouds are connected, and which bucket is which folder's.rclone.conf: cloud keys and encryption passwords (see above).manifest-<personal or agent id>.json: what went where, each file's size and modification time, and when each copy was last checked.engine: the pinned rclone.- Each rclone run gets its file list in a temporary file, mode 600 and created new, deleted afterwards.
A folder cloud's backup folder is created 700.
The engine
- rclone at a pinned version, downloaded over HTTPS from its GitHub releases and installed only if its SHA-256 matches the one in the plugin's source. Archives over 100 MB are refused.
- The pinned sums come from rclone's
SHA256SUMS, checked against its release key's PGP signature when pinned. - It runs with its own config file and every
RCLONE_*variable removed. - File names are always passed to rclone after
--or in a file list, never where rclone would read them as options. LNK_RCLONEpoints at another rclone, for tests. Whoever can set it can already run programs as the user.
Keeping a good copy
What clean guarantees
At the moment it runs, every connected cloud has a copy of each file that matches the file here, and every file was reported on. The check is MD5 on S3 and GCS, through the encryption for encrypted clouds, or a byte-for-byte read where the copies have no checksum, encrypted or not. That catches missing, truncated and corrupted copies.
- On S3, a checksum the uploader set is not taken. S3 computes an
object's MD5 only for an upload in one part, in its ETag. For an
upload in parts, rclone takes the MD5 from the object's
md5chksummetadata, which whoever uploaded it sets and S3 never checks. Anyone who can write to the bucket could replace a file with junk carrying the original's MD5, an agent's box with the bucket's key included. So on aws, s3 and rclones3clouds,cleanfirst asks for each object's metadata (one request per file) and reads back every copy that carries an MD5 there, through the encryption too. - A copy is proved once. On those clouds, what a
cleanproved (its dry run included) is kept in the manifest: the file here, by its size, inode and modification time to the nanosecond, and its copy, by its size and the time S3 wrote it, from one listing that reads no file. Only S3 sets that time, so a copy written since, forged or not, shows another and is checked again. A latercleanfinding both as they were takes the copy as proved, without asking for its metadata or reading it back. The first check of a copy still asks for its metadata, one request per file: rclone shows an object's ETag only through it. - A copy written during the check isn't taken. The copies are listed again after the check; one written in between, to swap a checked copy for junk carrying its MD5, is reported as written while it was checked, and nothing is deleted.
- A "copy" in a folder cloud that is the file itself is refused: the backup folder being a link back into Link's folders, or a hard link.
- Every command refuses a folder cloud whose backup folder has come to overlap Link's folders.
- A file changed or replaced after the check, or now reached through a link, is kept.
Reading back costs what a download does, and takes as long, once per
copy: a dry run then a clean reads it once. rclone sends a file over
200 MiB in parts, with its MD5 in that metadata, so those are the
copies read back. An archived copy can't be read until
it's restored, so clean keeps such a file here.
push takes the checksum the cloud reports, so it doesn't notice a
copy replaced this way and doesn't send the file again, --replace
included: reading every copy back on every push would cost a download
each time. clean finds it and says it differs. To send the file again
regardless of checksums:
lnk bucket push photos/2019 --all --replace -- --ignore-timesIt does not protect against a cloud losing data later, a closed cloud
account, lost keys or a lost encryption password, or a cloud that sets
out to deceive: the provider writes the ETag too, and MD5 is not
collision-resistant against an attacker. On other storage rclone
reaches, such as Backblaze B2 or Azure, the checksum of a large upload
can also be the uploader's word, and clean takes it.
Local files
- Bad local files can't overwrite good copies.
pushrefuses to replace a copy that differs unless--replace, so files corrupted or encrypted by ransomware here are reported instead of sent. Files never pushed are only as safe as this machine. pushtrusts size and modification time. A file with the same size and modification time, to the nanosecond where the file system keeps them, as when its copy was checked is skipped unread. A file changed without either changing, or a copy damaged in the cloud since, goes unnoticed bypush;push --allandcleancheck every file.- Names clouds store differently are skipped. A file whose name has control characters, or the look-alike characters rclone uses for them, is never pushed or cleaned, so two such files can't be taken for one another.
- Links stay inside the folders.
pushandcleanskip symlinks.pullrefuses any file whose way from the folder goes through a link, which could lead outside it, for example into~/Library/LaunchAgents. - The folders are kept apart. Link refuses to work when your files' folder and an agent's are one inside the other, or when either is or holds your home folder or Link's settings. When the harness plugin is installed but can't list the agents, it stops too, rather than take an agent's folder for yours.
Deleting from clouds
remove counts a copy in another cloud as kept only if a listing of
that cloud, made now, has it at its recorded size.
Who can read the files
- Without
--encrypt: the cloud provider, and anyone with access to the bucket. Buckets Link makes are private, GCS ones with uniform bucket-level access. - With
--encrypt: rclone'scryptencrypts contents (XSalsa20-Poly1305) and names before upload. - Bucket names say nothing about you:
local-link-and 12 random characters, since bucket names are global and anyone can test whether one exists. - Labels do. An agent's bucket, in a cloud from
lnk cloud, is labelledlnk-agent(its ID),lnk-agent-name(its name) andlnk-owner(github-<your GitHub user id>). Anyone who can list the account's buckets can read them, as with a box's tags.
Agents' files
- Each agent's files have their own bucket, always encrypted, apart from yours. Link backs both up with the key you connected, which reaches both.
cleanandpullrefuse in an agent's folder while the agent runs, in the background or in a terminal, and when the harness plugin can't say whether it does.cleanasks again right before deleting, and refuses if the harness plugin doesn't answer then.- Reconnecting an encrypted bucket needs its password, and
connectrefuses one that decrypts none of the names already there. - When an agent moves here from a box, the box names its buckets. Link
takes only a name it gives an agent's bucket, in any cloud:
local-link-and 12 random characters, orlocal-link/<agent id>on a folder. In a cloud fromlnk cloud, the bucket must also be labelled as that agent's. So a box can't point an agent's backups at another bucket or folder of yours. - Finding an agent's buckets by their labels takes, when you're logged
in, only buckets labelled with your login's
lnk-ownerforlnk agent restore. A box's keys, revoking them and a move's buckets also take one with no owner, made before you logged in, and never one labelled as another login's. Labels are only what anyone who can make buckets in the account says, so two buckets labelled as one agent's in one cloud are refused, both named: delete the one Link didn't make.
lnk bucket rclone is unguarded
It runs the pinned rclone with Link's config, so it can do anything the
connected keys allow, including sync and purge, which delete. Link's
refusals apply only to its own commands.
Share links
lnk bucket share hands out files from your clouds with a link,
https://<user>.<domain>/b/<id>.
- The files never pass through Local Link. Each file's cloud signs a URL for it on your machine, with your keys: S3 presigned URLs, through rclone. The relay keeps those URLs and gives them to whoever opens the link, who downloads from the cloud. Your cloud keys never leave the machine.
- Anyone holding a signed URL can download that file until it
expires, whatever happens to the link.
lnk bucket unshareends the link at the relay, not URLs already handed out. A share lasts at most 7 days, S3's longest, and each URL expires with it. - Credentials that expire sooner sign shorter URLs. URLs signed with
an SSO or
aws stssession stop working when that session ends. - The relay's operator can read the URLs, as they can read tunnel traffic.
- Only your files, unencrypted, in S3 storage. An agent's files are refused: their buckets are encrypted, and a link would hand out ciphertext or need the password. So are encrypted clouds, archive tiers, and clouds that can't sign expiring links (gcp, folders, other rclone storage).
- Links are unguessable: 24 random letters and digits, about 124 bits, on the sharer's own host, which gets a certificate only while they have a live share.
- The page is Local Link's own and static. Names are escaped, a
Content-Security-Policy lets nothing run or load, and it's sent
no-storeandno-referrer, so the URLs aren't cached or passed on. It says who shared it and that Local Link doesn't check the files. - Only URLs shaped like S3's. Over HTTPS the relay takes a file's
URL only with a whole SigV4 presigned query:
X-Amz-AlgorithmofAWS4-HMAC-SHA256,X-Amz-Credential,X-Amz-Date,X-Amz-SignedHeadersand a 64-digit hexX-Amz-Signature. That keeps out plain links, but any host can be S3-compatible storage, so a sharer can still make a URL of that shape to any site. - The visitor warning. So a share page of an
open-signup user shows the
warning their tunnels get before the page, once a week per browser.
lnk bucket pulland programs asking for JSON pass. Invited users' share pages show none. - A password (
--auth user:password) is checked by the relay with HTTP Basic before anything is shown, and kept only as a salted PBKDF2-HMAC-SHA256 hash. Wrong guesses are limited per visitor and per link, as for password-protected tunnels. Visits and password checks have the relay's limits. - Your account, not your login token. The bucket plugin gets a token
from
lnk auth token --jsonthat works only for the share API (and/_link/whoami, which says whose it is) and expires in 10 minutes. It's HMAC-signed with a key the relay makes at each start. A blocked user's links stop working, and their short tokens are refused. - Pulling a share (
lnk bucket pull <link>) writes into the current folder only. Names are relative paths without.., a name reaching through a link here is refused, an existing file is kept unless--replace, and each file is written beside its place and moved there only once its size matches the share's. It reads the link only overhttps://, so a password never crosses the network in clear, and fetches each file only overhttps://, from a public address, without following redirects: a share can't make your machine reach its own services or your network's. A share from a relay on your own machine (a dev relay) is the one exception.
Gaps
- Keys and passwords are kept in a file, and one key reaches both your bucket and your agents'. See known limitations.
- Archive tiers exist only on aws and gcp. Other storage classes need
lnk bucket rclone.
