Timing bounds in parallel tests leave room for a slow race runner, the
block 502 test checks its fault fired, refusedCommit's 408 exclusion is
pinned, the user docs explain the "server doesn't show it yet" error and
the conformance checklist matches the current draft and trust rules.
Every other minor from the review is listed in docs/TODO.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Log lines no longer cut a name inside a UTF-8 sequence, the "superseded
upload" error says the file's state rather than claiming a revision is
active, a test-only block decrypter moves to the tests, a one-line alias
of encryptPassphrase goes, the upload godoc is rewrapped and the fake's
server struct is gofmt clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the access token expired and the refresh request got a 5xx, 429 or
408, the operation failed instead of being retried, and a 408 even ended
the session as if the refresh token had been rejected. Such failures are
now retried like any other call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only fstest.Initialise, run by the integration test, applied
RCLONE_CONFIG, so a filtered test run read the default rclone config,
which may hold a real Proton remote. TestMain now applies it first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proton announces requirements it will enforce, such as a minimum client
version, in x-pm-drive-requirements response headers. They are now
logged at debug level whenever they change, so they show in -vv bug
reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proton's generic "requirements not met" code 2000, which it returns for
a node changed since it was read (as when a lost rename response is
resent), aborted the whole run with "upgrade rclone". It is now an
ordinary error that isn't retried. Code 2028 (a feature disabled,
rate-limited or blocked) is retried, and the codes Proton actually uses
for a rejected or missing app version, 5003 and 5001, now give the
upgrade message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file whose extended attributes don't record its block sizes, as some
other clients write, was read from the first block for every ranged
read, so a multi-thread copy, which opens each chunk as a range,
downloaded data quadratically: about 8.5 GiB for a 1 GiB file. The sizes
of blocks already read are now remembered per Fs (bounded to 16
revisions and 65536 blocks), so later chunks start at the right block.
A block list that disagrees with what was learnt is an integrity error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A remote set up with environment variables or a connection string can't
save its draft client ID, so it got a new random one on every run, and a
draft left by an earlier run then blocked uploading that name as if
another client were uploading it. When the ID can't be saved it is now
derived from the remote name and the account's volume, so it is the same
on every run. A remote in the config file still gets a random ID, saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extended attributes whose signature failed or was missing were used, so
a server could set a file's modification time, size and SHA-1 at will:
for example make `rclone sync --update` skip every changed file. Without
ignore_signature_errors they are now not used and an ERROR is logged:
the modification time is the server's, there is no SHA-1, and the size
comes from the recorded block sizes, which reads check. The file stays
readable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renaming or moving a node whose name signature failed reused the old
name's session key, which a server that made that name knows, so it could
read the new name. The new name now gets a new session key unless the
old name's signature verified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A folder's hash key whose signature failed or was missing was only
logged, and the names of everything written into the folder were then
hashed with it, so a server which made the key could test guesses at
those names. Creating, uploading, renaming, moving or copying into such
a folder is now refused unless ignore_signature_errors is set, when an
ERROR is logged once per folder. Reading is unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node or share key from the server was unlocked with whatever Argon2
parameters it carried, so a crafted key could make every listing
including it allocate up to 2 TiB and crash rclone. Node and share keys
locked with Argon2 beyond 4 passes, parallelism 8 or 128 MiB are now
refused before the key derivation runs, as an integrity error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server could replace a file's content key with one it made, without a
signature: updates were then encrypted with a key the server knows, and
ranged reads returned whatever it encrypted under that key. A missing or
invalid content key signature now makes the file unverified, like an
unverified node key: reads and updates are refused unless
ignore_signature_errors is set, when an ERROR is logged once per file and
operation. The fake gains corruptions for this and for the hash key,
name and extended attribute checks that follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captured from a throwaway test account on 2026-11-12 and re-keyed onto
test-only keys; no key, passphrase, ID or email of the account is in them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a failed commit the client read the file back and deleted the
draft if the read showed it uncommitted. Proton's server can take a
moment to show a commit, so a stale read could send a delete against a
file or revision which had in fact been committed.
The draft is now deleted only when the server refused the commit with a
4xx API error on a single attempt which was not resent; any other
failure leaves it and reports that the upload may have succeeded. A
cancelled upload whose commit succeeded stops waiting for the server to
show it at once. The fake server's read lag can also serve a file
without an active revision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The capture names its folder with -capture-remote and refuses -remote,
so a mistyped -run can't start the integration tests against the
account. Every key written has a placeholder user ID; a node key holding
another user's email, or a signature by or intended for a key not being
replaced, fails the capture without naming it. Salt notations are
written only where the original had one.
TestGoldenFixtures now also walks every key, key packet and signature of
a fixture, inside encrypted messages too, and checks folder modification
times; tampered fixtures are shown to fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proton's server can take a moment to show a change: a file read back
just after its commit may be missing or still a draft with no active
revision. Uploads then failed with "upload committed, but the file now
has another revision (revision is active ...)" or "object not found".
The read back after a commit, the read of a server-side copy and, with
enable_caching off, the read of a folder to write into now retry with
backoff for about 7 s while the change doesn't show. A committed upload
the server still doesn't show after that fails with a new "committed,
but the server doesn't show it yet" error, deletes nothing and is not
retried at once. The fake server gains SetReadLag to model the lag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestGoldenCapture (-capture DIR -capture-label NAME -remote NAME:PATH)
captures a folder another client wrote, re-keyed onto keys made for the
tests: blocks and signature-free SEIPD packets kept byte for byte, key
packets re-wrapped, signatures re-made with the same parameters (names,
hash keys and extended attributes encrypted again with their own session
keys, as their signatures are embedded), IDs and emails replaced.
TestGoldenFixtures reads every fixture in testdata/golden through the
backend from the fake server with every signature checked.
The fake server can now seed stored folders and keeps the times of
seeded nodes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file written by an old client without recorded block sizes was read
in ranges by assuming 4 MiB blocks, so a range past the first block could
return the wrong bytes, or too few, without an error. Such a range is now
read from the first block, discarding the bytes before it, with every
block still checked against its hash.
Also identify the SDK the conformance checklist was checked against by
commit, and align the enable_caching help with the docs page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrite the hand-written parts of the Proton Drive docs page for the
native client: browser and password sign-in with a transcript taken from
the current code, the third-party notice, what is verified on upload and
download, the ranged-read caveat, the trust rules and the
ignore_signature_errors option, the two protocol limits, the --dump
warning, filename rules, drafts, trash and limits. The generated options
section is left for make backenddocs.
Describe everything ignore_signature_errors allows in its help text,
update the feature list and score in protondrive.yaml, and add the SDK
conformance checklist and fork notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old libraries were only used by the differential crypto tests. Those
go, and TestFrozenFixtures moves into crypto_internal_test.go without its
-update branch, so files the old library wrote stay checked through the
frozen fixtures in testdata/oldlib. go mod tidy removes the two libraries
and the modules only they pulled in; relvacode/iso8601 leaves go.mod.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment and spec put the block metadata a full read holds at a few
hundred bytes a block; with realistic tokens and URLs the cap allows
about 250-350 MiB, and those fields and API response bodies aren't
bounded at all. TODO also asks the live check to confirm the
root-of-trust rule on a drive made by Proton's own apps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A range covering the whole file was read as a plain full read, so a
file whose recorded size understated its content returned more bytes
than the range asked for. It is now checked as a full read but stops
at the end of the range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fetching a file or folder fetches its folders up to one already known,
and a server reporting folders as each other's parents made that
recurse until rclone crashed. A cycle, or folders nested more than 2048
deep, is now an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With ignore_signature_errors set, reading a file, writing into a folder,
or moving, copying or renaming either when its key isn't verified went
ahead without a word. Each now logs an error, once per file or folder
and operation, saying what was done and why the key isn't trusted - and
that what is moved or copied out is trusted from then on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check that the drive is shared and signed by your own address
ignored letter case, but keys were looked up by exact address, so a
server naming your address in capitals could serve keys of its own for
it. Addresses are now matched ignoring ASCII case only, everywhere, and
your own address in any such case is always verified with your own
keys, never ones the server supplies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The warm listing budget, with caching on and off, reuses the folders
TestBudgetList seeds instead of seeding its own, which saves about 15s
under -race. The assertions are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an encoding which lets invalid UTF-8 through, rclone could create
a file or folder whose name Proton's clients, and rclone itself, then
refuse to read, so it vanished from listings. Such names are now
refused when writing.
Found by FuzzValidateName.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file whose extended attributes had a SHA-1 but a missing, negative or
malformed Size was listed with size 0, so copying it failed with a size
mismatch. Its size is now the sum of its recorded block sizes, if they
are usable, and otherwise unknown, as for a file without extended
attributes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A listing whose pages were read before a file or folder was made in,
copied into or trashed from the folder cached what it read when it
finished, overwriting the change. As new files are added to a cached
listing rather than dropping it, the stale listing then lasted: a file
just uploaded stayed missing, and a trashed one stayed listed, until
something else dropped the listing.
The node cache now counts changes to the children of each folder being
listed, and a listing is cached only if none happened since its first
page was read. The count is kept only while a listing is in progress.
The fake server gains a Hold fault, which answers a request with the
response made on arrival only once released, to test this without
sleeps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The password test's cleanup restored the config path before the
storage, and setting the storage does nothing while the path is empty,
so with an in-memory config the storage was left behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestPasswordStoredObscured installed the config file storage for its
temporary config and left it in place for the tests after it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full read holds the list of a file's blocks until it has checked the
signed manifest, and how long that list could grow was bounded only by
a size the server sends unsigned. A file listing more than 2^19 blocks
(2 TiB in 4 MiB blocks) is now an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request for the whole file as a range, as "bytes=0-" from serve http
or webdav clients, was read as a ranged read, which can't check the
file's signed block list. It is now read as a full read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drive's share and root folder were accepted when signed by any
address whose keys the server served, so a hostile server could vouch
for a whole drive of its own making. If the share wasn't created, or
the root folder signed, by one of the account's own addresses, reading
and writing now fail with an error saying so, unless
ignore_signature_errors is set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving or copying a file or folder out of a folder with a forged key
had rclone sign the server's key as the user's, after which it read
without error. Moving, copying or renaming a node whose key isn't
verified now fails unless ignore_signature_errors is set, and anything
beneath such a folder, including what rclone writes there with the
option set, stays unverified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Request budgets, measured against the fake server and asserted at the
measured counts, for listing (cold and warm, with and without
enable_caching), a no-op sync by modification time and by checksum,
NewObject three folders deep, uploads and making folders. A TestMain
hook fails the package if any test, through any fake server, requested
a removed share-scoped route; the fake keeps a process-wide tally of
them for it.
Fuzz targets, seeded from testdata/oldlib and from what the fake
writes, for name validation, link decoding, extended attributes, API
error bodies with Retry-After and session fork payloads. Each checks
invariants of what is accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node which decrypted was handed on whatever its link ID and encrypted
size, so a faulty or hostile server could make rclone cache a node
under an empty ID or list a file with a negative size. Such nodes are
now left out of listings and logged, as nodes with invalid names are.
Found by FuzzDecodeLink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Names, node passphrases, hash keys and extended attributes are
encrypted to public keys, so the server, or anyone, can write them as a
small compressed message which expands to gigabytes when decrypted.
They were decompressed into memory without a limit.
Names, passphrases and hash keys are now limited to 64 KiB and extended
attributes to 16 MiB, enough for the block sizes of an 8 TiB file.
Extended attributes with a negative Size are now read as having no
size, as the reference SDK reads other invalid sizes, so a file is never
listed with a negative size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Making a file or folder, or a server-side copy, forgot the cached
listing of its folder, so the next upload into that folder - which
checks the name is free - listed the whole folder again. Copying M new
files into a folder of N cost on the order of M*N/100 extra requests.
The new node is now added to the cached listing. A failed call still
forgets it, as the call may have taken effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The password test now runs rclone config with the password in plain
text and checks it is stored obscured; a test checks a file whose key
isn't its owner's can't be updated; and the log test checks every
session token the fake issued, which it now labels by kind, not only
the saved ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A token refresh still in flight when the remote was disconnected could
write its new tokens back to the config after Disconnect removed them.
Disconnect now waits for it and removes them afterwards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full read verified the manifest signature only at the end, so blocks
a server swapped, dropped or took from an older revision reached the
reader - and rclone cat or a mount - before the error. A full read now
lists every block and verifies the manifest before releasing the first
byte, and a block listed again with a different hash is an integrity
error. Ranged reads are unchanged.
The fake's "rollback" corruption is renamed "block-rollback": serving a
whole earlier revision under the current revision ID can't be detected,
which the spec and TODO now say.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server could put a folder with a key of its own making into a real
folder and serve anonymous files beneath it whose every signature
checked out against that key, so they were read without an error.
A key checked against an unverified folder key is now unverified too,
all the way down: reading such a file, or creating, uploading, moving
or copying anything into such a folder, fails unless
ignore_signature_errors is set. Listing it still works.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hostile fake server tampers with block hashes, block order and count,
serves an older revision's or another key's blocks, signs a node
passphrase with its own key and gives nodes names such as "../x", "a/b",
NUL and 10 MiB: each read fails or the node is left out, with and
without ignore_signature_errors, and a copy or sync writes nothing
wrong or outside its destination. The log at debug level, and with
--dump headers, holds no password, key password, session or storage
token, node passphrase or session key; browser sign-in leaves no
password in the config and password sign-in keeps it obscured.
The fake gains rollback and foreign-block corruptions and exposes the
secrets it issued and a file's node secrets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A call in flight when the remote was disconnected, and then rejected
as unauthorised, was sent again with empty credentials. It now fails
with the "not signed in" error instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An expired session, or one signed out with "rclone config disconnect"
while a long running rclone was using it, told the user to reconnect a
remote literally called "remote:". The error now names the remote.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>