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>
The race suite ran under Go's default 10 minute limit, which a slow
runner would hit; it now has 40 minutes in a 60 minute job. Go is the
1.27 line the code was verified with, not go.mod's minimum, so
govulncheck checks a patched standard library, and govulncheck is
pinned to v1.9.0 so a new release can't break the build.
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 owner added the rule; docs/PERMISSIONS.md already listed it in the
Allow tier. Also record that the force-with-lease deny rule misses the
`--force-with-lease=` form.
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>
Add the TestProtonDriveFake init.d script, which builds and starts
protonfake on port 28642 and passes on the session it prints, and a
test_all entry for it, so fs/operations, fs/sync, vfs and cmd/bisync
run against the fake server. Five tests that need a second Fs of the
same account to see what the first wrote are ignored, as each Fs
caches separately, and cmd/gitannex is skipped as for every init.d
remote; both are in docs/TODO.md.
Add a Gitea Actions workflow that builds, vets, lints, race-tests,
fuzzes and vulnerability-checks the backend, and re-verify the agent
permission table on the host toolchain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reader with only the repository walked the handoff and the remaining
plan tasks against the code. Mark the plan and spec lines that later
rulings superseded, add corrections for Tasks 18 to 21, fix stale TODO
and PERMISSIONS entries, ignore the skill workspace, point AGENTS.md at
the handoff and take the test account usernames out of it.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Development of plan 001 is paused after Task 17 of 21. Record the state,
the rulings made during execution, the test setup and how to resume in
docs/plans/001-protondrive-tier1-HANDOFF.md, commit the execution ledger,
task briefs, implementer reports and reviews, and mark the plan and spec
with their current status.
Co-Authored-By: Claude Fable 5.2 <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>