Skip to content

Send login cookies from a browser extension - #15

Merged
stefan-azentrius merged 2 commits into
mainfrom
cookie-extension
Oct 25, 2026
Merged

stefan-azentrius merged 2 commits into
mainfrom
cookie-extension

Conversation

@stefan-azentrius

Copy link
Copy Markdown
Contributor

Summary

Sharewerk needed the cookies of a logged-in account as a file put on the server by hand, and every download got the whole file. Now a Chrome extension sends them whenever you log in, per platform.

Server

  • app/cookies.py: the platforms with their login cookies (Instagram sessionid, Facebook c_user and xs). Sent cookies are kept in /data/cookies/<platform>.txt (mode 600, folder 700, written atomically).
  • PUT /api/cookies/<platform> takes the cookies as chrome.cookies returns them. It refuses cookies of another domain (cookie_domain), a set without the login cookies (cookie_session_missing), fields with anything but printable ASCII or longer than 4 KB, expiry dates that are not a finite time up to the year 9999, JSON it cannot read (cookies_invalid), more than 100 cookies and bodies over 512 KB. It needs the app's login (or the proxy's) and the header X-Sharewerk-Client: extension, which a web page cannot send to another site without a CORS preflight. Cookie values never reach a log or an answer.
  • GET /api/cookies (state, source and time per platform) and DELETE /api/cookies/<platform>.
  • Each download gets only the cookies of its own platform: the sent ones, else the matching lines of SHAREWERK_COOKIES_FILE. Before, a Facebook download got the Instagram cookies too.
  • /healthz adds cookie_states per platform. cookies stays the Instagram state, so monitors on "cookies":"expired" keep working. missing now also covers a cookie file without cookies of that platform; a cookie file that exists but cannot be read is logged once (without its content).
  • A download's verdict on the cookies counts from when its job was created: cookies that arrive while it runs (a new login after a login error) stay unknown instead of being marked expired by a job that still had the old ones.

Extension (extension/, Chrome, Manifest V3)

  • Watches the login cookies. After a login or a new session it sends the cookies of that platform, but only for the account ID you confirmed once in its menu; another account in the same browser waits for your confirmation. The Instagram account ID comes from the session cookie that is sent (falling back to ds_user_id); a login without an account ID is never confirmed or sent, and a click confirms only the account the menu showed.
  • Uses the browser's login at Sharewerk (the token login, or a proxy such as Pomerium). A redirect or 401 shows "Sharewerk asks for a login"; the cookies go out once you sign in there (at most one retry a minute), on the hourly retry, or when the menu opens. A new Sharewerk address, or cookies Sharewerk no longer has from the extension, get the login again.
  • Permissions: cookies, storage, alarms, the hosts instagram.com and facebook.com. The Sharewerk address is an optional host permission, granted on the settings page. No content scripts, no remote code.
  • Menu and settings page follow the Azentrius Design System: the build copies app/static/azentrius/, theme.js and icons.js unchanged. Texts in English and German. The icons come from scripts/app-icon-sharewerk.py (new sizes 16 to 128).
  • tools/build-extension.py builds dist/sharewerk-extension-VERSION/ and a reproducible zip, with the version of app/__init__.py. The release workflow attaches the zip with its checksum and build provenance, and now also runs the JavaScript tests.

Docs: README sections "Browser extension (Chrome)" and "Cookie file by hand", health check, security notes and troubleshooting; NOTICE.

Test

  • tests/test_cookie_upload.py (36): upload, host-only and session cookies, foreign and lookalike domains, missing login cookies, fields that could break the file (also U+2028 and U+0085), infinite, huge or negative expiry dates and deeply nested JSON (422, not 500), malformed requests, missing header, token login, no values in answers or logs, delete, unwritable volume, cookies per platform for downloads, state per platform, cookies that arrive while a job runs, an unreadable cookie file.
  • tests/test_extension_build.py (5): version, unchanged copies, every referenced file present, reproducible zip.
  • tests/js/extension.test.mjs (21): the sending logic against a fake chrome API (confirmation of the shown account only, a login without account ID, the account from the session cookie, no second send of the same login, another account, a new address and lost cookies, missing Sharewerk login, errors, offline, no address or permission, delete), address validation, texts in both languages, manifest permissions, platforms in step with the server.
  • Existing tests adjusted for cookie_states and for cookie files with (fake) cookie lines.
  • Locally: ruff clean, pytest 140 passed and 3 skipped (no FFmpeg), node --test 35 of 35, git diff --check clean, gitleaks hook passed.
  • Menu and settings page checked in a browser with a stand-in for the chrome API. Not checked yet: the extension in a real Chrome with a real Pomerium login.

A Chrome extension (extension/) sends the cookies of Instagram and
Facebook to Sharewerk after a login there, only for the account confirmed
once in its menu, with the browser's login at Sharewerk.

The server keeps them per platform in the data volume (PUT, GET and
DELETE /api/cookies), each download gets only the cookies of its own
platform, and /healthz adds cookie_states per platform. The release
workflow builds the extension zip with checksum and build provenance.
Comment thread tests/test_cookie_upload.py Fixed
CodeQL read the substring checks for a domain as URL sanitization.
@stefan-azentrius
stefan-azentrius merged commit acaa1e7 into main Oct 25, 2026
8 checks passed
@stefan-azentrius
stefan-azentrius deleted the cookie-extension branch October 25, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants