CookieCloud: self-hosted cookie sync with a key you can recompute yourself
CookieCloud是一个和自架服务器同步浏览器Cookie和LocalStorage的小工具,支持端对端加密,可设定同步时间间隔。本仓库包含了插件和服务器端源码。CookieCloud is a small tool for synchronizing browser cookies and LocalStorage with a self-hosted server. It supports end-to-end encryption and allows for setting the synchronization interval. This repository contains both the plugin and the server-side source code
At a glance
- What is it?
- A GPL licensed browser extension and Node server that encrypts your cookies and localStorage before upload, so the server is a dumb pipe and you hold the only key that opens it.
- Who is it for?
- CookieCloud's appeal is that the security model is short enough to hold in your head: the server stores an opaque string keyed by a UUID you choose, and the encryption key is derived locally from that UUID and a password, so the operator of a public instance learns nothing about your session. That same simplicity is its limit.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 40 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What actually gets synced, and in which direction
The tool is a browser extension plus a small server, both in this repository, and the payload is more than cookies. As of plugin version 0.1.5 it also captures `localStorage` for the same domain, and that change altered the encrypted payload from a bare cookie object into a two-key structure, `{ cookie_data, local_storage_data }`. Anything that decrypts old blobs needs updating, which is the kind of break worth knowing before you point an automation at an existing account.
The same release moved configuration storage from the server back to local storage, so that remote configuration could not conflict with what the extension actually does. The README is blunt about the consequence: users of previous versions need to reconfigure their setup. That is an unusual admission for a project README to make about its own breaking change, and it tells you the sync design changed shape rather than just gaining a field.
Direction is the other limitation, and it is a real one. The FAQ states that synchronization is currently one-way: one browser uploads and another downloads. There is no merge, and no conflict resolution, which means two browsers that both write produce a last-write-wins result with no warning. For a phone that only reads, that is exactly what you want. For two machines you use equally, it is not.
Running your own server with one docker command
The server is published as `easychen/cookiecloud` and listens on port 8088. The whole deployment for the default case is:
docker run -p=8088:8088 easychen/cookiecloud:latestThe README notes the image covers `linux/amd64` and `linux/arm64`, so it runs on the two architectures you are most likely to have, including most ARM NAS boxes and Raspberry Pis. That combination is the project's real deployment story: the server is small enough to live on hardware you already own.
If you want the API under a subdirectory rather than at the root, the environment variable is `API_ROOT`, and the README is specific that the value must start with a slash:
docker run -e API_ROOT=/cookie -p=8088:8088 easychen/cookiecloud:latestFor anything you intend to keep, the compose file in the repository root is the version to read, because it is the one that persists data:
version: '3'
services:
cookiecloud:
image: easychen/cookiecloud:latest
container_name: cookiecloud-app
restart: always
volumes:
- ./data:/data/api/data
ports:
- 8088:8088The `volumes` mapping is the line that matters. Without it the server runs fine and loses everything on recreate. Note that the file in the tree is capitalised `Docker-compose.yml` while the README calls the option Docker-compose, a small inconsistency that has bitten people searching for it.
There is also a Node path for hosts without Docker, requiring Node in advance:
cd api && yarn install && node app.jsSame default port, and it honours `API_ROOT` as well.
The key derivation is fifteen lines of CryptoJS you can read
The whole security claim reduces to how the key is built, and the README prints the function rather than describing it:
function cookie_decrypt( uuid, encrypted, password )
{
const CryptoJS = require('crypto-js');
const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16);
const decrypted = CryptoJS.AES.decrypt(encrypted, the_key).toString(CryptoJS.enc.Utf8);
const parsed = JSON.parse(decrypted);
return parsed;
}So: MD5 over the UUID joined to the password with a hyphen, take the first sixteen hex characters, use that as an AES passphrase. The client does this before uploading, so what lands on disk is ciphertext. The `/get/:uuid` endpoint will hand back that ciphertext to anyone who knows the UUID, and will attempt decryption only if a password is supplied.
Two details are worth reading carefully. First, the prose above the function describes the key as `md5(uuid+password)` while the code uses `uuid+'-'+password`. Trust the code, since that is what both ends run, but if you ever compute a key by hand from the prose description you will get garbage. Second, since version 0.3.0 the project moved to encryption algorithms with a fixed IV, and the README says this is what allows more standard libraries to decrypt. The `examples/fixediv/` directory in the tree is where those implementations live, and the same release replaced the extension's build system with `wxt`.
Is one MD5 cut down to sixteen hex characters a good key derivation? It is eighty bits of MD5 output keyed by a password you choose, with no work factor. That is fine for the threat model the project actually has, where the concern is a curious server operator, and it is not fine against someone who can guess your password offline against a captured blob. Treat the password as the only real secret and make it a long random one.
Two endpoints, and the one the download side does
The API surface is small enough to state in full. Upload is `POST /update` taking two parameters: `uuid`, which identifies your slot, and `encrypted`, the string you encrypted locally. Download is `POST` or `GET /get/:uuid`, taking an optional `password`.
That optional parameter is the interesting one. Omit it and you receive the encrypted string back unchanged, which is the mode a headless browser integration wants, because it decrypts in its own process. Supply it and the server attempts decryption and returns the content. That second mode implies the server can be asked to do the decryption for a client that cannot, which also means the server does hold the password transiently in that path.
There is no authentication on either endpoint beyond knowing the UUID. Anyone who guesses or obtains your UUID can overwrite your stored cookies with `POST /update`, which is the argument for running your own instance on a network you control rather than pointing the extension at a public one.
For debugging there is no server-side log to read. The README directs you to the browser extension list, click the service worker, and a panel opens with the operation log, which is the fastest way to see whether an upload actually fired.
Chrome and Edge are supported, Firefox is a compile away
The FAQ is specific: the extension officially supports Chrome and Edge, other Chromium-based browsers might work but have not been tested, and for Firefox you are expected to build it yourself. The command given is:
cd extension && pnpm build --target=firefox-mv2Underneath that command is a real constraint rather than a packaging inconvenience. Firefox's cookie format differs from Chrome's and the two cannot be mixed, so a build target is not enough on its own: syncing a Chrome-uploaded blob into Firefox is not a supported path even though both sides speak the same API.
The `extension` directory in that command does not match the repository tree, which lists `ext/`. The same mismatch appears later in the README, which points at `extension/function.js` for more of the encryption implementation. The tree is the authority on layout, so expect to go looking for the file yourself. Worth knowing because it is the kind of small documentation drift that makes a project feel less finished than it is.
Installations from the Edge Store and Chrome Store are both linked, with a caveat that store versions can lag because of review processes. Manual download from a release is the alternative. Three releases are recorded: `release-v1.0.2` and `release-v1.0.3` both on 2026-05-03, the earlier one bundling the wxt rewrite along with custom service port support and retrieval of partitioned cookies, and a `0.3.1 beta` from 2025-09-17 fixing a `setBadgeText` error on Firefox. The last push was 2026-08-27.
The examples show what people actually build on it
Three example directories sit in the tree and they answer the question the README does not: what is this for beyond moving your own logins to a phone. They are a Python decryptor, a set of fixed-IV reference implementations, and a Playwright suite.
The Playwright example is the clearest statement of intent. It reads and decrypts the cloud cookie, injects it into a fresh browser context, and then browses normally:
test('Access nexusphp using CookieCloud', async ({ page, browser }) => {
const cookies = await cloud_cookie(COOKIE_CLOUD_HOST, COOKIE_CLOUD_UUID, COOKIE_CLOUD_PASSWORD);
const context = await browser.newContext();
await context.addCookies(cookies);
page = await context.newPage();That is a scriptable session as a service: a headless browser that arrives already authenticated, sourced from a cookie jar you control on your own hardware. For automation against sites where logging in through a UI is slow or blocked, it removes the most fragile part of the job.
It is also the strongest reason to be careful about which instance you use. A session cookie for an authenticated service is a bearer credential, and pointing a script at a third party server means that server operator could substitute the cookie jar for a whole automation run. The README says as much in its own voice, calling the official test server for testing purposes only with stability not guaranteed and recommending you run your own to further enhance data security, and it lists ten community servers with the same warning attached.
Editorial conclusion
CookieCloud's appeal is that the security model is short enough to hold in your head: the server stores an opaque string keyed by a UUID you choose, and the encryption key is derived locally from that UUID and a password, so the operator of a public instance learns nothing about your session. That same simplicity is its limit. Synchronization is one way only, the key derivation is a single MD5 cut to sixteen characters, and the encryption format changed once already at plugin version 0.1.5, so treat any stored blob as tied to the client version that wrote it. Run your own instance rather than a listed third party server, and if you are scripting against it, copy the decrypt function out of the README rather than assuming the platform `node` example reflects your own schema.
Frequently asked questions
Is it safe to sync browser cookies through a CookieCloud server?
The payload is encrypted on the client before upload, so the server stores ciphertext and the key comes from your UUID and password combined. Anyone who knows only the UUID still gets the encrypted blob back and can overwrite it with a POST to `/update`, which is why the README recommends running your own instance rather than a public one. The derivation is a single MD5 with no work factor, so a long random password is the part that matters.
Does CookieCloud work with Firefox?
Not from the store. The extension officially supports Chrome and Edge, other Chromium browsers might work untested, and for Firefox the README expects you to compile it yourself with `pnpm build --target=firefox-mv2`. Firefox's cookie format differs from Chrome's and the two cannot be mixed, so a build is not enough to sync Chrome cookies into Firefox.
How do I decrypt cookies from CookieCloud in my own script?
Derive the key by MD5 hashing the UUID joined to the password with a hyphen and taking the first sixteen characters, then AES decrypt and JSON.parse the result. The README prints that as a short CryptoJS function, and the repository ships a Python version plus fixed-IV implementations under `examples/`, which are the ones to use from a current client since version 0.3.0 moved to a fixed IV.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/easychen-cookiecloud)