dropbox-sdk-java #57: Trust anchor for certification path not found
Android silently dropped certificates from the pinned PEM file in SDK 2.0.4; only one of two requestors pinned, so it escaped testing. Fixed in 2.0.5 four days later.
Between 2014 and 2026 Dropbox moved logic out of Python, into and back out of shared C++, and into Rust, while keeping a public API that six SDKs in six languages are regenerated from. This guide reconstructs the decade from the artefacts Dropbox published (repositories, slide decks, issue threads and registry records) and asks one question of each change: where did the languages meet, and what did that seam cost when something underneath it moved?
The problem, stated without any language's name: a growing engineering organisation keeps discovering that the language it wrote a component in is wrong for that component, and has to decide where the boundary between old and new code will sit, who generates it, and what crosses it.
Dropbox is a useful company to read this way because it made every kind of language-boundary decision in public, and then reversed several of them in public. In 2014 two of its engineers told CppCon that the mobile Camera Uploads feature had "shipped 4 months apart" on iOS and Android, built by "~3 engineers", and that the answer was a shared C++ core behind a generated bridge called Djinni. In the same year a separate team started an interface definition language, first called Babel and later Stone, to describe the public HTTP API once and generate clients from it. Inside the company the server and desktop code was Python, and Dropbox paid for a large share of mypy's early development to type that Python in place rather than rewrite it. In 2016 the storage system started moving bytes through Rust.
Ten years on, the record sorts these bets cleanly. The schema-described network boundary is alive and automated: a bot updated the API specification on 9 October 2026 and regenerated five of the six official SDKs the same day. The in-process bridge was declared unmaintained in April 2019. The typing work became a language standard (PEP 484) and a compiler (mypyc), and the temporary scaffolding Dropbox built around it was left to stop. The C++ JPEG codec was archived after a denial-of-service report, and the successor its deprecation notice recommends is a Rust port whose stated reason is safety.
The part of the API boundary that broke every old client was the part the schema did not
describe. Stone's own design note says third-party integrations "may never be updated" and
codifies which spec changes are safe for them; Dropbox followed it for a decade. But the
Python SDK's first commit, on 15 June 2015, also shipped a pinned CA bundle,
trusted-certs.crt, and the Java SDK pinned too. Nothing in the specification
language could see that file. The pin was removed in April and May 2024, and in January 2026
every Python, Java and .NET SDK released before the removal stopped working.
Scope. This guide covers the boundaries between languages and between
Dropbox and the code that calls it: the mobile bridge, the API description language and its
SDKs, the typing of Python, and the move of byte-handling code to Rust. It does not cover
Magic Pocket's storage design, the sync engine's internals, Dropbox's data centres or its
costs, because none of that is reachable from this session's corpus (GitHub and package
registries only; dropbox.tech, USENIX and video hosts did not resolve). Where a
reason for a decision appears only in a blog post this session could not fetch, the guide says
the reason is not in the record rather than supplying it.
Reconstructed from the artefacts, Dropbox's languages met in four kinds of place. They differ in one property that turned out to decide their fate: whether the thing crossing the seam was described by a schema, or was shared code.
Stone's first commit is dated 21 July 2014, and within a month its authors were merging from a
repository named babelsdk; the Python SDK's first commit in June 2015 still ships
files called babel_serializers.py and babel_validators.py. The
specification itself lives in its own repository, dropbox-api-spec, as a set of
.stone files (files.stone, sharing.stone,
auth.stone and so on), first committed on 19 June 2015. Each SDK pulls it in as a
git submodule. Since October 2020 SDK updates have arrived as commits titled "Automated Spec Update",
first pushed by an engineer, then by a DropboxBot account (40 such commits in the Python SDK),
and since 29 July 2026 by a pair of bots: one commits to the spec, the other regenerates each
SDK. The spec repository took 39 commits in 2026 to mid-October.
What makes this seam durable is written down. Stone's evolve_spec.rst, added on
2 March 2017, frames compatibility as a sender and a receiver either of which may be newer, and
gives the reason plainly: "The owner of the API does not have control over 3rd parties that
have integrated their software at some point in the evolution of the interface. These
integrations may never be updated." It then lists the changes that break an old receiver
(removing a struct field, changing a field's type, adding a tag to a closed union) and the ones
that do not (adding a route, adding an optional field). That list is the whole contract. Note
what it is about: fields, unions and routes. It says nothing about transport or trust, and
section 04 shows why that gap mattered.
The mobile seam made the opposite choice on the property that matters. The 2015 Djinni talk states the design principle in two lines: "Method calls pass control between languages. Not serialized RPC." One interface written in Djinni's IDL produced C++, Java and Objective-C abstract classes plus "(4 files) JNI bridging" and "(4 files) Objective-C++ bridging". The generator removed the boilerplate the 2014 team had complained about (five extra places to touch per function), but the seam still sat inside one process, so a defect could live on either side of it, in either toolchain, and the 2014 Allain and Twyman deck already listed "General: inconsistent debugging" among the costs. The measured wins in 2014 were real and platform-specific: on a Nexus 4, "~500k objects: 500 MS GC!", and holding 30,000 photos in C++ made "300k objects disappear from the GC's view". Those numbers describe Android's 2014 runtime, which matters for reading the reversal.
For the server and desktop Python, Dropbox did not move the boundary between languages; it
put a new boundary inside the language. PEP 484, created on 29 September 2014 and co-authored by
mypy's creator Jukka Lehtosalo, says it is "strongly inspired by mypy" and takes optional
annotations "from the idea of gradual typing". Commits to mypy from a Dropbox address went from
116 in 2014 to 321 of 1,442 in 2015 and 297 of 976 in 2016, a floor, since several core
developers committed from personal addresses. Around that core Dropbox built scaffolding:
pyannotate (2017) inserts annotations from types observed at runtime;
sqlalchemy-stubs (2018) supplies stubs and a mypy plugin because "SQLAlchemy uses
some Python 'magic'"; mypy-protobuf (2017) generates type stubs from protobuf
schemas. Then the annotations became an input to a compiler: mypyc, whose README says "the mypy
project has been using mypyc to compile mypy since 2019, giving it a 4x performance boost".
PyPI confirms the date: mypy 0.700, uploaded 3 April 2019, is the first release with
CPython-ABI wheels, and release 2.4.0 ships 50 compiled wheels out of 52 files.
Where code parsed or shuffled large volumes of bytes, Dropbox changed the language outright.
The pb-jelly README says Dropbox's Rust protobuf generator was "initially written in 2016 to
satisfy the need of shuffling large amount of bytes in Dropbox's Storage System (Magic
Pocket)", replacing rust-protobuf to avoid serialising twice, and is now used in
"our Sync Engine". The Rust Brotli codec started in April 2016 and took 170 of its 387 commits in
2018 from Dropbox addresses. The C++ codec from the same period, Lepton, which compresses JPEGs
losslessly "by an average of 22%", did not make the transition, and its end is the clearest
single case in the corpus of why the byte seam moved.
Stone spec to six SDKs; protobuf to Rust (pb-jelly) and to Python stubs (mypy-protobuf). All three are maintained in 2026.
Evidence: spec bot commit, mypy-protobuf releases
Djinni's IDL to JNI and Objective-C++ glue around a shared C++ core. Unmaintained by Dropbox since 11 April 2019.
Evidence: maintenance note commit
pyannotate (last release 2019), sqlalchemy-stubs (last release 2021), dbx_build_tools (last commit 2023). None carries an archive banner; all stopped.
Evidence: pyannotate, sqlalchemy-stubs
Five forks, each with the choice Dropbox made, what it gave up, and the condition under which the other branch is right. Where Dropbox's own reason is not in the reachable record, the entry says so.
ca_certs parameter for callers who want to pin.trusted-certs.crt in Python; pinning in Java's standard requestor).| Decision | Chosen | Rejected or reversed | Because | Evidence |
|---|---|---|---|---|
| Mobile logic | Shared C++ via Djinni, 2014 | Reversed by 2019 | Reason not in reachable record; Dropbox commit share fell first | djinni dfb9982 |
| Public API clients | Stone spec, generated SDKs | Hand-written SDKs | Third parties "may never be updated" | evolve_spec.rst |
| Server Python | Gradual typing, then mypyc | Bulk rewrite | Running code stays running | PEP 484 |
| Byte-handling code | Rust | C++ (Lepton) | Hostile-input fixes outran capacity | lepton notice |
| SDK trust anchors | Platform store, opt-in pin | Pinned CA in package | Old pins break on rotation | sdk-python #489 |
Five incident records, from issue threads, a security report and two README notices. None is a formal postmortem document; Dropbox's public postmortems live on a blog this session could not reach. Read them as the incident trail the repositories kept.
encode_eobrun.The sequence below is the January 2026 failure path. It is the most instructive record in the corpus because it was preventable by the company's own written rule and was still not caught, since the rule's vocabulary (fields, unions, routes) had no word for a file of certificates.
Every figure below was computed in this session from a repository or a registry, with the date it describes. Commit shares count author addresses containing "dropbox" and are floors.
| Metric | Value | At | Context | As of | Source |
|---|---|---|---|---|---|
| Life of the pinned CA file in the Python SDK | 8 yr 11 mo | dropbox-sdk-python | Added 2015-06-15, removed 2024-05-09 | 2024 | 41e4b00 |
| Fixed release to forced break | ~19 months | Python, .NET | 12.0.2 on 2024-06-03 and .NET 7.0.0 on 2024-05-22, break in January 2026 (arithmetic) | 2026 | PyPI, NuGet |
| SDK languages that broke | 3 of 6 | Python, Java, .NET | JS, Swift and Obj-C READMEs carry no January 2026 notice | 2026-10 | .NET README |
| Spec commits in 2026 | 39 | dropbox-api-spec | Jan to mid-Oct; spec bot active since 2026-07-29 | 2026-10 | 3c363a5 |
| Stone releases on PyPI | 27 | stone | 0.1 on 2016-06-03 to 3.5.5 on 2026-09-23 | 2026-09 | PyPI |
| JS SDK versions published | 155 | npm dropbox | Latest 10.47.0 | 2026-09 | npm |
| Lepton average saving | 22% | Lepton | Dropbox's claim, lossless on JPEG | 2016 | README |
| Report to archive, Lepton | 88 days | Lepton | 2022-11-18 to 2023-02-14 | 2023 | #158 |
| Android GC pause, 500k objects | 500 ms | Nexus 4 | The 2014 case for C++ on mobile | 2014 | CppCon 2014 |
| Djinni Dropbox commit share | 41% to 10% | djinni | 93 of 229 in 2015; 4 of 39 in 2017 | 2017 | djinni |
| mypy Dropbox commit share | 30% to 0% | python/mypy | 297 of 976 in 2016; 0 of 862 in 2025 | 2025 | mypy |
| mypy speed-up from mypyc | 4x | mypy | Project's own claim; compiled since 0.700, April 2019 | 2021 | mypyc README |
| Rust Brotli, Dropbox share | 44% to 0% | rust-brotli | 170 of 387 in 2018; 0 of 153 in 2024 | 2024 | crates index |
The 22% and 4x figures are their projects' own claims and no independent measurement was reachable. Commit shares undercount Dropbox, because engineers commit from personal addresses (mypy-protobuf's maintainer switched from a Dropbox to a personal address in 2021). The nineteen-month lead time is arithmetic on release dates, not a measurement of how many integrations broke; Dropbox has not published that number in any reachable source.
Thirty-one sources, graded. Issue threads and README notices that record a production failure are graded as incidents; there is no blog or paper tier because neither host class resolved from this session.
Android silently dropped certificates from the pinned PEM file in SDK 2.0.4; only one of two requestors pinned, so it escaped testing. Fixed in 2.0.5 four days later.
A developer quotes Dropbox's email warning of a server certificate switch on 1 August 2016 because SDKs pin; the maintainer confirms v2 needs no action.
The banner states the outcome flatly: SDKs older than 12.0.2 no longer work with the API servers.
Same cut-off for Java, plus the new rule that callers who want pinning supply their own pinner.
A crafted JPEG yields a LEP file whose verification round-trip loops forever. Repository archived 14 February 2023.
"We no longer have sufficient resources to adequately support this project"; internal use unaffected; users pointed to a Rust port, Brotli, WebP or JPEG XL.
"A port of the C++ Lepton JPEG compression tool that was released by DropBox", motivated by "the safety features that Rust has to offer" after "an exhaustive security check on the C++ code".
Removes the bundle, falls back to the platform's verification, keeps ca_certs for callers who want to pin, and drops a runtime dependency on setuptools.
Merged the day v7.0.0 was tagged, with no description; the commits restore an opt-in pin for standard connections.
The first commit already contains trusted-certs.crt next to the Babel-generated serializers and route modules.
The third SDK with the January 2026 cut-off; the fixed 7.0.0 was published on 22 May 2024.
Sender and receiver may each be newer; third-party integrations "may never be updated"; explicit lists of breaking and non-breaking spec changes.
First commit 21 July 2014; merges from a babelsdk repository in August 2014; 27 PyPI releases since 2016.
A bot has committed .stone changes since 29 July 2026; SDKs consume the repository as a submodule. 39 spec commits in 2026 to mid-October.
One of five SDK regenerations on 9 October 2026; the first commit with that title in Python and JS is dated 5 October 2020, pushed then by an engineer.
Camera Uploads "shipped 4 months apart" by "~3 engineers"; without a bridge tool, one function meant code in "5 places"; GC pauses of 500 ms at 500k objects on a Nexus 4.
The bridge is "Hard to get right" and "Filled with boilerplate"; "General: inconsistent debugging"; the goal stated as "zero cost for cross-platform dev".
Djinni's principle: "Method calls pass control between languages. Not serialized RPC." One interface yields eight files of JNI and Objective-C++ glue.
"This repo is stable but no longer maintained by Dropbox", committed by one of the 2014 and 2015 speakers.
Co-authored by mypy's creator; "strongly inspired by mypy"; gradual typing so annotations can be added one function at a time.
Dropbox-addressed commits peak at 321 (2015) and 297 (2016), then fall to zero in 2025 and 2026 while total yearly commits stay in the high hundreds.
mypy compiled by mypyc since 2019 for a claimed 4x; 0.700 is the first release with CPython-ABI wheels.
The plugin "cannot be maintained across constantly changing releases of mypy"; replaced by native, PEP 484 compliant declarative typing in 2.0.
Stubs plus a mypy plugin for SQLAlchemy's "magic"; last release 0.4 on 12 January 2021, last commit November 2022.
Infers annotations from runtime call arguments and return types; last release 1.2.0 on 16 September 2019.
Schema-to-stubs generator; 46 releases, latest 5.1.0 in April 2026; zero Dropbox-addressed commits since 2022, maintained by its original author from a personal address.
Rust protobuf code generation written in 2016 for Magic Pocket to avoid double serialisation; now also in the sync engine.
Latest crate 9.0.0; 170 of 387 commits Dropbox-addressed in 2018, none of 153 in 2024.
Bazel rules for "hermetic Python binaries" with an interpreter built by Bazel; 326 of 330 commits in 2020 from Dropbox; last commit August 2023.
Dropbox's Gradle plugin for testing only changed Android modules, archived and handed to Flo Health in June 2026.
Last pseudo-version 23 June 2023; 255 of 318 commits Dropbox-addressed in 2014; no deprecation notice in the README.
Six rungs, from a toy IDL to an audit of the clients you have already shipped. Each one reproduces a decision from this corpus.
Install Stone from PyPI, describe one struct and two routes, and generate Python and JavaScript clients with its built-in backends.
Done when: both clients call a local stub server. Teaches: the spec, not either client, is the artefact you version.
Apply each change in evolve_spec.rst's two lists to your spec and run an old
generated client against a new server, and the reverse.
Done when: you have a table of which changes broke which direction. Teaches: compatibility is sender and receiver, not version numbers.
Ship the client with a bundled CA, then reissue the stub server's certificate from a new
CA. Repeat with the platform trust store and an opt-in ca_certs.
Done when: the pinned client fails before any route runs and the other does not. Teaches: the failure in section 04, in ten minutes.
Take an untyped Python module, collect runtime types with pyannotate under its tests, apply them, and get mypy clean. Write the date you will delete any stub or plugin you added.
Done when: mypy passes in strict mode for that module. Teaches: migration tools are meant to stop.
Run mypyc on the typed module and benchmark it against the interpreted version.
Done when: you have your own speed-up figure to set against the claimed 4x. Teaches: types as a performance input, not only a safety one.
For every SDK or agent you distribute, list what it carries that your IDL does not describe: CA files, hostnames, TLS settings, retry constants, feature flags. Give each an owner and a rotation plan.
Done when: each item is in the contract, is fetched at runtime, or is deleted. Teaches: the production version of rung 3.
The commands and queries that produced this guide. The git ones work from any network that can clone GitHub.
git clone --filter=blob:none --no-checkout https://github.com/dropbox/djinnigit log --since=2015-01-01 --until=2015-12-31 --format=%ae | grep -ci dropboxgit show HEAD:README.md | grep -iE 'archiv|deprecat|no longer|maintenance'git log --format='%ad %h %s' --date=short --grep='Automated Spec Update'repo:dropbox/dropbox-sdk-java certificate pinningrepo:dropbox/dropbox-sdk-python certificate"stopped working in January 2026" dropbox sdk"no longer maintained by Dropbox"curl -s https://pypi.org/pypi/mypy/jsoncurl -s https://proxy.golang.org/github.com/dropbox/godropbox/@latestcurl -s https://api.nuget.org/v3/registration5-semver1/dropbox.api/index.jsoncurl -s https://index.crates.io/br/ot/brotligit clone --depth 1 --filter=blob:none https://github.com/CppCon/CppCon2014git ls-tree -r --name-only HEAD | grep -i dropboxDjinni "not serialized RPC" CppCon 2015site:dropbox.tech api-server-certificate-changesRead across ten years, Dropbox's record makes one claim with unusual clarity. Language seams that crossed a network and were described by a schema lasted; the generators behind them (Stone, pb-jelly, mypy-protobuf) are still shipping in 2026, and mypy-protobuf is maintained by its original author from a personal address, four years after his last Dropbox-addressed commit. Seams that shared a process (Djinni) or shared a parser of hostile input (Lepton) were retired once the people who could debug both sides moved on. Scaffolding around a migration (pyannotate, sqlalchemy-stubs, the internal build mirror) stopped, mostly without saying so, which is what finished scaffolding should do.
And the one break that reached every unupgraded customer did not come from any of those. It came from a file that sat beside the generated code in the SDK's first commit, outside the schema's vocabulary, and therefore outside the compatibility rules Dropbox wrote and honoured. The design note was right that clients "may never be updated". It applied that insight to fields and routes and not to the trust anchor.
Your API contract is everything your client package carries, not only what your IDL can express. Put each language seam where a schema can describe what crosses it; when you must share a process, make sure the people on both sides can debug it; give every piece of migration scaffolding an end date; and before you ship a client you cannot force-upgrade, list every hand-written constant in it (certificates, hostnames, TLS and retry settings) and either move it into the versioned contract, fetch it at runtime, or delete it. The cost of skipping that list is paid years later by customers you cannot reach.